Skip to content

Theme navbar improvements and theme card follow-ups - #5908

Open
burieberry wants to merge 11 commits into
mainfrom
cs-12672-theme-card-followups-navbar
Open

Theme navbar improvements and theme card follow-ups#5908
burieberry wants to merge 11 commits into
mainfrom
cs-12672-theme-card-followups-navbar

Conversation

@burieberry

@burieberry burieberry commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Addresses CS-12672.

  • Update base realm boxel and cardstack brand guides to match the new ones in catalog

Navbar (theme dashboard)

  • Fixed-height sticky bar; items that don't fit move into a "…" dropdown menu, and under a 400px container the strip becomes a hamburger menu listing every section
  • The dark-mode toggle is a boxel-ui Switch affixed to the right end of the navbar in all four theme cards
  • Semantic nav/ul markup; nav clicks use history.replaceState so embedded cards stop polluting host-app history
  • SimpleNavBar is removed (drops a base-realm export)

boxel-ui

  • BoxelDropdown no longer scroll-jumps on close (focus trap uses preventScroll, EBD's trigger refocus is skipped) — without this, menu actions can't smooth-scroll

Theme card follow-ups

  • The visualizer is nav-listed as "Preview" in the three cards that render it; theme-less edit views lead with the Import CSS and Generated CSS sections
  • Shared PreviewPills component, reused in the brand guide
  • Bugfixes: brand guide's empty custom-css section is hidden, Reset CSS also clears customCssVariables

Narrow-container layout

  • No horizontal overflow at 320px in any card: grid items shrink below content min-width, column floors use minmax(min(N, 100%), 1fr), pre blocks scroll instead of wrapping, brand guide palettes/rows/specimens wrap
  • Compact padding aligned on --boxel-sp; spacing uses --boxel-sp-* tokens instead of calc()

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Preview deployments

Host Test Results

    1 files      1 suites   2h 28m 22s ⏱️
4 599 tests 4 585 ✅ 14 💤 0 ❌
4 614 runs  4 600 ✅ 14 💤 0 ❌

Results for commit 3b5996e.

Realm Server Test Results

    1 files      1 suites   18m 14s ⏱️
2 348 tests 2 348 ✅ 0 💤 0 ❌
2 431 runs  2 431 ✅ 0 💤 0 ❌

Results for commit 3b5996e.

@burieberry
burieberry marked this pull request as ready for review August 27, 2026 18:23

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 644f5b7299

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/base/default-templates/theme-dashboard.gts Outdated
@burieberry
burieberry force-pushed the cs-12672-theme-card-followups-navbar branch 3 times, most recently from 209b013 to 45a557f Compare August 28, 2026 00:45
… toggle into it, and fix narrow-container layout
@burieberry
burieberry force-pushed the cs-12672-theme-card-followups-navbar branch from 45a557f to 3b5996e Compare August 28, 2026 00:57
@burieberry
burieberry requested a review from a team August 28, 2026 13:49
@burieberry
burieberry marked this pull request as draft August 28, 2026 15:30
@burieberry
burieberry marked this pull request as ready for review August 28, 2026 15:30

@backspace backspace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] This review went after the parts of the change that reach outside the theme cards — the shared BoxelDropdown edit, the base-realm exports, and the new NavBar's measurement/reactivity loop — plus the section-list plumbing that now has three places to stay in sync. It did not evaluate the visual design, and it did not audit the brand-guide JSON content edits beyond confirming that dropping the persisted cssImports is inert (cssImports is a computed field, so those values were already ignored).

One blocking issue: the BoxelDropdown focus change drops the trigger refocus for every dropdown in the app, not just menus that scroll. Everything else below is non-blocking.

The Codex bot's P1 about deferring the initial overflow measurement does not apply at this head: trackFit installs the ResizeObserver and nothing else, leaning on its first async delivery exactly as that comment asks. Safe to dismiss.

