feat(shell): move the chrome to a global bar above the sidebar - #533
Merged
Conversation
The complaint that started this: put TRUSCA next to its sister project and they read as the same class of tool. The clearest reason is the silhouette — both were a left rail running to the top of the window with a thin header beside it. Structure, not colour, is what the eye settles on first. So the bar now spans the full width and the sidebar starts beneath it. That shape is also one a single-scan tool cannot borrow: what the bar carries is the organisation, the team you are acting as, and search across everything — and a tool with no accounts has nothing to put there. - Global bar, 56 px, ink. It is a dark surface inside a light theme, so it gets its own `--topbar-*` foreground scale rather than borrowing the page's; without those tokens every call site would reach for a palette class, which the token lint blocks. Ink matches `--primary`, so the bar and the primary CTA read as one material. - Team switcher. `user.teams` was already in the store, so no backend change. Single-team users see a label; multi-team users can switch. Switching moves the active team the store already tracked (project creation follows it) and deliberately filters nothing — a control that quietly narrowed the dashboard would be a trap dressed as a shortcut. - Sidebar grows 224 -> 256 px and gains groups: Portfolio, Operations, Administration. A flat list did not say what kind of place each destination was. - The 48 px per-page header is gone; everything it held moved up. Live count badges on Scans and Approvals are deliberately not here. There is no existing query to read them from, and the cockpit work builds the aggregation this would need — inventing a second one now, to throw away in a few weeks, is not economy.
Two things looked wrong in the first capture of the new shell. The notification button kept its `outline` variant, which paints a light background — on the dark bar it rendered as a white block with a red badge floating on it, reading as an error state rather than a bell. The brand mark is a dark slate tile carrying a paper hexagon and a teal check, drawn for light surfaces. On the ink bar the tile all but disappears into the bar, leaving the hexagon looking like a fragment. It now drops the tile when it sits on ink; the hexagon and check stand on the bar directly, which is what the tile was providing for them anyway.
The accent had reached only the sidebar. Two more places on the list: - Active tab underline. The label stays ink, so the underline is a second signal rather than the only one carrying the state. - Progress indicator. A scan running is where the product is visibly working, so the bar is brand rather than neutral ink. Terminal states keep their semantic colours through `indicatorClassName`, so a failed scan still reads red and a finished one green. While in ScanProgress, pay down the two palette bypasses it carried: `bg-emerald-100` / `bg-emerald-600` map exactly onto the status surface tokens. Ratchet 248 -> 246.
Design system reference gains the two new token families in EN and KO — the brand accent with its whitelist and the reason there are two teals, and the global bar's own foreground scale. CLAUDE.md's summary table follows. Visual baselines and the accessibility baseline are re-captured for the new shell. Accessibility holds at 19 frozen violations: the bar and the regrouped sidebar add none.
An adversarial review of this branch refuted four of six angles. All four were real; this is the repair. **The team switch did not reach the form it was documented to drive.** `ProjectCreatePage` read `user.teamId` into a `useState` initialiser and never re-synced. Switch teams in the bar while /projects/new is open and the bar said "Security" while submit POSTed Platform — two visible controls for one concept, disagreeing. The choice also lived in the auth store, which is not persisted (it holds the access token, and it must stay that way), so the switch vanished on reload. Active team now lives in `uiStore` beside the other shell preference, persisted, and one hook resolves it. `useActiveTeam` discards a stored id that no longer names a membership, so a revoked team cannot keep scoping anything after the session that granted it. **The switcher was silent to screen readers.** Plain `DropdownMenuItem`s with an `aria-hidden` check mark: every team announced identically, with nothing saying which one is current. axe cannot see this — a named menuitem in a menu is structurally valid — which is the class of defect the "no new violations" number cannot speak to. Adds a `DropdownMenuRadioItem` primitive (`menuitemradio` + `aria-checked`). **The bar could not fit a phone.** No wrap, no overflow, and ~450 px of non-shrinkable controls against ~330 px of width. Overflow spilled right, pushing the sign-out button — the app's only one — off screen. Nothing tested below 800 px, so it went unseen. The search trigger and locale button now hide below `sm`; the keyboard shortcut still opens search. **Claims that outran the code.** The accent whitelist named empty states and the login gateway, which have no brand token; `--brand-strong` and `--brand-foreground` shipped with no consumers. The whitelist now describes what exists, and the two unused tokens are gone — they can come back when something needs them. Also corrects the contrast test, which measured `--brand` against white. The indicator is painted on `--brand-subtle` and the progress bar on `--muted`, so a retune of either could have dropped the real ratio under 3:1 with the gate still green. And it replaces an assertion that pinned `--brand-on-ink` as failing on white — a test that breaks when someone improves a value is a trap — with one that asserts each teal is better on the surface it is for. Docs: 224 px sidebar and the "top header" description were wrong in six places including the file that sets 256 px; `--layout-topbar` was undocumented; the keyboard inventory listed an avatar dropdown that does not exist. The user guide claimed the dashboard and project list are scoped to the active team, which the switcher explicitly does not do, and had no entry for the new control at all. Tests: the sequence open-form → switch → submit, the revoked-membership case, and persistence across reload — none of which anything asserted.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
W14 in progress — capturing the new baselines for visual review before this is ready.