Skip to content

Redesign code block controls as a header bar - #218

Merged
MaggieAppleton merged 10 commits into
mainfrom
design/code-block
Sep 30, 2026
Merged

MaggieAppleton merged 10 commits into
mainfrom
design/code-block

Conversation

@MaggieAppleton

@MaggieAppleton MaggieAppleton commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Turns the loose row of code block controls into one bordered unit with a slim header, and replaces the native language <select> with the app's own menu.

Change

  • One box. A code block is a single bordered, rounded unit. Its header (inset background, 4px padding) sits on top, and the rendered code, source panel, diagram and errors sit below it.
  • Language menu. A new LanguageMenu has a ghost btn-sm trigger and a portalled listbox with the app's dropdown motion. It supports keyboard use (arrows, Home/End, Enter/Space, Escape, Tab, type-to-jump) and dismisses on an outside click without moving the editor caret. Read-only viewers see a plain label instead of a disabled grey field.
  • Source toggle. "Show source" / "Hide source" is now a standard btn-icon btn-ghost with a new CodeIcon and a tooltip, and it tints petrol while the source is open.
  • One text edge. The language label, rendered code, source, invalid diffs and diagram errors all start 0.75rem in. Rendered code has 0.75rem above and below, matching a plain fence. Real diffs keep their line-number column.
  • File title. A titled file's name now appears in the header after the language (the renderer's own file header is off). Diffs keep the renderer's header.
  • Focus. The header uses the theme's data-focus-boundary, so focus rings draw inside the controls and the rounded box never crops them. The listbox panel has no ring of its own, because its highlighted option shows the keyboard position. That rule lives in theme.css, where the focus guard allows it.

Verification

  • bun test, bun run types and bun run ci pass.
  • e2e/code.e2e.ts and e2e/responsive-content.e2e.ts pass 15 of 15 on Chromium, including a new keyboard listbox test: ArrowDown opens the menu, Escape returns focus to the trigger, an outside click dismisses it, Tab moves to the source toggle, and Enter selects.
  • Alignment was measured in the design audit: every text line starts 12px from the box's inner edge.
  • A code review found two bugs, both fixed:
    • Tab from the open menu left the page.
    • The highlighted option could go stale after a collaborator's language change.
  • It also flagged a duplicate diff title and wasted repositioning while the menu scrolled. Both are fixed too.

Screenshots

Captured from the dev-only design audit page at 2x, on main and on this branch, with the code specimen made editable so the controls are live.

Code blocks

Before (main) After (this PR)
Code blocks before Code blocks after

Language menu open, and source shown

Menu open Source shown
Language menu open Source shown

Diffs and diagrams

Before (main) After (this PR)
Diffs before Diffs after
Diagrams before Diagrams after

Taking these showed that consecutive fences now touched once the loose control rows became a header inside the box, so eb3afb62 gives code blocks the same margin-block: 1em as tables.

Review fixes

  • Focus the language listbox after it is positioned, before the next animation frame, so rapid keyboard selection works.
  • Preserve an authored filename when invalid diff content falls back to plain code.
  • Both new Chromium regression tests failed before the fixes and pass afterwards. All 17 code-block browser tests, 1,723 tests (2 PostgreSQL suites skipped), types, CI checks, and the build pass locally.

The real editor below shows a normal code title and the restored filename on an invalid diff.

Normal code title and restored invalid-diff filename

🤖 Generated with Claude Code

CI navigation regression

Fixes a race inherited from #153: canonicalizing the current document URL could cancel navigation to a newly created document. The guard now tracks navigation separately from canonical URL changes. The onboarding test also uses a fresh identity for each retry.

Validated with 51 creation, onboarding and navigation browser tests, plus five repeated runs of each race-sensitive case; types, CI checks and build passed.

Document creation continues through URL canonicalization and respects later navigation

MaggieAppleton and others added 8 commits September 30, 2026 12:14
Language menu becomes a ghost button with a listbox panel, the source
toggle an icon button, and the block one bordered box with a shared
0.75rem left edge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps focus geometry in theme.css instead of exempting the editor
stylesheet from the focus guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Consecutive fences touched once the loose control rows became a header
inside the box.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Renew reviewed dynamic exception hashes for the new CodeIcon and the
edited CodeView boundary, add a reviewed exception for the new
LanguageMenu portal's measured placement and presence className, and
extend the legacy mono-stack exception to the new file-view selector.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The code specimen's baseline changes to match the intentional header-bar
redesign (taller box for the new chrome). The table specimen's baseline
also shifts by a hair: it renders below the code specimen on the same
continuous audit page, and the code specimen's ~82px height growth
nudges its rasterization by a sub-pixel amount even after the fixture's
own fractional-scroll correction. Verified repeatable by rerunning
design:browser without --update-snapshots.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same reviewed findings, differently-shaped axe selectors: code.json's
selector attribute changed from [role="group"] to [data-view="file"]
because the redesign adds that attribute to the same code-preview div.
Also drops one narrow-only entry (the "12" digit contrast finding,
still present at wide) that no longer surfaces as a separate node,
most likely merged into an adjacent span by the new header's
line-wrapping at that width. chat.json's three entries are the same
approved chat-timestamp/queued-status finding with two Tailwind
classes reordered in axe's generated selector string; chat.tsx itself
is untouched by this PR.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@MaggieAppleton
MaggieAppleton merged commit 8181170 into main Sep 30, 2026
3 checks passed
@MaggieAppleton
MaggieAppleton deleted the design/code-block branch September 30, 2026 12:02
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