From e54b0de196616f38ef1d85c145b2bf20a6e7ea77 Mon Sep 17 00:00:00 2001 From: mxrsv Date: Fri, 18 Sep 2026 23:57:09 +0700 Subject: [PATCH] feat(stage): float panes as cards on a 4px gutter Panes take a 4px gutter on every stage edge and between each other, a 6px corner and a 1px --seam-split border, so the work area reads as cards on the chrome the rail already paints instead of one ruled sheet. The split line goes: the divider element is now the gap itself and paints only its hover accent, at full --accent so it matches the sidebar grip. The document surface, the browser tab and the Agent Board take the same shape, so handing the stage over does not change the rectangle. Reverses the full-bleed grid of 2026-08-17 (DL-18.12, DL-2.3, DL-18.9, DL-20.1). DL-18.9's grip clause describes the sidebar-side half of this change, which landed on the local branch only. Claude-Session: https://claude.ai/code/session_01TPUA1o1Bc7wD6ZrqkXJDeA --- docs/DESIGN-LANGUAGE.md | 70 +++++++++++++++---- src/styles/06-stage-panes.css | 125 +++++++++++++++++++++++++++------- src/styles/19-agent-board.css | 9 ++- 3 files changed, 161 insertions(+), 43 deletions(-) diff --git a/docs/DESIGN-LANGUAGE.md b/docs/DESIGN-LANGUAGE.md index ac305e18..6e0c1df8 100644 --- a/docs/DESIGN-LANGUAGE.md +++ b/docs/DESIGN-LANGUAGE.md @@ -135,12 +135,19 @@ from the active terminal theme (`--bg --fg --accent --red --green --yellow layouts left `--seam-recessed` and joined them (DL-18.6), because two boundaries in one window drawn in two different greys made the quieter one read as an artefact rather than as something somebody chose. - **Amended 2026-09-11, owner:** the pane split alone left `--seam-divider` - for `--seam-split` — 20% of `--tone`, `--hair-strong`'s weight — and went - from 1px to 2px. At 12% and one pixel a grid of panes still read too faint, - while the strip's bottom edge did not, so the split is now the one line - inside the work area drawn heavier than the rest. Its hover and drag accent - is unchanged. + **Superseded for the pane split, 2026-09-18, owner: there is no split line + at all.** The 2026-09-11 amendment had taken it to `--seam-split` (20% of + `--tone`, `--hair-strong`'s weight) at 2px because a grid of panes read too + faint at 12% and one pixel. Panes are now rounded cards separated by the + stage gutter (DL-18.12), so the boundary is a background STEP — the thing the + split never had — and a drawn line beside a gap would read as three surfaces + instead of two. `--seam-split` keeps its 20% and its job — it **moved from + the line between two panes to the 1px edge around each one**, because + `--sidebar-bg` sits only a few luminance units off `--bg` and on a dark theme + the step alone left a card's corner legible and its straight runs invisible + (owner, in the dev app, same day). The divider element remains as the drag + target, transparent until its hover and drag accent, which is unchanged. The + tab strip's bottom edge keeps `--seam-divider` (DL-18.6). `--seam-raised` frames a surface that floats above chrome (popovers, dialogs). **The step must stay louder than the seam that marks it.** Before this rule a @@ -1434,13 +1441,19 @@ window's identity and its actions at the same time. absolute.** A resize seam lights its own hairline in full `--accent` while hovered, and for the whole of a drag — a target that says nothing until the column moves is found by guessing, and the app already answered this for pane - splits (`.split__divider:hover`). Full accent rather than that divider's 60% - mix: a shell seam has a chrome surface on at least one side, where the mix - reads as a lighter hairline instead of a lit line; a split divider stands - between two terminals on `--bg` and keeps its 60%. The line is 1px and sits ON the existing hairline - rather than beside it, so nothing new is drawn; only the colour of a line - that was already there changes, which keeps DL-2.3's one-boundary-one-seam - reading intact. The target itself also widened from 7px to 9px, for a + splits (`.split__divider:hover`). **Full accent, and since 2026-09-18 the + split divider takes the same value:** the 60% mix was justified by a + difference between the two — a shell seam has chrome on one side, a split + stands between two terminals on `--bg` — that DL-18.12 removed, because both + are now a 4px chrome gutter between two `--bg` cards. One idiom, one value. + **What lights is the GUTTER, not a hairline (2026-09-18, owner).** Until then + the lit line was 1px sitting ON the shell's own hairline, so that hovering + changed the colour of a line already there; once the stage stopped drawing + that hairline (DL-18.12) the same placement read as *grabbing the sidebar's + own border*, which is what the owner reported. The grip now fills + `--stage-gutter` at `--radius-flat`, so the bar the user grabs sits between + the two surfaces it moves instead of on the edge of one of them. The target + itself also widened from 7px to 9px, for a reported defect rather than a preference: along a 7px target the pointer crossed the edge repeatedly and the cursor flickered with it. Same figures on the docked column's seam (DL-19.4) — one gesture, one look, both edges. @@ -1559,6 +1572,34 @@ window's identity and its actions at the same time. exceptions until 2026-08-25, when DL-27.3's unread ripple became the third; they remain the scoped yellow exceptions recorded in DL-3.1/DL-3.2. +- **DL-18.12** **A pane is a card on the stage gutter (2026-09-18, owner).** + `--stage-gutter` is 4px and applies to all four edges of the work area AND + between panes ([`06-stage-panes.css`](../src/styles/06-stage-panes.css)); the + corner is 6px, DL-20.1's `--radius-tab` reused rather than a sixth role + opened at a use site. The gutter paints `--sidebar-bg`, the chrome the + navigation column already owns (DL-18.7), because the pane and the stage are + both `--bg` and a radius between two identical surfaces is invisible; + [`derive-colors.test.ts`](../src/lib/derive-colors.test.ts) already + guarantees `sidebarBg ≠ bg` in every theme. **That step is not enough on its + own, and the card carries a 1px `--seam-split` border as well** — the gutter + proved visible and the card's own outline did not. It is a real `border`, not + DL-1.3's permitted inset hairline: xterm fills the padding box and would + paint over an inset line. This reverses the full-bleed grid of + 2026-08-17, whose whole argument — the split line must meet the tab strip's + hairline — dissolves once the split is a gap rather than a line (DL-2.3). The + document surface takes the same gutter and radius so the stage handover does + not change the rectangle's shape; the browser tab is the one accepted + exception, since its web content is a native view the host paints over + [`.browser-panel__view`](../src/browser/browser-panel.tsx) and CSS cannot round + it. 4px and not the old 8px + because a terminal is measured in cells and the earlier gutter cost columns. + **The shell's vertical hairline went with this change.** `.window--sidebar > + .stage`'s `border-left` was the one structural line in the shell while it + separated `--sidebar-bg` from `--bg`; with the gutter painting + `--sidebar-bg` on the stage side, the same 1px ran between two identical + surfaces — a line with no step behind it, which DL-2.3 forbids. The rail and + the gutter are one chrome plane now, and the card's border is the boundary. + ## 19. Docked side panels Added 2026-08-12 for the browser panel, and written to cover the class rather @@ -1730,6 +1771,9 @@ direction token rebuild §9.4 its own. The tight role joined the earlier control/surface pair on 2026-08-16, when surface came down from 16px so the closed scale reads 8/10/12. A value chosen by feel at a use site is not part of this scale; + Amended 2026-09-18 (owner): `--radius-tab`'s membership now also covers the + stage's panes and document surface (DL-18.12), so the 6px corner is a shared + role rather than the strip's private number. `border-radius: 50%` and the 999px capsule stay shapes rather than scale values. - **DL-20.2** One motion pair for chrome state change: `--duration` (150ms) and diff --git a/src/styles/06-stage-panes.css b/src/styles/06-stage-panes.css index 55ccd6e2..c46e53c2 100644 --- a/src/styles/06-stage-panes.css +++ b/src/styles/06-stage-panes.css @@ -1,5 +1,16 @@ /* ── Stage (terminal area) ───────────────────────────────── */ +/* The gutter the pane-card geometry is derived from (owner, 2026-09-18): + 4px on every stage edge AND between panes. On `:root` and not on `.stage` + because `.pane` and `.split__divider` are also mounted outside a stage — the + gallery's seam specimens do exactly that. The matching 6px corner is + DL-20.1's `--radius-tab` spelled at each use site rather than aliased here: + `design-language.test.ts` reads the literal role names out of the + stylesheet, so an alias would read as a radius picked by feel. */ +:root { + --stage-gutter: 4px; +} + .stage { position: relative; min-height: 0; @@ -12,14 +23,20 @@ .stage__tabs { position: absolute; inset: 0; - /* No gutter (owner, 2026-08-17). The 8px this used to carry pushed the pane - grid in from every edge, so the vertical split divider started 8px BELOW - the tab strip's bottom hairline and the two lines never met — a gap nobody - noticed while the divider was 3% and impossible to miss at 12%. Panes are - full-bleed now: the split lines run into the strip's line, and the stage - reads as one ruled surface instead of a card floating in the window. */ - padding: 0; + /* A gutter on all four edges again (owner, 2026-09-18), replacing the + full-bleed grid of 2026-08-17: panes are rounded CARDS floating on the + gutter's chrome, so the stage no longer reads as one ruled surface and the + split lines no longer have to meet the strip's hairline — the whole reason + the 8px gutter was removed. 4px, not the old 8px, so the terminal keeps + its columns. */ + padding: var(--stage-gutter); display: flex; + /* The gutter has to be a DIFFERENT surface from the pane or the radius is + invisible: the pane background and `.stage` are both `--bg`. `--sidebar-bg` + is the chrome the rail already paints (DL-18.7), and + `derive-colors.test.ts` guarantees it never collapses into `--bg` in any + theme. */ + background: var(--sidebar-bg); } /* ── DL-18.6: the stage's tab strip (sidebar layout only) ───────────────── @@ -204,15 +221,34 @@ must still cover the document: the open board (30), Settings (35), the modal tier (40) and the popovers (100+). */ z-index: 20; - /* Whatever gutter `.stage__tabs` gives its panes — none, since 2026-08-17 — - so the document lands on exactly the rectangle the terminal was occupying - instead of shifting by a gutter when the stage changes hands. Both values - move together or the handover jumps. */ - padding: 0; + /* Whatever gutter `.stage__tabs` gives its panes — `--stage-gutter` since + 2026-09-18 — so the document lands on exactly the rectangle the terminal + was occupying instead of shifting by a gutter when the stage changes + hands. Both values move together or the handover jumps, and the gutter + surface has to match for the same reason. */ + padding: var(--stage-gutter); display: flex; min-width: 0; min-height: 0; - background: var(--bg); + background: var(--sidebar-bg); +} + +/* The document is a card on the gutter like a pane is, so the handover does + not change the shape of the rectangle. Named children, not `> *`: the + markdown view-mode toggle is also a direct child and it is a CONTROL, which + keeps its own DL-20.1 role. Radius only, no clipping: Monaco's suggest and + hover widgets live inside the editor and an `overflow` here would cut them + off. */ +.stage__surface > .fileview, +.stage__surface > .browser-panel, +.stage__surface > .agent-board { + border-radius: var(--radius-tab); + /* The pane's edge, for the pane's reason — see `.pane` below. This outranks + `.fileview`'s own `--hair` border (`11-settings-screen.css`) only while + the file view IS the stage's card; inside the settings screen it stays a + line within a surface, which is what `--hair` is for. */ + border: 1px solid var(--seam-split); + box-sizing: border-box; } .stage--strip .stage__surface { @@ -223,11 +259,13 @@ right: var(--dock-w); } -/* The browser variant of the surface: the native view meets the stage's - edges — a web page brings its own margins, unlike the editor the 8px - gutter was measured for. */ +/* The browser variant keeps the gutter the base rule gives it (2026-09-18): + a page floating on the chrome like every other card is worth more than the + few pixels a web page's own margins duplicate. The corners of the web + content stay SQUARE and that is accepted — the content is a native view the + host positions over `.browser-panel__view`, and CSS cannot round it. */ .stage__surface--browser { - padding: 0; + padding: var(--stage-gutter); } /* ── Browser surface: the browser tab's stage cover (DL-18.8) ──────────── @@ -243,6 +281,9 @@ min-width: 0; min-height: 0; background: var(--bg); + /* The radius comes from `.stage__surface > *`; this is what makes the URL + bar's own edges follow it instead of squaring off the top corners. */ + overflow: hidden; } .browser-panel__bar { @@ -318,7 +359,9 @@ inset: 0; display: flex; z-index: 10; - background: var(--bg); + /* The gutter's surface, not `--bg`: the zoomed pane is a card on the same + chrome as an unzoomed one, so its corners still read as corners. */ + background: var(--sidebar-bg); } .tab-stage > * { @@ -365,14 +408,19 @@ transition: none; } -/* A 2px seam between panes — no gap. `--seam-split`, not `--seam-divider`: - the owner asked for a heavier split on 2026-09-11 (DL-2.3), and the strip's - bottom edge keeps the lighter line. The grab zone is widened past the - visible line with a pseudo-element so a thin line stays easy to drag. */ +/* A `--stage-gutter` GAP between panes since 2026-09-18, replacing the 2px + `--seam-split` line of 2026-09-11: two rounded cards separated by the + gutter's chrome need no drawn seam, and a line plus a gap would read as + three surfaces. The divider element stays — it IS the gap, so it keeps + owning the drag — and paints nothing until hover. Its grab zone is still + widened past its own width with a pseudo-element. */ .split__divider { position: relative; - flex: 0 0 2px; - background: var(--seam-split); + flex: 0 0 var(--stage-gutter); + background: transparent; + /* The sidebar grip's shape, for the same reason it has it: a 4px accent + reads as a bar, and a bar takes DL-20.1's 2px detail corner. */ + border-radius: var(--radius-flat); transition: background var(--duration) var(--ease); } @@ -396,9 +444,13 @@ inset: -3px 0; } +/* Full `--accent`, up from a 60% mix (owner, 2026-09-18): the sidebar grip and + this divider are now the same object — a 4px chrome gutter between two + `--bg` cards — and the 60% was justified by a difference between them that + no longer exists (DL-18.9). One idiom, one value. */ .split__divider:hover, .split__divider.is-dragging { - background: color-mix(in srgb, var(--accent) 60%, transparent); + background: var(--accent); } /* While dragging a divider, block mouse events on terminals so text is not selected */ @@ -415,6 +467,11 @@ position: relative; display: flex; flex: 1 1 0; + /* Same corner as the pane it holds (DL-18.12): `pingPane` appends + `.pane-ping` to the SLOT, not to the pane, so the locator's 2px line would + otherwise draw square ends out over the gutter where the pane's curve has + already receded. */ + border-radius: var(--radius-tab); min-width: 0; min-height: 0; overflow: hidden; @@ -430,6 +487,24 @@ overflow: hidden; background: var(--bg); } + /* 6px (owner, 2026-09-18). The existing `overflow: hidden` is what makes it + visible: xterm's canvases fill the pane and would otherwise paint over the + rounded corners, and the same clip keeps `.is-agent-working`'s own + top-edge overlays inside the curve. `.pane-ping` is NOT one of them — it + hangs off `.pane-slot`, which carries the matching radius above. */ + border-radius: var(--radius-tab); + /* The card's own edge, added the same day the gutter was: `--sidebar-bg` is + only a few luminance units off `--bg`, so on a dark theme the gutter alone + left the corner legible and the straight runs invisible — the owner saw + exactly that in the dev app. `--seam-split` is the token the pane boundary + has always owned (DL-2.3); it moved from the line BETWEEN two panes to the + edge AROUND each one, so it is the same 20% of `--tone` doing the same + job at the new geometry. A real border and not `box-shadow: inset` + (DL-1.3's permitted hairline): xterm's canvases fill the padding box and + would paint straight over an inset line, while a border sits outside it. + The 1px costs the terminal nothing — the grid re-fits to the padding box + and a cell is never that small. */ + border: 1px solid var(--seam-split); /* The pane's two yellow signals share one visual language but not one life: a rail click appends `.pane-ping` for a single 1.5s locator; any pane whose diff --git a/src/styles/19-agent-board.css b/src/styles/19-agent-board.css index a0b5e406..95c3e255 100644 --- a/src/styles/19-agent-board.css +++ b/src/styles/19-agent-board.css @@ -11,11 +11,10 @@ are kept, unmatched, as what a revert re-mounts. */ /* The Board's rectangle on the stage is `.stage__surface` itself (spec §4.1), - and that class has carried `padding: 0` since 2026-08-17 — so unlike - `.stage__surface--browser`, which zeroed a gutter that no longer exists, the - Board's modifier needs no declaration of its own. The class stays on the - element as the surface's name in the DOM; a rule that only restated the base - would be dead the day someone tried to change it. */ + and it takes that class's `--stage-gutter` and 6px corner unchanged + (DL-18.12, 2026-09-18) — the Board is a card on the gutter like a pane or a + document. The modifier needs no declaration of its own; the class stays on + the element as the surface's name in the DOM. */ .agent-board { /* DL-34.5 / DL-4.1: the one place chrome is monospace. */