From e8c4020432c24361098e363de2d51f244b57bb6d Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Fri, 31 Jul 2026 12:46:13 -0400 Subject: [PATCH 1/2] Collapse the steering bar by default and expand it on bar click The steering dock on run pages now starts collapsed, staying out of the way until the operator opens it. A run that is interrupted and waiting for steering still forces the dock open. While collapsed, the whole dock header is a click target that expands it. Clicks on buttons in the bar (Interrupt, the chevron) keep their own behavior, and the chevron remains the keyboard/assistive-tech toggle. The interview dock shares the shell, so it gets the same click-to-open behavior. Co-Authored-By: Claude Fable 5 --- .../app/components/run-dock.test.tsx | 56 ++++++++++++++++++- apps/fabro-web/app/components/run-dock.tsx | 18 +++++- .../app/components/steer-bar.test.tsx | 6 +- apps/fabro-web/app/components/steer-bar.tsx | 4 +- 4 files changed, 76 insertions(+), 8 deletions(-) diff --git a/apps/fabro-web/app/components/run-dock.test.tsx b/apps/fabro-web/app/components/run-dock.test.tsx index 56c5a1599..a6d238e49 100644 --- a/apps/fabro-web/app/components/run-dock.test.tsx +++ b/apps/fabro-web/app/components/run-dock.test.tsx @@ -9,7 +9,7 @@ import { import TestRenderer, { act } from "react-test-renderer"; import { setupReactTestEnv } from "../lib/test-utils"; -import { DockComposer } from "./run-dock"; +import { DockComposer, RunDockShell } from "./run-dock"; const mountedRenderers: TestRenderer.ReactTestRenderer[] = []; let teardownReactEnv: (() => void) | undefined; @@ -26,6 +26,60 @@ afterEach(() => { teardownReactEnv = undefined; }); +describe("RunDockShell", () => { + function mountShell( + collapsed: boolean, + onCollapsedChange: (collapsed: boolean) => void, + ) { + let renderer!: TestRenderer.ReactTestRenderer; + act(() => { + renderer = TestRenderer.create( + } + />, + ); + }); + mountedRenderers.push(renderer); + return renderer; + } + + function clickableDivs(renderer: TestRenderer.ReactTestRenderer) { + return renderer.root.findAll( + (node) => node.type === "div" && node.props.onClick !== undefined, + ); + } + + test("the whole collapsed bar is a click target that expands it", () => { + const onCollapsedChange = mock((_collapsed: boolean) => undefined); + const renderer = mountShell(true, onCollapsedChange); + + const [header] = clickableDivs(renderer); + expect(header).toBeDefined(); + act(() => header.props.onClick({ target: { closest: () => null } })); + expect(onCollapsedChange).toHaveBeenCalledWith(false); + }); + + test("clicks on header buttons do not also expand the bar", () => { + const onCollapsedChange = mock((_collapsed: boolean) => undefined); + const renderer = mountShell(true, onCollapsedChange); + + const [header] = clickableDivs(renderer); + act(() => header.props.onClick({ target: { closest: () => ({}) } })); + expect(onCollapsedChange).not.toHaveBeenCalled(); + }); + + test("the expanded header is not a click target", () => { + const onCollapsedChange = mock((_collapsed: boolean) => undefined); + const renderer = mountShell(false, onCollapsedChange); + expect(clickableDivs(renderer)).toHaveLength(0); + }); +}); + describe("DockComposer", () => { test("describes its keyboard behavior to assistive technology", () => { let renderer!: TestRenderer.ReactTestRenderer; diff --git a/apps/fabro-web/app/components/run-dock.tsx b/apps/fabro-web/app/components/run-dock.tsx index 4e26daa3a..ce3ce0774 100644 --- a/apps/fabro-web/app/components/run-dock.tsx +++ b/apps/fabro-web/app/components/run-dock.tsx @@ -99,7 +99,23 @@ export function RunDockShell({ className, )} > -
+ {/* While collapsed, the whole bar expands on click. Clicks that land on + a button (Interrupt, the chevron) keep their own behavior. The + chevron button stays the keyboard/assistive-tech toggle. */} +
{ + if ((event.target as HTMLElement).closest("button")) return; + onCollapsedChange(false); + } + : undefined + } + > {/* A live region: the dock changing to a state that needs the operator has to reach assistive tech, not only the eye. */} diff --git a/apps/fabro-web/app/components/steer-bar.test.tsx b/apps/fabro-web/app/components/steer-bar.test.tsx index b13498af6..583a68750 100644 --- a/apps/fabro-web/app/components/steer-bar.test.tsx +++ b/apps/fabro-web/app/components/steer-bar.test.tsx @@ -98,11 +98,7 @@ describe("SteerBar", () => { }); mountedRenderers.push(renderer); - act(() => { - renderer.root - .findByProps({ "aria-label": "Collapse Steer running agent" }) - .props.onClick(); - }); + // The dock starts collapsed by default. expect( renderer.root.findByProps({ "aria-label": "Expand Steer running agent", diff --git a/apps/fabro-web/app/components/steer-bar.tsx b/apps/fabro-web/app/components/steer-bar.tsx index 1b6e61f7c..4ec85bbcb 100644 --- a/apps/fabro-web/app/components/steer-bar.tsx +++ b/apps/fabro-web/app/components/steer-bar.tsx @@ -57,7 +57,9 @@ export function SteerBar({ ref, }: SteerBarProps) { const [errorMessage, setErrorMessage] = useState(null); - const [collapsePreferred, setCollapsePreferred] = useState(false); + // Steering is an occasional control, so the dock starts collapsed and + // stays out of the way until the operator opens it. + const [collapsePreferred, setCollapsePreferred] = useState(true); const textareaRef = useRef(null); const steer = useSteerRun(runId); const interrupt = useInterruptRun(runId); From 4cb983ce1d7b068d4bf7d23a49937fa928b931e9 Mon Sep 17 00:00:00 2001 From: Release Repro Date: Sat, 1 Aug 2026 09:24:34 -0400 Subject: [PATCH 2/2] Guard steering bar click target --- apps/fabro-web/app/components/run-dock.test.tsx | 9 +++++++++ apps/fabro-web/app/components/run-dock.tsx | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/fabro-web/app/components/run-dock.test.tsx b/apps/fabro-web/app/components/run-dock.test.tsx index a6d238e49..045b41445 100644 --- a/apps/fabro-web/app/components/run-dock.test.tsx +++ b/apps/fabro-web/app/components/run-dock.test.tsx @@ -73,6 +73,15 @@ describe("RunDockShell", () => { expect(onCollapsedChange).not.toHaveBeenCalled(); }); + test("clicks with a non-element target still expand the bar", () => { + const onCollapsedChange = mock((_collapsed: boolean) => undefined); + const renderer = mountShell(true, onCollapsedChange); + + const [header] = clickableDivs(renderer); + act(() => header.props.onClick({ target: {} })); + expect(onCollapsedChange).toHaveBeenCalledWith(false); + }); + test("the expanded header is not a click target", () => { const onCollapsedChange = mock((_collapsed: boolean) => undefined); const renderer = mountShell(false, onCollapsedChange); diff --git a/apps/fabro-web/app/components/run-dock.tsx b/apps/fabro-web/app/components/run-dock.tsx index ce3ce0774..cb2a89c0e 100644 --- a/apps/fabro-web/app/components/run-dock.tsx +++ b/apps/fabro-web/app/components/run-dock.tsx @@ -110,7 +110,9 @@ export function RunDockShell({ onClick={ collapsed ? (event) => { - if ((event.target as HTMLElement).closest("button")) return; + if ((event.target as HTMLElement | null)?.closest?.("button")) { + return; + } onCollapsedChange(false); } : undefined