Skip to content

Commit f09a5d8

Browse files
authored
fix(dashboard): stop the navbar overflowing the page at narrow widths (#1107)
Below a narrow viewport the top nav pushed the document wider than the screen, so the whole page scrolled sideways and slid the app off-screen. The nav's two fixed clusters were both shrink-0: brand mark + "The Framework" wordmark on one side, "New session" + the icon buttons on the other. Together with the project picker they could not fit a phone-width viewport, and nothing was allowed to give. Below sm the nav folds to what fits: the mark stays (still the link home) but the wordmark drops, "New session" collapses to its + icon (still aria-labelled), and the picker caps narrower and truncates. At sm and up it is unchanged. Verified in a real browser at 375/420px (no page scroll) and 1200px (labels back); jsdom has no layout engine so it cannot see this. Closes #980
1 parent 5abc227 commit f09a5d8

5 files changed

Lines changed: 34 additions & 5 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@gemstack/the-framework": patch
3+
---
4+
5+
Fix the navbar overflowing the page at narrow widths.
6+
7+
Below a narrow viewport the top nav pushed the whole document wider than the screen, so the page scrolled sideways and slid the app off-screen. The cause was the nav's fixed clusters: the brand mark plus wordmark on one side and the "New session" button plus the icon buttons on the other were both `shrink-0`, so together with the project picker they could not fit a phone-width viewport.
8+
9+
Below `sm` the nav now folds down to what fits: the brand keeps its mark (still the link home) but drops the "The Framework" wordmark, the "New session" button collapses to its `+` icon (still labelled for a screen reader), and the project picker caps narrower and truncates a long name. At `sm` and up everything is exactly as before. Verified by driving a real browser at 375px and 420px (no page scroll) and at 1200px (labels back); jsdom cannot see this class of layout bug.

packages/framework-dashboard/components/BrandLink.test.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ describe('BrandLink', () => {
3939
expect(onNavigate).not.toHaveBeenCalled()
4040
})
4141

42+
test('folds the wordmark away below sm so the nav fits a narrow viewport (#980)', () => {
43+
// jsdom has no layout engine and does not apply the utility CSS, so the actual hide can only
44+
// be proved by driving a real browser (done for the PR). This just pins that the wordmark keeps
45+
// its responsive classes, so a future tidy-up cannot silently bring the overflow back. The mark
46+
// stays visible either way, and it is still the link home (#909).
47+
const { container } = render(<BrandLink working={false} onNavigate={vi.fn()} />)
48+
const wordmark = screen.getByText('The Framework')
49+
expect(wordmark.className).toContain('hidden')
50+
expect(wordmark.className).toContain('sm:inline')
51+
expect(container.querySelector('svg')).not.toBeNull() // the mark is not hidden
52+
})
53+
4254
test('carries the working state through to the mark', () => {
4355
const { container, rerender } = render(<BrandLink working onNavigate={vi.fn()} />)
4456
expect(container.querySelector('path')?.getAttribute('fill')).toBe('url(#hexknot-0)')

packages/framework-dashboard/components/BrandLink.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ export function BrandLink({ working, onNavigate }: { working: boolean; onNavigat
1818
className="flex shrink-0 items-center gap-3 rounded-md transition-opacity hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-primary)]"
1919
>
2020
<Logo className="h-5 w-auto shrink-0" working={working} />
21-
<span className="shrink-0 font-semibold">The Framework</span>
21+
{/* Below sm the wordmark folds away so the nav fits a narrow viewport (#980); the mark stays,
22+
and it is still the link home (#909). */}
23+
<span className="hidden shrink-0 font-semibold sm:inline">The Framework</span>
2224
</a>
2325
)
2426
}

packages/framework-dashboard/components/ProjectPicker.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ export function ProjectPicker({
6565
// which does not say it is a picker.
6666
aria-label={`Project: ${label}`}
6767
title="Select a project"
68-
className={cn(buttonVariants({ variant: 'outline', size: 'sm' }), 'max-w-56 gap-1.5 font-normal')}
68+
// Capped narrower below sm so a long project name cannot push the nav past a narrow
69+
// viewport (#980); the label truncates. Full width returns at sm.
70+
className={cn(buttonVariants({ variant: 'outline', size: 'sm' }), 'max-w-40 gap-1.5 font-normal sm:max-w-56')}
6971
>
7072
<span className="truncate">{label}</span>
7173
{interventionCount > 0 && selectedId !== null && (

packages/framework-dashboard/pages/index/+Page.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { useDocumentTitle } from '../../lib/document-title.js'
2929
import { useWorking } from '../../lib/use-working.js'
3030
import { useFavicon } from '../../lib/favicon.js'
3131
import { useDaemonHealth } from '../../lib/use-daemon-health.js'
32-
import { TriangleAlert, Settings } from 'lucide-react'
32+
import { TriangleAlert, Settings, Plus } from 'lucide-react'
3333

3434
/** Stable, so `files` keeps one identity while no project is selected. */
3535
const EMPTY_FILES: string[] = []
@@ -306,14 +306,18 @@ export default function Page() {
306306
<div className="flex shrink-0 items-center gap-1">
307307
{/* Which daemon this dashboard is talking to (#1052) — obvious once you can hop devices. */}
308308
<ConnectionIndicator />
309+
{/* Below sm the label folds to the icon alone so the nav fits a narrow viewport (#980);
310+
the aria-label keeps it named for a screen reader either way. */}
309311
<Button
310312
variant="outline"
311313
size="sm"
312314
onClick={newSession}
313315
disabled={!projectId}
314-
title={projectId ? undefined : 'Select a project first'}
316+
title={projectId ? 'New session' : 'Select a project first'}
317+
aria-label="New session"
315318
>
316-
New session
319+
<Plus className="h-4 w-4 shrink-0 sm:hidden" aria-hidden />
320+
<span className="hidden sm:inline">New session</span>
317321
</Button>
318322
<ThemeToggle />
319323
<NotificationsMenu />

0 commit comments

Comments
 (0)