Skip to content

feat(web): render routed pages in a pane on the canvas - #409

Draft
zhangfand wants to merge 1 commit into
mainfrom
feat/tile-surfaces
Draft

zhangfand wants to merge 1 commit into
mainfrom
feat/tile-surfaces

Conversation

@zhangfand

Copy link
Copy Markdown
Contributor

What this PR does

The dashboard had no rule for where a page's content sits. A bare list or table (/people, /sessions) lay on the tinted canvas, where muted ink reads dull. A carded page (/routines, /settings) lifted its rows onto surface tiles a shade off that canvas, so the tiles barely registered. Each page picked one, and the two looked like different products.

The shell now raises the content column onto --surface: inset from the canvas on desktop, full-bleed on a phone. The canvas is what remains around it — the sidebar and the gutters. Where the free canvas splits the column, each region becomes a pane of its own with the gutter between them, which is the same rule one level down and the part the split screen needed.

On the name. This is the "Tile" direction from the surface-model preview. Tile is already a component in this repo — the selectable option card in the Surface role group — so the concept is called a pane in code and docs rather than overloading a taken noun.

Design & Invariants

  • The pane belongs to the shell. A page, a layout, and a kit primitive never place one and never read whether they are inside one. Page, Section, ListCollection, FormRows, Card and Table are untouched.
  • A card inside a pane is an outlined region of the same step, not a second lift. That is what its border-border hairline already drew, which is why the card components needed no change.
  • --background is now the shell's. Every region that painted it while sitting inside the column now reads as a block of canvas cut into the pane, so those sites take --surface. Layers that float above a pane take --surface-elevated: the trace drawer, the app drag preview.
  • overflow-clip, not overflow-hidden. A page whose own column paints to the edge squares off the pane's corners, which /memory and /projects do. hidden fixes that but makes the column a scroll container, and the sticky bottom-0 composers inside would stop tracking the viewport. clip clips without joining the scroll chain.
  • The trace drawer docks inside the gutter rather than flush to the viewport edge, so a docked inspector shares the pane's frame.
  • Considered and rejected: gating the pane on a theme, as a surfaceModel field. This is a migration rather than a trial, and a per-theme switch would leave two layouts to reason about forever.

Test plan

  • pnpm typecheck
  • Full rstest suite for packages/web — 1487 passing
  • biome check on the changed files from the devShell
  • Mock dashboard: /routines, /people, /sessions, /apps, /settings, /memory, /projects, and /chat with the apps panel open
  • Ember light, Ember dark, Slate light, and a 430px viewport
  • Pane corners on /memory and /projects, which is what overflow-clip is for
  • pnpm test:layout (Playwright) — not run locally; the bundled browser will not launch on this host, so CI's pinned container is the first run
  • Trace drawer docked open, in a browser — the mock transcript exposes no trace trigger

Not in this PR

  • Four kit sites use bg-background as their lightest fill: Button's outline variant, the Switch thumb, Calendar, and SegmentedControl. Inside a pane they paint the canvas, about 1.08:1 against it in Ember. The fix belongs to @rome-os/ui and needs its own fix(ui) type to bump the package, so it follows separately.
  • App-side design. rome_apps/CLAUDE.md gains the contract — an app root sits on --surface and never paints --background — but the apps that do (briefing, showcases, and four others, 29 sites) are not converted here, and showcases is the sample app authors copy.
  • FileView's recessed viewer now reads as a large --surface-muted block on /projects, where it used to blend into the canvas. That is the depth model working rather than a token error, but it is worth a design look.
  • Ember, Ash and Slate keep their palettes. The pane reads by its border in all three, since --surface sits within 1.11:1 of --background everywhere.

🤖 Generated with Claude Code

The dashboard had no rule for where a page's content sits. A bare list
or table lay on the tinted canvas, where muted ink reads dull; a carded
page lifted its rows onto surface tiles a shade off that canvas, so the
tiles barely registered. Each page picked one, and the two looked like
different products.

The shell now raises the content column onto `--surface`: inset from the
canvas on desktop, full-bleed on a phone. The canvas is what remains
around it, the sidebar and the gutters. Where the free canvas splits the
column, each region becomes a pane of its own and the gutter runs
between them, which is the same rule at one more level and the one the
split screen needed. Layers that float above a pane — the trace drawer,
the app drag preview — take `--surface-elevated`.

