feat(runner): DEV-2156 app chrome — top bar, column bars, authed action bar - #92
Conversation
…on bar Rebuild the shell chrome against Figma 72:15697 / 48:6560. Toolbar.tsx is gone; its job is split across TopBar (row 1), EditorBar and PreviewBar (the two 36px column bars), AuthedActionBar, and a shared MenuButton backing the version and framework dropdowns. Three things the frames settle that the task's scope line got wrong: * Row 2 is two bars, not one. 72:15811 lives inside the editor column frame and 72:15706 inside the preview column frame — there is no full-width secondary row, and no divider component either (the sidebar's own borderRight is the only rule at that height). * The preview column is a fixed half. It starts at x=864 of 1728 in both 72:15697 (sidebar collapsed) and 48:6560 (sidebar open at 240): the editor absorbs the sidebar. s.body is `240px minmax(0,1fr) 50%`. * Download is not gated on auth. Share mode has always offered it to anonymous visitors and no frame shows an anonymous share view, so ADR-0023 rule 1 wins: Download whenever a file set exists, Sign in when anonymous, both together on an anonymous share page. The refresh button needed a reload() on DemoRuntime. Container re-points the iframe at its existing previewUrl; Sandpack re-runs updateSandbox(setup, true). Neither creates a session — a remount would mint a fresh container per click against a five-slot pool. DocsCascader is touched further than "restyle the trigger" for two reasons the pill forces. Its trigger lost its border/background, because the pill is the one box the design draws and keeping both nested two. And the popover needed width: max-content plus a zIndex on the top bar: the pill is centred with a transform, which makes it a stacking context, so the preview pane — position: relative, later in the DOM — painted over the open popover. Also here: PreviewPane's full-width accent status strip is dropped (the design gives the preview one bar, and `● ready` belongs in T5's bottom bar) and its overlays re-anchor to inset 0; the tab strip renders inline in EditorBar with one open file, for T4 to extract; App.tsx captures mount()'s previewUrl for the row-2 address field, container engine only — Tier 1 reports Sandpack's bundler origin, which is meaningless here and a CodeSandbox mark ADR-0001 keeps out of the UI. A closed tab reopens when the workspace is replaced, driven by App's existing mountGen: two examples can share an entry path, so a still-valid active file is not proof the document is the same one. Two open items logged: the pill's 20x20 Handsontable mark has no asset in the repo (same gap blocks the favicon), and Tier 1 has no preview URL to show. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both findings hold. The GitHub control rendered in edit and share too, because repoUrl was always a non-empty string. Neither mode has a docsPath, so it pointed at the framework starter and called it "Fork this starter" — but a saved demo's source is the demo, not the template it came from, and 48:6560 (the one edit-mode frame) ends its bar at window-maximize. Play only, as the old top bar had it. docsUrl and frameworks were already play-only in practice: docsPath is seeded from the URL only when route.mode is "play". Sandpack reload() claimed the update sequence *after* awaiting its transpile, so a keystroke landing inside that window could have its newer result dropped while the refresh published the older one — preview stuck behind the editor until the next edit. It now shares pushUpdate(), which claims the sequence up front; `initial` carries the isInitializationCompile flag that made refresh a separate path in the first place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
T4 (#93) landed the editor tab strip and status bar against the pre-T2 shell, so both branches rewrote EditorShell around the same region. Resolved in T4's favour wherever the two overlap — it owns the tabs by the plan's own boundary. * The inline tab strip T2 rendered in EditorBar is gone; EditorBar is now the sidebar toggle plus `<EditorTabs>`. The bar takes the strip's recessed background and inset hairline (`s.editorBar`) rather than `s.bar`'s surface + bottom border, so toggle and tabs read as the one row the frame draws with no seam between them. `s.bar` stays as it was for PreviewBar. * T2's close-the-tab behaviour is dropped, with the `closed` state and the `workspaceGen` prop that existed to reset it. T4 documents the ✕ as decorative until multi-tab lands, which is the more coherent call for a single-file shell. * `s.editorPane` is replaced by T4's `s.editorBody` — the editor column is `s.column` now, and that is just the CodeMirror slot between the tabs and the status bar. It takes over the editor background `editorPane` carried. * EditorShell keeps T2's three-column body and T4's cursor state, so the status bar sits at the foot of the editor column only. Open items renumbered: T2's two become 13 and 14, after T4's 6-12. Verified after the merge in both themes: top bar, both column bars, the tab strip, the authed action bar and the status bar all render together; typecheck clean across all four projects. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Merged Tabs. T2 rendered a tab strip inline in Tab close. T2 made the ✕ functional — it cleared the active file — with a Editor slot.
Re-verified after the merge at 1728×900 in both themes: top bar, both column bars, the tab strip, the authed action bar and the status bar all render together, and |
Bugbot, correctly. In edit mode `publicUrl` was `/edit/:id`, and the field is click-to-copy — but `/edit` is auth-gated (`Gate` sends a signed-out visitor to the login broker, which only accepts @handsontable.com), so anyone the link was copied to hit a wall. `/share/:id` is the same demo served without auth, and is what `ShareLinks` already hands out. Now `/share/:id` in both edit and share. The field reads a shareable address rather than mirroring the browser's, which is what "the demo's public URL" meant in the first place — noted in the plan's open item 14. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ascader Bugbot again, and it caught the consequence of my own comment. Dropping the ▾ chevron (`72:15863` has it hidden) left the search icon as the trigger's only affordance — but the icon was a sibling of `DocsCascader` inside the pill, so it looked like the opener without being one: only the label area toggled the menu. The icon moves inside the trigger button. `s.trigger` becomes `width: 100%` rather than `flex: 1`, because the wrapper it sits in is a positioned *block* (it is the popover's containing block), so a flex value there did nothing and the icon tucked itself against the label instead of sitting at the pill's right edge. Verified: clicking the icon opens the cascader, and it renders at x≈1086 of the 1104-wide pill, matching `72:15865`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI caught what I should have: six docs-example specs drive the version and framework pickers, and T2 turned both from native controls into custom listboxes in the preview bar. `selectOption` fails outright on a <button>, and the framework picker no longer has `aria-pressed` because it is no longer a button group. A `pickFromMenu` helper opens the trigger by its `aria-label` and clicks the `role="option"`; the framework assertions move from "all five are buttons, React is pressed" to "React labels the trigger, all five are options, React is selected". The docs link is matched by its label rather than its old visible text, since it is an icon button now. No product change — the specs were asserting the old chrome. Full suite locally: 10 passed, 85 skipped, 0 failed (the same subset CI runs; the rest are gated behind E2E_LIVE / the starter matrix). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bugbot, correctly again. `versionWarning` moved from the old top bar — which grew with its content, so wrapping was free — into row 2, which is a fixed 36px. Both strings run ~90 characters, well past the 260px cap, so the span wrapped to three lines and pushed itself out of the bar and over whatever sat below. One line with an ellipsis now, full text still in `title`. Measured against the longer of the two strings: 14px tall inside the 36px bar. Logged as open item 15 rather than called done. A truncated warning is one you can miss and `title` is not reachable by touch, but no frame in `18.1` shows a warning anywhere, so there is no designed slot to move it to. T5's preview status bar is the obvious candidate — the frames do draw it, and it has the width. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ff636ae. Configure here.
… chrome
Bugbot caught what neither CI nor I would have: the starter matrix waits on the
preview's status strip reading "Live", and T2 deleted that strip. The suite is
opt-in (E2E_STARTER_MATRIX=1), so PR CI never ran it — the next matrix run would
have timed out on every entry with a preview that was actually fine.
`PreviewPane` now sets `data-preview-status` on its section and the matrix polls
that instead. The point is that readiness stops being a property of the chrome:
T5 moves the visible readout to a bottom bar, and this survives it.
Worth noting the selector already had one scar — scoping to the strip was itself
a fix for `getByText("Live")` matching a container's boot-log <pre> when the
error text began with "Error:" (mui, strict-mode violation). An attribute has
neither failure mode.
Verified by actually running a matrix case rather than reasoning about it:
`matrix: javascript @ 18 [sandpack]` passes. Default suite still 10 passed,
85 skipped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

T2 of the DEV-2027 redesign — DEV-2156. Depends on T0 (DEV-2154, theming) and T1 (DEV-2155, icons), both already on this branch. Targets
feat/DEV-2027-redesign, notmaster.Rebuilds the shell chrome against Figma
72:15697(logged out) and48:6560(light, authed).Toolbar.tsxis deleted; its job is split acrossTopBar(row 1),EditorBarandPreviewBar(the two 36px column bars),AuthedActionBar, and a sharedMenuButtonbacking the version and framework dropdowns.Three things the frames settle that the task's scope line got wrong
Row 2 is two bars, not one.
72:15811lives inside the editor column frame and72:15706inside the preview column frame — there is no full-width secondary row. The "divider" in the scope line is the sidebar/editor boundary, whichs.sidebar's existingborderRightalready draws, so there is no divider component either.The preview column is a fixed half. It starts at
x=864of 1728 in both72:15697(sidebar collapsed) and48:6560(sidebar open at 240) — the editor absorbs the sidebar, the preview does not move.s.bodyis240px minmax(0,1fr) 50%;1fr 1frwould have put the boundary at 984 with the sidebar open. T6 replaces this with the draggable ratio.Downloadis not gated on auth. The scope line says Download (authed) / Sign in (anon), but share mode has always offered Download to anonymous visitors and no frame anywhere shows an anonymous share view. ADR-0023 rule 1 wins: Download renders whenever a file set exists, Sign in when anonymous, and an anonymous share page shows both. This is the one deliberate deviation from the task text.Refresh needed a runtime change
packages/runtime/src/{types,container,sandpack}.tsare outside the task's file list. The row-2 refresh button needs somewhere to point, and the only cheap correct answer lives in the runtime:reload()onDemoRuntime. The container re-points the iframe at its existingpreviewUrl(container.ts:242already does that assignment); Sandpack re-runsupdateSandbox(setup, true). Neither creates a session — amountGenremount would mint a fresh container per click against a five-slot pool, which is the exhaustion bug we already fixed once in #87.DocsCascader is touched further than "restyle the trigger"
Two things the pill forces. Its
triggerlost its own border/background, because the pill is the one box the design draws (72:15859) and keeping both nested a second box inside the first. Ands.popgainedwidth: max-contentwhiles.topBargained azIndex: the pill is centred with atransform, which makes it a stacking context, so without that the preview pane —position: relative, later in the DOM — painted straight over the open popover. T7 owns the popover's own restyle and should know both.Also here
PreviewPane's full-width accent status strip is dropped and its boot/error overlays re-anchor toinset: 0— the design gives the preview one bar, and● readybelongs in T5's bottom 28px bar. The tab strip renders inline inEditorBarwith one open file, for T4 to extract intoEditorTabs.tsx.App.tsxcapturesmount()'spreviewUrlfor the row-2 address field, container engine only: Tier 1 reports Sandpack's bundler origin, which is meaningless to a user and a CodeSandbox mark ADR-0001 keeps out of the UI. A closed tab reopens when the workspace is replaced, driven by App's existingmountGen— two examples can share an entry path, so a still-validactiveis not proof the document is the same one.Sidebar CRUD gating stays T3's job; the handlers pass through unchanged.
Verification
pnpm typecheckclean across all four projects (the acceptance criterion). Nolintscript exists in this workspace.Walked in a browser at 1728×900, in both themes: anonymous play (starter), anonymous docs example, anonymous share, authed play, authed edit. All nine signed-in affordances exercised end to end — fork →
/edit/:id, Save (PATCH200), the Share dialog, My demos, Log out, title/description, the custom-version input, Embed, plus the framework dropdown actually switching examples.Tier-2 refresh checked against the network log: one click produced a second
GETto the preview URL and zero additionalPOST /api/session. Tier-1 refresh re-renders with no console errors.Open items logged, not resolved here
Both appended to
runner/docs/dev-2027-redesign-plan.mdper the redesign's convention:48:6582,72:15861); the repo has only the 145×22 wordmark, which at 20px tall is ~130px wide and swamps the pill. Shipped without it. The same gap blocks the favicon, which T9 lists and which currently 404s in dev — one square asset closes both.Live previewplaceholder. So anonymous play on a Tier-1 starter — the exact case72:15697draws with a/share/…URL — shows the placeholder. Worth a line from design on whether to drop the field instead.One known temporary gap: share mode used to print
{displayName} · HOT {version}in the top bar.frameworkLabelstill flows through the shell but nothing renders it until T5 builds the preview's bottom status bar.🤖 Generated with Claude Code
Note
Medium Risk
Touches core authoring layout, preview lifecycle (
reloadvs remount), and widespread E2E selectors; behaviour is mostly restyle plus intentional auth/Download rules, but container session handling is sensitive.Overview
T2 (DEV-2156) replaces the monolithic toolbar with Figma-aligned chrome: a 72px top bar (logo, centred example pill, theme toggle, Download / Sign in), separate 36px bars on the editor column (sidebar toggle + tabs) and preview column (refresh, URL, version/framework menus, docs/GitHub links, maximize), plus a signed-in-only action bar above the preview for Save/Share, Fork/Embed, and custom version.
App.tsxdrops its bespoke header and feedsEditorShellvia new slots (examplePill,authedExtras,publicUrl,previewUrl, etc.). The example cascader lives inside the pill with a search affordance; framework picking moves from button groups toMenuButtonlistboxes. Download stays available when anonymous (share/play), matching prior behaviour vs the task wording.Preview refresh adds optional
DemoRuntime.reload()— container re-navigates the iframe; Sandpack re-runsupdateSandbox(..., true)— avoiding remounts that would exhaust container session slots. Row-2 URL shows/share/:idwhen saved, containerpreviewUrlfor Tier 2, or a "Live preview" placeholder for Tier 1 Sandpack.PreviewPaneremoves the top "Live" status strip; E2E now assertsdata-preview-status="ready". Playwright docs tests use menu helpers for version/framework.Reviewed by Cursor Bugbot for commit 06d34b2. Bugbot is set up for automated code reviews on this repo. Configure here.