Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 57 additions & 13 deletions docs/DESIGN-LANGUAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
125 changes: 100 additions & 25 deletions src/styles/06-stage-panes.css
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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) ─────────────────
Expand Down Expand Up @@ -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 {
Expand All @@ -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) ────────────
Expand All @@ -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 {
Expand Down Expand Up @@ -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 > * {
Expand Down Expand Up @@ -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);
}

Expand All @@ -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 */
Expand All @@ -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;
Expand All @@ -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
Expand Down
9 changes: 4 additions & 5 deletions src/styles/19-agent-board.css
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
Loading