From 88bc0671eca20d3c90bd3d81de1d8363dde59cd2 Mon Sep 17 00:00:00 2001 From: AviationNerd Date: Fri, 13 Nov 2026 00:42:28 +0000 Subject: [PATCH 1/4] fix(dashboard): make agent sign-in terminals usable Opening agent setup ran its login command before the user chose to run it, and forced the terminal back to main after every tab change. Leave the shell available for typing and provide an explicit login or install action. Keep new tabs selected, prevent alt-click cursor moves into output, and expose terminal sizing controls on desktop. --- docs/dashboard-frontend.md | 11 ++- docs/terminal.md | 10 ++- web/src/components/agents/agent-setup.tsx | 12 ++- web/src/components/dock.test.ts | 57 +++++++++++++- web/src/components/dock.tsx | 19 +++-- web/src/components/xterm-host.test.tsx | 11 +++ web/src/components/xterm-host.tsx | 1 + .../routes/environment/terminal-dock.test.tsx | 44 +++++++++++ web/src/routes/environment/terminal-dock.tsx | 74 +++++++++++++++---- 9 files changed, 202 insertions(+), 37 deletions(-) diff --git a/docs/dashboard-frontend.md b/docs/dashboard-frontend.md index cf66da07e..508dbd5b3 100644 --- a/docs/dashboard-frontend.md +++ b/docs/dashboard-frontend.md @@ -6077,10 +6077,13 @@ else `default_mode`. **Install and log in** calls `agent.install` with `enhanced` set when Enhanced is chosen and shows the answer: the log tail in an **Install output** disclosure, open when `error` is set, and **Install failed** with the error. -Once the agent is installed it mounts an Environment dock with the agent's -login command typed. A gateway without `agent.install` types the -install command there instead, and one without a terminal socket prints the -`aether terminal` commands. **Check** re-reads `agent.list` and lists +Once the agent is installed it mounts an Environment dock. The login or +install command appears above the dock. Its Run button becomes available +when the main terminal is ready; selecting another tab disables it. Resize +the dock by dragging its visible top grip or using the expand/shrink button; +both stop at the space available to the dock. A gateway without a terminal +socket prints the `aether terminal` commands. +**Check** re-reads `agent.list` and lists **Installed**, **Enhanced installed** and **Login found** or **No login found**, or **Signed out:** or **Sign-in refused:** with `login_problem` when the server reports one; diff --git a/docs/terminal.md b/docs/terminal.md index f75d62c64..f87c57719 100644 --- a/docs/terminal.md +++ b/docs/terminal.md @@ -127,6 +127,10 @@ text size** (on a phone, **Fit to width** in its place), **Copy selection**, **C image…**. Sessions add **Captures…**. **Copy screen** copies the visible rows, which is how to copy on a touch screen. +During agent sign-in, resize the Environment terminal by dragging its visible +top grip or using its expand/shrink button. It stops at the height available +to it. + ### Messages and notes Talk to the agent in the **Chat** view's composer. Press **Enter** or @@ -310,9 +314,9 @@ by UNKNOWN port 65535`: ## Sign in to agents and set up GitHub -The Agents page and onboarding embed the environment terminal with the agent's -login command typed in. Press Enter to run it. Login state lives in your home, -so you do not save the environment. See +The Agents page and onboarding show the login or install command above the +terminal. Use its Run button when the main terminal is ready. +Login state lives in your home, so you do not save the environment. See [environment-home.md](environment-home.md#set-up-an-agent) and [GitHub](environment-home.md#connect-github). diff --git a/web/src/components/agents/agent-setup.tsx b/web/src/components/agents/agent-setup.tsx index 5c75c87a4..eb7f1f961 100644 --- a/web/src/components/agents/agent-setup.tsx +++ b/web/src/components/agents/agent-setup.tsx @@ -148,7 +148,7 @@ export function AgentSetup({ ) : (

{typedInstall - ? 'The install command is typed into your Environment terminal below. Press Enter to run it.' + ? <>Run {typedInstall} in your Environment terminal below. : `Install the ${agent.executable ?? agent.name} executable into ~/.local/bin from your Environment terminal below.`}

)} @@ -172,10 +172,16 @@ export function AgentSetup({ <> {login && (

- Log in once: {login.command} is typed into your environment terminal. {login.hint} + Log in once by running {login.command} in your environment terminal. {login.hint}

)} - + ) : ( <> diff --git a/web/src/components/dock.test.ts b/web/src/components/dock.test.ts index 0de198bb4..6f6989e7e 100644 --- a/web/src/components/dock.test.ts +++ b/web/src/components/dock.test.ts @@ -36,7 +36,56 @@ describe('Dock controls', () => { expect(screen.getByRole('status')).toHaveProperty('textContent', 'At most 2 tabs') }) - it('trades the drag separator for half and full heights on a touch screen', () => { + it('shows desktop resize affordances and sizes within the available height', () => { + atViewport(1440, { height: 900, pointer: 'fine' }) + const onHeightChange = vi.fn() + const props = (height: number) => ({ + tabs: [{ id: 't1', label: 't1' }], + activeTab: 't1', + onSelectTab: vi.fn(), + maxTabs: 1, + height, + onHeightChange, + collapsed: false, + onToggleCollapse: vi.fn(), + children: createElement('div'), + }) + + const view = render(createElement(Dock, props(240))) + screen.getByRole('separator', { name: 'Resize terminal' }) + fireEvent.click( + screen.getByRole('button', { name: 'Expand terminal to its available height' }), + ) + expect(onHeightChange).toHaveBeenLastCalledWith(700) + + view.rerender(createElement(Dock, props(700))) + fireEvent.click( + screen.getByRole('button', { name: 'Shrink terminal to half its available height' }), + ) + expect(onHeightChange).toHaveBeenLastCalledWith(350) + }) + + it('keeps fill docks free of resize and collapse controls', () => { + atViewport(1440, { height: 900, pointer: 'fine' }) + render(createElement(Dock, { + tabs: [{ id: 't1', label: 't1' }], + activeTab: 't1', + onSelectTab: vi.fn(), + maxTabs: 1, + height: 240, + onHeightChange: vi.fn(), + collapsed: false, + onToggleCollapse: vi.fn(), + containment: 'fill', + children: createElement('div'), + })) + + expect(screen.queryByRole('separator')).toBeNull() + expect(screen.queryByRole('button', { name: /available height/ })).toBeNull() + expect(screen.queryByRole('button', { name: 'Collapse terminal' })).toBeNull() + }) + + it('trades the drag separator for half and maximum heights on a touch screen', () => { // A phone: the separator is a drag, and a finger has no drag to give it. atViewport(390, { height: 844, pointer: 'coarse' }) const onHeightChange = vi.fn() @@ -55,13 +104,13 @@ describe('Dock controls', () => { const view = render(createElement(Dock, props(240))) expect(screen.queryByRole('separator')).toBeNull() - // Half and full are measured from the room the dock has, which here is + // Half and maximum are measured from the room the dock has, which here is // the viewport less the shell's own chrome. - fireEvent.click(screen.getByRole('button', { name: /full screen/ })) + fireEvent.click(screen.getByRole('button', { name: /available height/ })) expect(onHeightChange).toHaveBeenLastCalledWith(644) view.rerender(createElement(Dock, props(644))) - fireEvent.click(screen.getByRole('button', { name: /half the screen/ })) + fireEvent.click(screen.getByRole('button', { name: /half its available height/ })) expect(onHeightChange).toHaveBeenLastCalledWith(322) }) diff --git a/web/src/components/dock.tsx b/web/src/components/dock.tsx index e84c41d94..fb71aa6ac 100644 --- a/web/src/components/dock.tsx +++ b/web/src/components/dock.tsx @@ -115,7 +115,6 @@ export function Dock({ ) const min = Math.min(requiredMinimum, max) const currentHeight = Math.min(max, Math.max(min, height)) - // A finger cannot drag the edge, so touch toggles between half and full height. const halfHeight = Math.min(max, Math.max(min, Math.round(max / 2))) const full = currentHeight >= max const index = Math.max( @@ -254,10 +253,16 @@ export function Dock({ className={cn( focusRing, // Without `touch-none` the browser claims a pen or trackpad drag - // as a pan and cancels the pointer stream this listens to. - 'absolute inset-x-0 -top-px z-10 h-1 cursor-row-resize touch-none bg-transparent transition-colors hover:bg-accent/20 focus-visible:bg-accent/20', + // as a pan and cancels the pointer stream this listens to. The + // strip ends at the dock edge so it cannot cover header controls. + 'group absolute inset-x-0 -top-1.5 z-10 flex h-1.5 cursor-row-resize touch-none items-center justify-center bg-transparent transition-colors hover:bg-accent/20 focus-visible:bg-accent/20', )} - /> + > +