Recommendations, in order:

  1. Name the trigger as focus-trap's return-focus node instead of skipping the focus move entirely — thread on closeSkippingFocus in packages/boxel-ui/src/components/dropdown/index.gts. Blocking.
  2. Delete ThemeVisualizer's now-unreachable @toggleDarkMode — thread on its signature in theme-dashboard.gts.
  3. Pin nav-ids-equal-body-ids in a test so style-reference.gts's new eq chain can't fail open — thread on the {{#each this.visibleSections}}.
  4. Cover the menu → scroll path, untested in both the overflow and compact menus — thread on scrollFromMenu.
  5. Fix the --dsr-nav-height comment — thread on .dsr-nav's height.

One question outside the threads: packages/experiments-realm/Theme/ loses five theme instances (bubblegum, doom64, neo-brutalism, retro-arcade, starry-night). They still adopt from a live Theme in card-api, nothing in the repo references them, and neither the description nor the commit that removes them mentions the deletion. Intentional?

Red check: Percy reports one visual change awaiting review — expected for this change, but it needs a human approval before that check goes green. Nothing else is red.

Adjacent, out of scope: with SimpleNavBar gone, ThemeDashboard's <:navBar> block has no in-repo consumer left. Reasonable to keep as an extension point for card authors, but nothing exercises it now.

Comment on lines +170 to +175
// BasicDropdown's plain close() refocuses the trigger with a bare focus(),
// whose scroll-into-view cancels a smooth scroll a menu action just started;
// the focus trap already returns focus on deactivate, with preventScroll
@action closeSkippingFocus(dropdown: Dropdown) {
dropdown.actions.close(undefined, true);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] skipFocus drops the trigger refocus entirely, and focus-trap's return focus is not an equivalent substitute: returnFocusOnDeactivate targets state.nodeFocusedBeforeActivation — whatever document.activeElement was when the trap activated — and setReturnFocus is unset. That equals the trigger only when the user clicked the trigger to open it.

NewFileButton (packages/host/app/components/operator-mode/new-file-button.gts) is opened from operatorModeStateService.state.newFileDropdownOpen through @initiallyOpened, so the trap activates at mount with focus still on <body>; closing it now leaves focus on <body> rather than returning it to the New File trigger. Same for any registerAPI-driven open, and for browsers that don't focus a <button> on click.

The scroll comes from the bare focus(), not from focusing the trigger — so keep the target and drop the scroll by naming the trigger as the return-focus node, where the preventScroll you added already applies:

setReturnFocus=(fn this.triggerReturnFocus dd)
@action triggerReturnFocus(dropdown: Dropdown, previouslyFocused: HTMLElement) {
  return (
    document.querySelector(
      `[aria-controls='ember-basic-dropdown-content-${dropdown.uniqueId}']`,
    ) ?? previouslyFocused
  );
}

A plain selector string works for setReturnFocus too, but focus-trap's getNodeForOption throws when it resolves to nothing, and the overlay trigger in operator-mode-overlays.gts unmounts with its {{#if isHovered}} — hence the function form with the fallback.

Regression introduced here, in a component every dropdown in the app shares. Blocking.


export class ThemeVisualizer extends GlimmerComponent<{
Args: {
toggleDarkMode?: () => void;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] @toggleDarkMode no longer has a producer — structured-theme.gts, style-reference.gts and detailed-style-reference.gts each swapped it for id='preview' in this change, so this arg and the {{#if @toggleDarkMode}}<ModeToggle …> branch in the visualizer header are unreachable. Worth removing along with the move: left in place, the next card that renders a visualizer wires up a second toggle competing with the navbar's over the same isDarkMode.

Dead machinery this change creates, so the fix belongs here. Non-blocking.

<section id='wallpapers' class='wallpapers'>
<h2>Wallpaper Gallery</h2>
<@fields.wallpaperImages />
{{#each this.visibleSections as |navSection|}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] This chain fails open: an id in visibleSections with no matching eq branch renders nothing while the nav still lists it, and the resulting link is silent — scrollToSectionFrom returns without a word when querySelector misses the id. It is also the second hand-maintained enumeration of the same list (contentSections + GUIDE_SECTIONS is the first), so the two drift the first time a section is added to either.

Every id is covered today, so this is not a live bug. The cheap guard is in the test rather than the template: for a themed and a theme-less edit view, assert the [data-test-style-ref-section] ids equal the nav's [data-test-theme-nav-item] ids (dropping preview). The new theme-less case pins body order but never checks the two lists against each other, which is exactly the invariant this chain can break.

Non-blocking; follow-up-shaped but small enough to land here.

Comment on lines +776 to +782
// the dropdown content is portaled outside the card, so the menu action
// cannot walk up from its own element; scroll from the captured nav instead
private scrollFromMenu = (sectionId: string) => {
if (this.navElement) {
scrollToSectionFrom(this.navElement, sectionId);
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] Nothing exercises this. The overflow test opens the "more" menu and asserts the item is listed, then stops; the compact branch (data-test-theme-nav-menu, allMenuItems) never renders in any test. So the one non-obvious mechanism in the navbar — menu content is portaled out of the card, so the section lookup has to start from the captured navElement rather than from the clicked element — has no coverage, and it degrades silently: if registerNav ever stops running, scrollFromMenu no-ops with no error.

Ask: add a case below the compact threshold that opens the hamburger and asserts a menu item click resolves to its section, rather than only that the item is listed. Non-blocking.

Comment on lines +952 to +953
/* also read by content pinned below the bar, so it stays in sync */
height: var(--dsr-nav-height);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] Nothing else reads --dsr-nav-height — it has exactly two references, the declaration on .detailed-style-reference and this height. Drop the second clause or point it at the consumer you have in mind. Non-blocking.

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