Skip to content

feat(header): branch label, truncation priority, and icon-only actions below lg - #5230

Merged
guitavano merged 5 commits into
mainfrom
feat/header-branch-ui
Jul 25, 2026
Merged

feat(header): branch label, truncation priority, and icon-only actions below lg#5230
guitavano merged 5 commits into
mainfrom
feat/header-branch-ui

Conversation

@tlgimenes

@tlgimenes tlgimenes commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

UI-only header/branch fixes (split out of #5215).

  • Branch button always shows the current branch (truncated), and now yields its width before the centered address bar — it sits in its own min-w-0 shrink slot, while the fixed publish/⋯ actions never clip and remain what rightRef measures (so the tab-count budget is unaffected).
  • Below lg (< 1024px) the header collapses to icon-only + tooltip: the branch button, the primary action button (icon keyed off button.action — GitPullRequest / RefreshCw01 / AlertTriangle / CheckCircle / MessageCircle01), the side Publish (Upload01), and the Develop/Live toggle (Code01 / Globe01). Loading pills → spinner-only; plain status pills keep their text. Desktop unchanged.

Layout/presentational only — no logic. tsc + oxlint clean.


The problem, for anyone not on a big monitor

The panel header collapsed its controls on viewport media queries (lg, 1024px). But the header doesn't span the viewport — it lives inside one panel. So screen width tells you almost nothing about the room it actually has:

Situation Screen Main panel header
Chat closed 1074px 826px
Chat open 1180px 605px

That second row is the bug. At a 1180px screen the viewport rule says "plenty of room, show every label" — while the strip is actually 605px and out of space. The labels stayed full-width, the tab budget got squeezed, and view tabs fell into the ⋯ overflow menu earlier than they needed to.

It also meant the same screen behaved differently depending on where you dragged the splitter, which is impossible to reason about — and it's why this looked fine on a wide monitor and bad on a laptop.

Reported as: "the screen is 882px wide and I'm still seeing the chat and preview labels."

What changed

PanelHeader now declares @container/panel-header and every control inside queries its own panel's width:

Control Collapses below Was
Chat, system tabs (Preview/Code), Library, Tasks, CMS 768px of header viewport 768px / none
Dynamic tabs (files, pinned views, expanded tools) 576px of header viewport 768px
Branch pill, branch picker, Develop/Live, Publish 768px of header viewport 1024px
Preview's URL group (CMS · refresh · page select · open-in-new-tab) 448px of header JS measurement, ~668px

Two tiers rather than one, because the icon has to carry the button once the label goes. Chat/Preview/Code/Library/Tasks have fixed, distinctive icons and collapse first. Dynamic tabs can resolve to a generic fallback glyph (resolve-tab-icon.ts), so several open files would become mutually indistinguishable — they hold their text longest.

Small screens keep the Preview controls much longer: the old JS rule hid the whole URL group below ~668px of header; it now survives to 448px.

Tabs finally have tooltips

HeaderTabButton had no Tooltip at all, while its own docstring claimed "every button is wrapped in a Tooltip." Below md the tabs were already collapsing to bare icons — so on a small screen you got an unlabelled icon with nothing identifying it but aria-label. That's the part that actually hurt. Tooltips are now real, which is what makes collapsing labels safe in the first place.

preview.tsx wrapped one of these in its own Tooltip; that's replaced by a tooltip prop so the two don't nest.

Screenshots

1440px, chat closed — header 1192px, everything labelled.

1-wide-1440

1000px, chat closed — header ~752px, labels collapsed to icon + tooltip, page selector still there.

2-mid-1000

1180px, chat open — header 605px. The point of the PR: a large screen where the panel is narrow, so the strip collapses. Under the old rule this showed full labels and ran out of room.

3-chat-open-1180

Trade-off worth a reviewer's opinion

The URL group's 448px cutoff is deliberately later than the old ~668px. Between roughly 448 and 530px the page selector has less than HEADER_W.middle (232px) and degrades toward a bare chevron instead of showing the page name — measured 220px at a 530px header, 150px at 460px.

That squish is the accepted cost of keeping the controls reachable on narrow panels; the old code hid them instead. Documented at the call site. Easy to move to 576px (@xl) if it reads badly in practice.

Dead code removed

headerLayout no longer returns showPageSelector — CSS owns that now, so keeping it would be dead. maxTabs stays in JS, because it changes which items render in the bar vs the overflow popover and CSS can't express that. Its tests were inverted rather than appended to, plus one asserting the field doesn't come back. HEADER_W.middle's docstring no longer claims to gate the centre's visibility.

Testing

  • bun run check clean across all workspaces; bun run lint 0 errors; bun run fmt applied.
  • 1029 unit tests pass (apps/web layouts, sandbox, chat).
  • Verified in the running app by driving the container's inline size directly and sweeping the thresholds:
Forced header Chat label Centre group
820, 780 shown visible (397, 379px)
767 collapsed visible (361px)
700 collapsed visible (361px)
530 collapsed visible (220px — into the squish band)
460 collapsed visible (150px)
447 collapsed hidden
  • Confirmed all four right-cluster components (BranchPill, BranchPicker, DevAgentControl, HeaderActions) render only inside a PanelHeader, so the container always resolves. Outside one the query finds no container and the label simply stays — the safe direction.
  • Mobile (MobileTaskWorkspace) doesn't render the branch selector or publish actions at all; Preview's inline fallback bar declares the same container so its queries still work on that path.

Also in here: the CMS toggle gets its own icon

Edit05 was doing double duty as the new-chat affordance (task-groups-list.tsx, shell-breadcrumb.tsx) and the header's CMS toggle — two unrelated actions, one pencil-square. That collision matters more once the label collapses and the icon is the only thing identifying the button. Now PuzzlePiece01 (the page-builder "blocks" metaphor, unused elsewhere), applied to both entry points: the toolbar button and the ⋯ menu fallback.

🤖 Generated with Claude Code


Summary by cubic

Collapses the header by panel container width using @container/panel-header, keeps the current branch visible (truncated), and switches key actions/toggles to icon-only with tooltips at small widths. Also centers the page selector in the mobile preview toolbar and lowers the Preview URL group cutoff to 384px so controls stay usable longer.

  • UI Improvements

    • Branch sits in its own min-w-0 slot before the centered address bar; right-side actions stay shrink-0 and are the only area measured by rightRef, so tab count is unaffected.
    • Container queries drive collapse: branch picker/pill, primary action, side Publish, and Develop/Live go icon-only below 768px; tab/chat labels collapse with tooltips (system tabs at 768px, dynamic tabs at 576px); Preview URL group hides below 384px. JS now only decides maxTabs (1–3); page selector visibility is CSS-driven.
    • Icons and tooltips: primary action uses state icons (GitPullRequest, RefreshCw01, AlertTriangle, CheckCircle, MessageCircle01); side Publish uses Upload01; Develop/Live use Code01/Globe01; CMS toggle switches to PuzzlePiece01. Tabs now have tooltips; loading pills are spinner-only, while plain status pills keep text.
  • Bug Fixes

    • Mobile preview toolbar declares @container/panel-header and centers the page selector between the CMS toggle (left) and ⋯ (right).
    • Branch picker opens focused on the current branch and marks it with a check.

Written for commit 6b8827c. Summary will update on new commits.

Review in cubic

Deco Studio and others added 5 commits July 25, 2026 09:18
…s below lg

- Branch button always shows the current branch (truncated), and yields its
  width BEFORE the centered address bar (its own `min-w-0` shrink slot; the
  fixed publish/⋯ actions never clip and remain what `rightRef` measures).
- Below `lg` (< 1024px) the header collapses to icon-only + tooltip: the branch
  button, the primary action button (icon keyed off `button.action`), the side
  Publish button (Upload01), and the Develop/Live toggle (Code01 / Globe01).
  Loading pills → spinner-only; plain status pills keep their text. Desktop
  unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…iewport (#5241)

* refactor(header): degrade the panel header by container width, not viewport

The header strip lives inside one panel, so viewport width says nothing about
the room it actually has: with chat open, a 1180px screen leaves the main
header at 605px. Every control in it collapsed on viewport media queries, so
on a laptop the labels stayed at full width while the strip ran out of space,
and the same screen behaved differently depending on the splitter.

PanelHeader now declares `@container/panel-header` and every control queries
that instead:

- Tab / Chat labels collapse to icon + tooltip below 768px of header. Buttons
  with a fixed, distinctive icon (Chat, system tabs, Library, Tasks, CMS) go
  at 768px; dynamic tabs, whose icon can resolve to the generic fallback and
  would be mutually indistinguishable, hold their text to 576px.
- The branch pill, branch picker, Develop/Live toggle and the publish actions
  move off `max-lg:`/`lg:hidden` onto the same 768px cut, so the whole strip
  collapses at one width instead of in two unrelated stages.
- Preview's URL group (CMS / refresh / page select / open-in-new-tab) hides
  below 448px of header. Deliberately later than the old JS rule (~668px):
  between ~448 and ~530px the page selector shows with less than
  HEADER_W.middle and degrades toward a bare chevron, judged better than
  losing the controls outright.

HeaderTabButton also gains the Tooltip its docstring already claimed it had —
without it an icon-only tab had nothing identifying it at all. preview.tsx's
external Tooltip is replaced by the new `tooltip` prop so the two don't nest.

headerLayout keeps only maxTabs; showPageSelector is gone, since CSS owns that
now and leaving it would be dead code. Its tests are inverted rather than
appended to, and HEADER_W.middle's docstring no longer claims to gate the
centre's visibility.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(preview): give the CMS toggle its own icon

Edit05 is the new-chat affordance in the sidebar (task-groups-list) and the
shell breadcrumb, so the header's CMS toggle rendered the identical
pencil-square — two unrelated actions with one glyph, and the collision got
worse once the label collapses and the icon is all that identifies the button.

PuzzlePiece01 is the page-builder "blocks" metaphor, which is what the toggle
opens, and it is unused elsewhere. Avoided LayoutAlt01 (already the page icon
in this same toolbar's page-selector), Grid01 (means card/grid view in
collections) and Rows01 (a rows-view toggle in the sidebar).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The group's min-content is 112px — 36 (CMS) + 28 (refresh) + 24 (page chevron)
+ 28 (open-in-new-tab). Measured against the live header, a 384px container
still leaves it 112-128px once the left group (66px; the tab budget is one tab
at that width) and the actions (178px) take their share, so the icons render
without clipping. One breakpoint lower and they clip.

The page name is already gone by ~448px — the selector collapses to a bare
chevron there — so this buys the 384-448px band four usable icons rather than
nothing. Losing the controls outright is worse than losing the label.

Because container queries measure the content box, `px-1.5` makes this fire
around 396px of rendered width, leaving a ~16px margin above the floor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
On the inline (mobile) path every control lived in one left-packed group, so
the page selector sat wherever Edit and refresh happened to push it — off
centre on a phone, where that bar is the full width of the screen.

Split the group into `cmsToggle` and `urlGroup` so each layout can place them
independently. Desktop composition is unchanged: Edit leads, divider, then the
view controls, portaled into the header's centre slot. Mobile now runs three
zones — Edit left, the view controls centred, ⋯ right.

Both side zones are `flex-1` so the selector centres on the bar rather than
between two unequal neighbours (Edit is ~9px wider than ⋯, which was the
original offset). On the narrowest phones the left zone cannot shrink below
the Edit button, so ~5px returns; closing that fully would mean pinning the
sides to a fixed width and taking the space from the selector instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The picker relied on cmdk's default first-item highlight, which made an
unrelated branch look "selected" while the branch actually in use had no
indication. Seed the active item with the current branch so it opens
focused on it, and render a check on the current branch in each group.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@guitavano
guitavano force-pushed the feat/header-branch-ui branch from 10d88d4 to 6b8827c Compare July 25, 2026 13:00
@guitavano
guitavano merged commit 2714b12 into main Jul 25, 2026
14 checks passed
@guitavano
guitavano deleted the feat/header-branch-ui branch July 25, 2026 13:07
decocms Bot pushed a commit that referenced this pull request Jul 25, 2026
PR: #5230 feat(header): branch label, truncation priority, and icon-only actions below lg
Bump type: minor

- decocms (apps/api/package.json): 4.126.4 -> 4.127.0

Deploy-Scope: web
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.

2 participants