diff --git a/frontend/README.md b/frontend/README.md index 3c2ff7db..a2a28c22 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -25,7 +25,7 @@ runs lint, tests, and build for PRs into `main` and `codex/` stack branches. - The work sidebar and searchable installed app roster - Settings -- Dashboard, Events, and Usage +- Dashboard, Activity, and Usage - Shared routing and fallback behavior. Bundled app UI lives under `src/apps//`. Its module calls @@ -35,7 +35,7 @@ Import the module one time from `src/apps/index.ts`. The shell finds the registration and does not hardcode the app name. The work sidebar keeps the same destinations across app pages. The Dashboard -opens at `/`. Events and Usage have shared routes. App-declared navigation appears below the page +opens at `/`. Activity and Usage have shared routes. App-declared navigation appears below the page header. Settings opens from the bottom of the sidebar. Below 650 px, a navigation button opens a modal drawer. Escape closes the drawer and returns focus to the button. @@ -108,6 +108,24 @@ router hooks, decode each subject component once, and let `subjectApi` encode the HTTP path. Canonical slug replacement preserves the raw query and hash and only runs while the owner page is visible. +## Activity + +Activity at `/events` shows recorded changes to app work. Search matches recorded +work labels only. App, exact Activity type, and day filters apply to the same +HTTP history and live stream. Day boundaries use the operator timezone and the +next local midnight. The first HTTP page supplies type choices for the selected +app scope. Pagination and live updates retain those choices. + +Each selected row opens a 360 px panel, or a full-width page on narrow screens. +The panel reads the exact saved artifact and uses the shared Markdown renderer. +It shows historical request and reply facts. Owner links retain the recorded run +and request round; current approval controls stay on the owner page. + +Filters and selection remain in the URL when the operator leaves and returns. +Close and Escape restore row focus. New rows wait behind **New activity** while +older content or details are in use. **Pause updates** affects this feed only. +The feed shows connection state and retains its context after a failed read. + ## API and live data Shared requests use `src/api/client.ts`. The event feed and transcript diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 017b3d17..95974779 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -136,9 +136,9 @@ describe('command center navigation', () => { expect(within(pages).getByRole('link', { name: 'History' }).getAttribute('aria-current')).toBe( 'page', ) - fireEvent.click(screen.getByRole('link', { name: 'Events' })) + fireEvent.click(screen.getByRole('link', { name: 'Activity' })) await screen.findByRole('heading', { name: 'Events feed' }) - expect(screen.getByRole('link', { name: 'Events' }).getAttribute('aria-current')).toBe('page') + expect(screen.getByRole('link', { name: 'Activity' }).getAttribute('aria-current')).toBe('page') await act(async () => { window.history.back() }) @@ -166,7 +166,7 @@ describe('command center navigation', () => { }) const apps = screen.getByRole('navigation', { name: 'Apps' }) expect(within(apps).getAllByRole('link')).toHaveLength(1) - expect(screen.getByRole('link', { name: 'Events' })).toBeTruthy() + expect(screen.getByRole('link', { name: 'Activity' })).toBeTruthy() fireEvent.change(screen.getByRole('textbox', { name: 'Find an app' }), { target: { value: 'missing' }, }) @@ -193,7 +193,7 @@ describe('command center navigation', () => { it('leaves a shared page with Escape to where the operator came from', async () => { renderApp('/notes') await screen.findByRole('heading', { name: 'Notes home' }) - fireEvent.click(screen.getByRole('link', { name: 'Events' })) + fireEvent.click(screen.getByRole('link', { name: 'Activity' })) await screen.findByRole('heading', { name: 'Events feed' }) await act(async () => { fireEvent.keyDown(window, { key: 'Escape' }) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3658bdef..d247f128 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -32,7 +32,7 @@ import { registerInstalledApps } from './apps/installed' import { appHome, appLabel, appOwning, getAppUI, registeredApps } from './apps/registry' const ROUTER_BASE = import.meta.env.BASE_URL.replace(/\/$/, '') -const SHELL_PAGES: Record = { '/': 'Dashboard', '/events': 'Events', '/usage': 'Usage' } +const SHELL_PAGES: Record = { '/': 'Dashboard', '/events': 'Activity', '/usage': 'Usage' } export function App({ account }: { account: Account }) { const unsavedFormRef = useRef(null) @@ -239,7 +239,7 @@ function AppShell({ useEffect(() => { function onKey(event: KeyboardEvent) { - if (hidden || document.querySelector('dialog[open]')) return + if (hidden || event.defaultPrevented || document.querySelector('dialog[open]')) return const meta = event.metaKey || event.ctrlKey if (meta && event.key.toLowerCase() === 'k' && defaultApp) { event.preventDefault() @@ -297,7 +297,7 @@ function AppShell({ aria-current={location === '/events' ? 'page' : undefined} >