From d1003f1c68253d68678af953a262a6d7f256ccf3 Mon Sep 17 00:00:00 2001 From: hkonsti <45428767+hkonsti@users.noreply.github.com> Date: Thu, 5 Nov 2026 23:25:18 +0000 Subject: [PATCH] move back and forward into the desktop app's title bar, and drop the in-page back arrows --- .../web/src/components/activity-timeline.tsx | 7 - .../web/src/components/agent-timeline.tsx | 7 +- packages/web/src/components/back-button.tsx | 20 --- .../src/components/desktop-history.test.tsx | 121 ++++++++++++++++++ .../web/src/components/desktop-history.tsx | 71 ++++++++++ packages/web/src/components/not-found.tsx | 12 +- packages/web/src/components/root.tsx | 6 +- packages/web/src/components/run-timeline.tsx | 10 +- .../web/src/components/timeline-chart.tsx | 13 +- packages/web/src/index.css | 11 ++ packages/web/src/lib/back.test.ts | 30 ----- packages/web/src/lib/back.ts | 25 ---- packages/web/src/pages/add-bank-page.tsx | 12 +- .../web/src/pages/add-connection-page.tsx | 10 +- packages/web/src/pages/add-database-page.tsx | 12 +- packages/web/src/pages/add-email-page.tsx | 12 +- packages/web/src/pages/add-mcp-page.tsx | 12 +- packages/web/src/pages/agent-page.tsx | 34 +++-- packages/web/src/pages/agent-setup-page.tsx | 12 +- packages/web/src/pages/connection-page.tsx | 32 ++--- packages/web/src/pages/new-agent-page.tsx | 10 +- packages/web/src/pages/sandboxes-page.tsx | 10 +- packages/web/src/pages/task-page.tsx | 28 +--- packages/web/src/pages/vault-page.tsx | 8 +- 24 files changed, 265 insertions(+), 260 deletions(-) delete mode 100644 packages/web/src/components/back-button.tsx create mode 100644 packages/web/src/components/desktop-history.test.tsx create mode 100644 packages/web/src/components/desktop-history.tsx delete mode 100644 packages/web/src/lib/back.test.ts delete mode 100644 packages/web/src/lib/back.ts diff --git a/packages/web/src/components/activity-timeline.tsx b/packages/web/src/components/activity-timeline.tsx index b4cf7d43..8c2164b1 100644 --- a/packages/web/src/components/activity-timeline.tsx +++ b/packages/web/src/components/activity-timeline.tsx @@ -79,8 +79,6 @@ type Props = { // The windows the view is about to pass through, zooming in on a bar, in order; none otherwise. // Rows make room for every one of them as the zoom sets off, so they move with it. ahead: Window[]; - // Where a task's page comes back to: this page, with the window as it is. - backTo: string; }; // Who is doing what (the layout in lib/activity-timeline.ts): every agent's row over a window of @@ -103,7 +101,6 @@ export function ActivityTimeline({ onOpenEntry, onZoom, ahead, - backTo, }: Props) { const [measure, width] = useChartWidth(); const requestTooltip = useRequestTooltip(); @@ -249,7 +246,6 @@ export function ActivityTimeline({ top={barTop(laneIndex)} windowMs={window.to.getTime() - window.from.getTime()} onZoom={onZoom} - backTo={backTo} /> void; - backTo: string; }) { const org = useOrgSlug(); const [task] = bar.tasks; @@ -519,7 +513,6 @@ function BarTarget({ const className = cn(MOVES, 'hover:bg-foreground/5'); const pageOf = (one: ActivityTask) => ({ to: `/${org}/activity/tasks/${encodeURIComponent(one.id)}`, - state: {back: backTo, then: null}, }); if (!task) { return null; diff --git a/packages/web/src/components/agent-timeline.tsx b/packages/web/src/components/agent-timeline.tsx index dc5900ad..808eff00 100644 --- a/packages/web/src/components/agent-timeline.tsx +++ b/packages/web/src/components/agent-timeline.tsx @@ -83,8 +83,7 @@ export function AgentTimeline({ connections: Connection[]; }) { const org = useOrgSlug(); - // The window is kept in the address, so coming back to the page, by the browser's back button - // or a task page's, finds it as it was. + // The window is kept in the address, so going back to the page finds it as it was. const [params, setParams] = useSearchParams(); const location = useLocation(); const [minutes, setMinutes] = useState(() => windowFromParams(params).minutes); @@ -296,9 +295,6 @@ export function AgentTimeline({ }, [minutes, end, location.search, setParams]); // The window's length as read: whole minutes, which it is but for a moment while zooming in. const wholeMinutes = Math.round(minutes); - // Where a task's page comes back to: this page, with the window as it is now and the rest of the - // address, as the activity log's filters, as it was. - const backTo = `${location.pathname}${windowSearch(location.search, wholeMinutes, end)}`; // Scrolled or zoomed to where there is nothing, the timeline stays up, its rows going the way // they do, and only says so once the window has rested a second. @@ -393,7 +389,6 @@ export function AgentTimeline({ onOpenEntry={setOpened} onZoom={zoomTo} ahead={ahead} - backTo={backTo} /> )} {!inWindow && resting && ( diff --git a/packages/web/src/components/back-button.tsx b/packages/web/src/components/back-button.tsx deleted file mode 100644 index f6468c75..00000000 --- a/packages/web/src/components/back-button.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import {ArrowLeftIcon} from 'lucide-react'; -import {Link} from 'react-router'; -import type {Back} from '@/lib/back'; -import {Button} from '@proxy/ui/components/button'; - -// The arrow at the left of a detail screen's header, leading back to the list it came from, or to -// `to` with `state` where the screen knows where it came from (lib/back.ts). -export function BackButton({to, label, state}: {to: string; label: string; state?: Back | null}) { - return ( - - ); -} diff --git a/packages/web/src/components/desktop-history.test.tsx b/packages/web/src/components/desktop-history.test.tsx new file mode 100644 index 00000000..54d291dc --- /dev/null +++ b/packages/web/src/components/desktop-history.test.tsx @@ -0,0 +1,121 @@ +/// + +import {afterEach, beforeEach, describe, expect, test} from 'bun:test'; +import {act, cleanup, fireEvent, render, screen} from '@testing-library/react'; +import {createMemoryRouter, Outlet, RouterProvider} from 'react-router'; +import {TooltipProvider} from '@proxy/ui/components/tooltip'; +import {DesktopHistory} from '@/components/desktop-history'; + +// The window's history as the Navigation API tells it, which happy-dom has none of. +class FakeNavigation { + canGoBack = false; + canGoForward = false; + listeners = new Set<() => void>(); + + addEventListener(_type: 'currententrychange', listener: () => void) { + this.listeners.add(listener); + } + + removeEventListener(_type: 'currententrychange', listener: () => void) { + this.listeners.delete(listener); + } + + set(next: {canGoBack: boolean; canGoForward: boolean}) { + this.canGoBack = next.canGoBack; + this.canGoForward = next.canGoForward; + for (const listener of this.listeners) { + listener(); + } + } +} + +let history: FakeNavigation; + +beforeEach(() => { + history = new FakeNavigation(); + Object.assign(globalThis, {navigation: history}); +}); + +afterEach(() => { + cleanup(); + Reflect.deleteProperty(globalThis, 'navigation'); +}); + +function renderAt(entries: string[], index: number) { + const router = createMemoryRouter( + [ + { + element: ( + <> + + + + ), + children: [ + {path: '/connections', element:

Connections

}, + {path: '/connections/c1', element:

Gmail

}, + ], + }, + ], + {initialEntries: entries, initialIndex: index}, + ); + render( + + + , + ); +} + +function button(name: string): HTMLElement { + return screen.getByRole('button', {name}); +} + +function isOff(name: string): boolean { + return button(name).hasAttribute('disabled'); +} + +describe('DesktopHistory', () => { + test('is off where the history has nowhere to go, and follows it as it changes', () => { + renderAt(['/connections'], 0); + + expect(isOff('Back')).toBe(true); + expect(isOff('Forward')).toBe(true); + + act(() => history.set({canGoBack: true, canGoForward: false})); + + expect(isOff('Back')).toBe(false); + expect(isOff('Forward')).toBe(true); + }); + + test('goes back and forward', async () => { + history.set({canGoBack: true, canGoForward: true}); + renderAt(['/connections', '/connections/c1'], 1); + expect(screen.getByText('Gmail')).toBeTruthy(); + + fireEvent.click(button('Back')); + expect(await screen.findByText('Connections')).toBeTruthy(); + + fireEvent.click(button('Forward')); + expect(await screen.findByText('Gmail')).toBeTruthy(); + }); + + test('goes back with ⌘[ and forward with ⌘]', async () => { + renderAt(['/connections', '/connections/c1'], 1); + + fireEvent.keyDown(window, {key: '[', metaKey: true}); + expect(await screen.findByText('Connections')).toBeTruthy(); + + fireEvent.keyDown(window, {key: ']', metaKey: true}); + expect(await screen.findByText('Gmail')).toBeTruthy(); + }); + + test("goes back and forward with the mouse's side buttons", async () => { + renderAt(['/connections', '/connections/c1'], 1); + + fireEvent.mouseUp(window, {button: 3}); + expect(await screen.findByText('Connections')).toBeTruthy(); + + fireEvent.mouseUp(window, {button: 4}); + expect(await screen.findByText('Gmail')).toBeTruthy(); + }); +}); diff --git a/packages/web/src/components/desktop-history.tsx b/packages/web/src/components/desktop-history.tsx new file mode 100644 index 00000000..9933917a --- /dev/null +++ b/packages/web/src/components/desktop-history.tsx @@ -0,0 +1,71 @@ +import {ChevronLeftIcon, ChevronRightIcon} from 'lucide-react'; +import {useEffect, useSyncExternalStore} from 'react'; +import {useNavigate} from 'react-router'; +import {IconButton} from '@/components/icon-button'; + +// The mouse's side buttons, as `MouseEvent.button` numbers them. +const MOUSE_BACK = 3; +const MOUSE_FORWARD = 4; + +function subscribe(onChange: () => void): () => void { + navigation.addEventListener('currententrychange', onChange); + return () => navigation.removeEventListener('currententrychange', onChange); +} + +// Back and forward in the desktop app's title bar, beside the traffic lights, where a browser has +// its own: the window has none. They step through the window's history, kept by the Navigation +// API, which counts only the app's own pages, never the splash pages (`data:`) shown before it. +// ⌘[ and ⌘], and a mouse's side buttons, do the same. +export function DesktopHistory() { + const navigate = useNavigate(); + const canGoBack = useSyncExternalStore(subscribe, () => navigation.canGoBack); + const canGoForward = useSyncExternalStore(subscribe, () => navigation.canGoForward); + + useEffect(() => { + const onKeyDown = (event: KeyboardEvent) => { + if (!event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) { + return; + } + if (event.key === '[') { + event.preventDefault(); + void navigate(-1); + return; + } + if (event.key === ']') { + event.preventDefault(); + void navigate(1); + } + }; + const onMouseUp = (event: MouseEvent) => { + if (event.button === MOUSE_BACK) { + void navigate(-1); + return; + } + if (event.button === MOUSE_FORWARD) { + void navigate(1); + } + }; + window.addEventListener('keydown', onKeyDown); + window.addEventListener('mouseup', onMouseUp); + return () => { + window.removeEventListener('keydown', onKeyDown); + window.removeEventListener('mouseup', onMouseUp); + }; + }, [navigate]); + + return ( +
+ void navigate(-1)}> + + + void navigate(1)} + > + + +
+ ); +} diff --git a/packages/web/src/components/not-found.tsx b/packages/web/src/components/not-found.tsx index c9ac6100..c58de96e 100644 --- a/packages/web/src/components/not-found.tsx +++ b/packages/web/src/components/not-found.tsx @@ -1,17 +1,9 @@ import {AppShell, PageTitle} from '@/components/app-shell'; -import {BackButton} from '@/components/back-button'; // What a detail page shows when its id no longer points at anything, after a delete for one. -export function NotFound({what, back, backLabel}: {what: string; back: string; backLabel: string}) { +export function NotFound({what}: {what: string}) { return ( - - - Not found - - } - > + Not found}>

