Skip to content

feat(shell): move the chrome to a global bar above the sidebar - #533

Merged
haksungjang merged 6 commits into
mainfrom
feat/w14-shell-globalbar
Jul 28, 2026
Merged

feat(shell): move the chrome to a global bar above the sidebar#533
haksungjang merged 6 commits into
mainfrom
feat/w14-shell-globalbar

Conversation

@haksungjang

Copy link
Copy Markdown
Contributor

W14 in progress — capturing the new baselines for visual review before this is ready.

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.
@haksungjang
haksungjang merged commit 49b3244 into main Jul 28, 2026
26 checks passed
@haksungjang
haksungjang deleted the feat/w14-shell-globalbar branch July 28, 2026 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant