diff --git a/dashboard/src/workspaces/explorer/ExplorerPage.dom.test.tsx b/dashboard/src/workspaces/explorer/ExplorerPage.dom.test.tsx index c9ea4b7842..bde8758569 100644 --- a/dashboard/src/workspaces/explorer/ExplorerPage.dom.test.tsx +++ b/dashboard/src/workspaces/explorer/ExplorerPage.dom.test.tsx @@ -329,9 +329,10 @@ describe('ExplorerPage independent lane lifecycle', () => { expect(knowledge.queryByText('0')).toBeNull(); expect(knowledge.getByText('UNAVAILABLE')).toBeTruthy(); - // The ready lanes carry their own counts and grade, untouched. + // The ready lanes carry their own counts, grade and freshness, untouched. expect(within(lane('Code')).getByText('1')).toBeTruthy(); expect(within(lane('Code')).getByText('EXACT')).toBeTruthy(); + expect(lane('Code').querySelector('[data-lane-freshness="unknown"]')).toBeTruthy(); expect(within(lane('Sessions')).getByText('2')).toBeTruthy(); // The summary counts only lanes that answered. @@ -439,6 +440,10 @@ describe('ExplorerPage scope truth', () => { within(lane('Code')).getAllByText(/Other project is not the active project/).length, ).toBeGreaterThan(0); expect(within(lane('Code')).getByText('Read-only scope')).toBeTruthy(); + // The run register carries the same refusal rather than a pending admission. + const register = document.querySelector('[data-run-register]'); + expect(register?.getAttribute('data-run-state')).toBe('locked'); + expect(within(register as HTMLElement).getByText(/no run created/)).toBeTruthy(); expect( fetchImpl.mock.calls.some(([input]) => String(input).includes('/api/explorer/queries')), ).toBe(false); diff --git a/dashboard/src/workspaces/explorer/ExplorerPage.tsx b/dashboard/src/workspaces/explorer/ExplorerPage.tsx index fe3d94a401..1f3e9a8667 100644 --- a/dashboard/src/workspaces/explorer/ExplorerPage.tsx +++ b/dashboard/src/workspaces/explorer/ExplorerPage.tsx @@ -131,6 +131,7 @@ export function ExplorerPage() { explorer.setFacet(value === '' ? null : { lane: laneId, value }) @@ -245,25 +247,34 @@ function asLaneId(value: string): LaneId | null { } /** A native select behind an engraved legend, so every filter is one control - * with one accessible name and the platform's own keyboard behaviour. */ + * with one accessible name and the platform's own keyboard behaviour. + * + * `name` widens the accessible name past the visible legend when the legend + * alone would be ambiguous (three lanes each have a facet); the visible text + * stays inside the name, so label-in-name holds. The select is capped rather + * than sized to its widest option, which at 320px pushed the whole register + * past the aperture's edge. */ function FilterSelect({ label, + name, value, onChange, options, }: { label: string; + name?: string; value: string; onChange: (value: string) => void; options: readonly { value: string; label: ReactNode }[]; }) { return ( -