This {what} does not exist anymore.

); diff --git a/packages/web/src/components/root.tsx b/packages/web/src/components/root.tsx index 1272abbe..083d01a6 100644 --- a/packages/web/src/components/root.tsx +++ b/packages/web/src/components/root.tsx @@ -1,12 +1,16 @@ import {Outlet} from 'react-router'; +import {DesktopHistory} from '@/components/desktop-history'; import {ReleaseNotice} from '@/components/release-notice'; +import {hasHiddenDesktopTitleBar} from '@/lib/desktop'; -// Around every page, the human side's and the agents' alike: the page, and word of a new release. +// Around every page, the human side's and the agents' alike: the page, word of a new release, and +// in the desktop app's title bar, back and forward. export function Root() { return ( <> + {hasHiddenDesktopTitleBar() && } ); } diff --git a/packages/web/src/components/run-timeline.tsx b/packages/web/src/components/run-timeline.tsx index 1ece054f..5c0f01d3 100644 --- a/packages/web/src/components/run-timeline.tsx +++ b/packages/web/src/components/run-timeline.tsx @@ -2,7 +2,7 @@ import type {ActivityTask, AuditEntry} from '@proxy/contracts/activity'; import type {Connection} from '@proxy/contracts/connections'; import {ChevronDownIcon, ChevronRightIcon, CornerDownRightIcon} from 'lucide-react'; import {Fragment, useId, useMemo, useState} from 'react'; -import {Link, useLocation} from 'react-router'; +import {Link} from 'react-router'; import { AxisLabels, ChartLegend, @@ -20,7 +20,6 @@ import {useRequestTooltip} from '@/hooks/use-request-tooltip'; import {useTitleOf} from '@/hooks/use-title-of'; import {useChartWidth} from '@/hooks/use-chart-width'; import {useOrgSlug} from '@/hooks/use-org'; -import {backFrom} from '@/lib/back'; import {formatTime} from '@/lib/format'; import { buildRunTimeline, @@ -264,7 +263,6 @@ function RowLabel({ onHover: (over: boolean) => void; }) { const org = useOrgSlug(); - const back = backFrom(useLocation()); const [task] = row.tasks; const own = row.depth === 0; const foldable = row.kind === 'group' || row.rolledIn > 0 || open; @@ -326,7 +324,6 @@ function RowLabel({ {row.kind === 'task' && task && !own && ( @@ -353,7 +350,6 @@ function RowTarget({ onHover: (over: boolean) => void; }) { const org = useOrgSlug(); - const back = backFrom(useLocation()); const [task] = row.tasks; const style = { left: row.start, @@ -383,9 +379,7 @@ function RowTarget({ return ( {to: string; state: Back}; + pageOf: (task: ActivityTask) => {to: string}; }) { return ( @@ -328,9 +327,9 @@ export function TasksMenu({ /** * A hit target over a track, in the layer over the chart, saying more on hover or focus: a link to - * a task's page, coming back the way `state` says (lib/back.ts); a button; a menu, opening where - * the tooltip would, the tooltip kept shut while it is open; or, where it opens nothing, a stop - * for the keyboard all the same, so what it says can be reached. + * a task's page; a button; a menu, opening where the tooltip would, the tooltip kept shut while it + * is open; or, where it opens nothing, a stop for the keyboard all the same, so what it says can be + * reached. */ export function ChartTarget({ action, @@ -340,7 +339,7 @@ export function ChartTarget({ onHover, children, }: { - action: {to: string; state: Back} | {onClick: () => void} | {menu: ReactNode} | null; + action: {to: string} | {onClick: () => void} | {menu: ReactNode} | null; label: string; className?: string; style: CSSProperties; @@ -365,7 +364,7 @@ export function ChartTarget({ return ; } if ('to' in action) { - return ; + return ; } if ('menu' in action) { return } />; diff --git a/packages/web/src/index.css b/packages/web/src/index.css index 28149901..405c5637 100644 --- a/packages/web/src/index.css +++ b/packages/web/src/index.css @@ -21,6 +21,17 @@ -webkit-app-region: drag; } +/* Back and forward (components/desktop-history.tsx), in the title bar to the right of the traffic + lights, over the drag surface and taking clicks rather than dragging the window. */ +.desktop-app [data-desktop-history] { + position: fixed; + z-index: 61; + top: 0; + left: 5rem; + height: var(--desktop-title-bar-height); + -webkit-app-region: no-drag; +} + /* The columns themselves reach the window's top, so their backgrounds continue under the traffic lights. Only their contents make room for the controls and transparent drag surface. */ .desktop-app [data-slot='sidebar-inner'], diff --git a/packages/web/src/lib/back.test.ts b/packages/web/src/lib/back.test.ts deleted file mode 100644 index ceb405a0..00000000 --- a/packages/web/src/lib/back.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {describe, expect, test} from 'bun:test'; -import {backFrom, backOf} from './back'; - -describe('backOf', () => { - test('reads the way back from a page’s state, as far as it goes', () => { - expect(backOf({state: null})).toBeNull(); - expect(backOf({state: {back: 3}})).toBeNull(); - expect(backOf({state: {back: '/w/activity?window=60'}})).toEqual({ - back: '/w/activity?window=60', - then: null, - }); - expect( - backOf({state: {back: '/w/activity/tasks/t1', then: {back: '/w?end=5', then: null}}}), - ).toEqual({ - back: '/w/activity/tasks/t1', - then: {back: '/w?end=5', then: null}, - }); - }); -}); - -describe('backFrom', () => { - test('is the page a link is followed from, with the page’s own way back', () => { - expect( - backFrom({pathname: '/w/activity/tasks/t1', search: '', state: {back: '/w?end=5'}}), - ).toEqual({ - back: '/w/activity/tasks/t1', - then: {back: '/w?end=5', then: null}, - }); - }); -}); diff --git a/packages/web/src/lib/back.ts b/packages/web/src/lib/back.ts deleted file mode 100644 index 23f6bdf0..00000000 --- a/packages/web/src/lib/back.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type {Location} from 'react-router'; -import {z} from 'zod'; - -// Where a page was come to from, kept in the router's location state as a link is followed, so -// the page's back arrow returns there rather than to a list it may not have come from: the address -// of the page before, with its own back in turn, so going back twice retraces the way, as from a -// sub-task's page to its task's and from there to the home page as it was. -export type Back = {back: string; then: Back | null}; - -// A way back further than one that can't be read is none. -const backSchema: z.ZodType = z.object({ - back: z.string(), - then: z.lazy(() => backSchema.nullable()).catch(null), -}); - -/** The way back the state of the page at `location` says, where it says one. */ -export function backOf(location: Pick): Back | null { - const parsed = backSchema.safeParse(location.state); - return parsed.success ? parsed.data : null; -} - -/** The way back for a link followed from the page at `location`: that page, and its own way back. */ -export function backFrom(location: Pick): Back { - return {back: `${location.pathname}${location.search}`, then: backOf(location)}; -} diff --git a/packages/web/src/pages/add-bank-page.tsx b/packages/web/src/pages/add-bank-page.tsx index cfc44b7d..f01b5625 100644 --- a/packages/web/src/pages/add-bank-page.tsx +++ b/packages/web/src/pages/add-bank-page.tsx @@ -1,6 +1,5 @@ import {useNavigate} from 'react-router'; import {AppShell, PageTitle} from '@/components/app-shell'; -import {BackButton} from '@/components/back-button'; import {ConnectBankForm} from '@/components/connect-bank-form'; import {ShareNote} from '@/components/share-choice'; import {PLAID_FORM} from '@/lib/integrations'; @@ -12,17 +11,10 @@ import {useOrgSlug} from '@/hooks/use-org'; export function AddBankPage() { const org = useOrgSlug(); const navigate = useNavigate(); - const {shared, query} = useShareChoice(); + const {shared} = useShareChoice(); return ( - - - {PLAID_FORM.title} - - } - > + {PLAID_FORM.title}}>