Pages, layouts and kit primitives are untouched and never learn whether
they sit in a pane. A card inside one is an outlined region of the same
step, which is what its hairline already drew, so `Card`, `FormRows`,
`List` and `Table` needed no change. What did change is every region
that painted `--background` while sitting inside the column: it now
reads as a block of canvas cut into the pane, so those sites take
`--surface`.

The pane clips with `overflow-clip` rather than `overflow-hidden`. A
page whose own column paints to the edge would otherwise square off the
corners, but `hidden` would make the column a scroll container and the
`sticky bottom-0` composers inside would stop tracking the viewport.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@zoolsher zoolsher 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.

Code Review: 💬 COMMENT

This PR raises routed-page content onto a --surface "pane" at the shell (RomeShellLayout <main>), so pages paint bg-surface and the shell keeps --background for the sidebar and gutters. It uses a has-[[data-slot=tool-workspace]] selector to hand off the paint to per-region panes in the split (free-canvas) view, insets the trace drawer into the gutter, and updates docs plus the TraceDrawer placement tests to match. The overflow-clip vs overflow-hidden reasoning (keeping the sticky composer out of a new scroll container) is sound, and the surface-token migrations across AppFullPage, MemoryPage, SessionsPage, ToolWorkspace, etc. are consistent with the documented depth model.

Overall the design is clean and the scope is disciplined — deferred kit/app conversions are explicitly listed. The main issue is a small width-accounting drift in ToolWorkspace: the resize separator grew from 1px to 12px but the SEPARATOR_WIDTH constant that drives the pane-width clamp was not updated, so the right pane can be squeezed slightly below its minimum. Verified that no unit test asserts the exact separator width or the old shell background, so the change set is self-consistent.

Verdict: COMMENT — The change is coherent, well-documented, and test-backed; only a minor layout-math inconsistency and a low-priority affordance note are worth addressing, neither blocking.

2 finding(s) posted as inline comments below.

Severity Category File Title
P2 correctness packages/web/src/pages/free/ToolWorkspace.tsx Separator widened to 12px but SEPARATOR_WIDTH constant still 1
P3 design packages/web/src/pages/free/ToolWorkspace.tsx Resize gutter has no resting affordance

Automated review by RomeOS Code Review · commit b685f94

// The hairline between the two regions is a canvas gutter now, so the
// handle widens to it and drops the painted line; hover and drag tint
// the gutter instead of thickening a rule.
className="relative z-40 w-3 shrink-0 cursor-col-resize touch-none rounded-full transition-colors before:absolute before:inset-y-0 before:-inset-x-1 hover:bg-surface-hover focus-visible:outline-ring/50 data-[resizing]:bg-surface-hover"

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.

[P2] correctnessSeparator widened to 12px but SEPARATOR_WIDTH constant still 1

The resize handle changed from w-px (1px) to w-3 (12px), but SEPARATOR_WIDTH (line 26) is still 1. That constant feeds available = width - SEPARATOR_WIDTH (line 124), the chatWidth clamp (available - MIN_PANE_WIDTH), and COMPACT_WIDTH. Because the real gutter now consumes 11px more than the math assumes, at the extreme of the range the tabs pane can be squeezed ~11px below MIN_PANE_WIDTH (and the non-compact threshold is off by the same amount). Update the constant so the layout math matches the rendered gutter:

const SEPARATOR_WIDTH = 12; // matches the w-3 resize gutter

// The hairline between the two regions is a canvas gutter now, so the
// handle widens to it and drops the painted line; hover and drag tint
// the gutter instead of thickening a rule.
className="relative z-40 w-3 shrink-0 cursor-col-resize touch-none rounded-full transition-colors before:absolute before:inset-y-0 before:-inset-x-1 hover:bg-surface-hover focus-visible:outline-ring/50 data-[resizing]:bg-surface-hover"

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.

[P3] designResize gutter has no resting affordance

Dropping the painted after:...bg-border-subtle line means the split handle is now visually indistinguishable from the plain 12px gutter at rest — it only tints on hover/drag. This is intentional per the comment and the two panes' own borders imply the boundary, but discoverability of the drag affordance drops (no cursor/line hint until pointer-over). Optional: consider a very subtle resting indicator (e.g. a faint centered pill) so keyboard/mouse users can find it, or confirm this is the desired behavior.

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