{PLAID_FORM.intro}

diff --git a/packages/web/src/pages/add-connection-page.tsx b/packages/web/src/pages/add-connection-page.tsx index ec78628f..b9f1fe83 100644 --- a/packages/web/src/pages/add-connection-page.tsx +++ b/packages/web/src/pages/add-connection-page.tsx @@ -1,6 +1,5 @@ import {useLoaderData, useNavigate, useSearchParams} from 'react-router'; import {AppShell, PageTitle} from '@/components/app-shell'; -import {BackButton} from '@/components/back-button'; import {IntegrationCatalog, SignInFailed} from '@/components/integration-catalog'; import {SharePicker} from '@/components/share-choice'; import {useExperiments} from '@/hooks/use-experiment'; @@ -22,14 +21,7 @@ export function AddConnectionPage() { const {shared, query} = useShareChoice(); return ( - - - Add connection - - } - > + Add connection}> - - {title} - - } - > + {title}}>

{intro}

diff --git a/packages/web/src/pages/add-email-page.tsx b/packages/web/src/pages/add-email-page.tsx index 0639db32..a4fd2067 100644 --- a/packages/web/src/pages/add-email-page.tsx +++ b/packages/web/src/pages/add-email-page.tsx @@ -1,6 +1,5 @@ import {useNavigate} from 'react-router'; import {AppShell, PageTitle} from '@/components/app-shell'; -import {BackButton} from '@/components/back-button'; import {ConnectEmailForm} from '@/components/connect-email-form'; import {ShareNote} from '@/components/share-choice'; import {EMAIL_FORM} from '@/lib/integrations'; @@ -12,17 +11,10 @@ import {useOrgSlug} from '@/hooks/use-org'; export function AddEmailPage() { const org = useOrgSlug(); const navigate = useNavigate(); - const {shared, query} = useShareChoice(); + const {shared} = useShareChoice(); return ( - - - {EMAIL_FORM.title} - - } - > + {EMAIL_FORM.title}}>

{EMAIL_FORM.intro}

diff --git a/packages/web/src/pages/add-mcp-page.tsx b/packages/web/src/pages/add-mcp-page.tsx index f7b4c1b0..7cf1c182 100644 --- a/packages/web/src/pages/add-mcp-page.tsx +++ b/packages/web/src/pages/add-mcp-page.tsx @@ -1,6 +1,5 @@ import {useNavigate} from 'react-router'; import {AppShell, PageTitle} from '@/components/app-shell'; -import {BackButton} from '@/components/back-button'; import {ConnectMcpForm} from '@/components/connect-mcp-form'; import {ShareNote} from '@/components/share-choice'; import {useShareChoice} from '@/hooks/use-share-choice'; @@ -11,17 +10,10 @@ import {useOrgSlug} from '@/hooks/use-org'; export function AddMcpPage() { const org = useOrgSlug(); const navigate = useNavigate(); - const {shared, query} = useShareChoice(); + const {shared} = useShareChoice(); return ( - - - Connect an MCP server - - } - > + Connect an MCP server}>

diff --git a/packages/web/src/pages/agent-page.tsx b/packages/web/src/pages/agent-page.tsx index 998242f3..3265787d 100644 --- a/packages/web/src/pages/agent-page.tsx +++ b/packages/web/src/pages/agent-page.tsx @@ -27,7 +27,6 @@ import {setConnectionDefaults} from '@/client/connections-client'; import {AppShell, PageTitle} from '@/components/app-shell'; import {AuditList} from '@/components/audit-list'; import {AgentLogo, IntegrationLogo} from '@/components/brand-logo'; -import {BackButton} from '@/components/back-button'; import {ConfirmDialog} from '@/components/confirm-dialog'; import {ConnectionAccess} from '@/components/connection-access'; import {IconButton} from '@/components/icon-button'; @@ -73,7 +72,7 @@ function AgentDetail({id}: {id: string}) { const apply = useApply(setError); const human = useHuman(); if (!agent || agent.id !== id || !human) { - return ; + return ; } const revoked = agent.revokedAt !== null; @@ -95,23 +94,20 @@ function AgentDetail({id}: {id: string}) { return ( - - - - - {agent.name} - - - + + + + {agent.name} + + } actions={ mayChange && ( diff --git a/packages/web/src/pages/agent-setup-page.tsx b/packages/web/src/pages/agent-setup-page.tsx index 0232a1c1..bf5c94f9 100644 --- a/packages/web/src/pages/agent-setup-page.tsx +++ b/packages/web/src/pages/agent-setup-page.tsx @@ -1,7 +1,6 @@ import {useLoaderData, useNavigate} from 'react-router'; import {AgentSetup} from '@/components/agent-setup'; import {AppShell, PageTitle} from '@/components/app-shell'; -import {BackButton} from '@/components/back-button'; import {NotFound} from '@/components/not-found'; import type {agentSetupLoader} from '@/loaders'; import {useOrgSlug} from '@/hooks/use-org'; @@ -13,18 +12,11 @@ export function AgentSetupPage() { const {agent} = useLoaderData(); const navigate = useNavigate(); if (!agent) { - return ; + return ; } return ( - - - Set up {agent.name} - - } - > + Set up {agent.name}}> {agent.revokedAt === null ? ( - ); + return ; } const withAccess = agentsWithAccess(agents, connection); @@ -88,21 +85,18 @@ export function ConnectionPage() { return ( - - - - - {connection.name ?? integration.name} - - - + + + + {connection.name ?? integration.name} + + } actions={ <> diff --git a/packages/web/src/pages/new-agent-page.tsx b/packages/web/src/pages/new-agent-page.tsx index 335faeb9..48f7d305 100644 --- a/packages/web/src/pages/new-agent-page.tsx +++ b/packages/web/src/pages/new-agent-page.tsx @@ -1,7 +1,6 @@ import {useLoaderData, useNavigate, useSearchParams} from 'react-router'; import {AddAgent} from '@/components/agent-setup'; import {AppShell, PageTitle} from '@/components/app-shell'; -import {BackButton} from '@/components/back-button'; import {SharePicker} from '@/components/share-choice'; import {useShareChoice} from '@/hooks/use-share-choice'; import type {agentsLoader} from '@/loaders'; @@ -17,14 +16,7 @@ export function NewAgentPage() { const [params] = useSearchParams(); return ( - - - New agent - - } - > + New agent}> {!params.has('agent') && ( - - Sandboxes - - } - > + Sandboxes}> (); if (!run) { - return ; + return ; } // Keyed by the task, so following a link to another task's page starts it afresh, its timeline // laid out and its description shown, rather than as the last task's was left. @@ -53,16 +50,11 @@ function TaskRunPage({ connections: Connection[]; }) { const org = useOrgSlug(); - const location = useLocation(); // The run shown: the first came with the page, and every later reading of it replaces it. const [run, setRun] = useState(initial); const [opened, setOpened] = useState(null); const [shown, setShown] = useState('goal'); const textId = useId(); - // Where the page was come to from, where that page said (lib/back.ts): the home page or the - // activity log with its timeline's window as it was, or the task this one was handed on from; - // else the activity log, at the task. - const cameFrom = backOf(location); const task = run.tasks.find((candidate) => candidate.id === run.task); const running = run.tasks.some( (candidate) => taskStatus(candidate, new Date(run.now)) === 'running', @@ -92,7 +84,7 @@ function TaskRunPage({ }, [running, org, run.task]); if (!task) { - return ; + return ; } const now = new Date(run.now); @@ -107,18 +99,7 @@ function TaskRunPage({ ...outcomeSummary(own), ]; return ( - - - {task.title} - - } - > + {task.title}}> {/* The page in four blocks, a little further apart than the shell sets them. */}

@@ -164,7 +145,6 @@ function TaskRunPage({ Handed on from{' '} {run.parent.title} diff --git a/packages/web/src/pages/vault-page.tsx b/packages/web/src/pages/vault-page.tsx index 5c2a0d47..7e4155f5 100644 --- a/packages/web/src/pages/vault-page.tsx +++ b/packages/web/src/pages/vault-page.tsx @@ -14,7 +14,6 @@ import {useState} from 'react'; import {useLoaderData} from 'react-router'; import {createRecord, deleteRecord, updateRecord} from '@/client/records-client'; import {AppShell, PageTitle} from '@/components/app-shell'; -import {BackButton} from '@/components/back-button'; import {RecordForm} from '@/components/record-form'; import {Row, RowHeader, RowList} from '@/components/row-list'; import {SideEmpty, SideToggle} from '@/components/share-toggle'; @@ -103,12 +102,7 @@ export function VaultPage() { return ( - - Vault - - } + title={Vault} actions={