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
26 changes: 18 additions & 8 deletions specs/spec10/10-tui.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,11 +348,14 @@ clipped to end above the card (`waveRowsFor`), and either raster is deleted
only when the card's own rows would collide with it (`stagePlan`,
`cardTopRow` in `dock.ts` replaying the renderer's width/chrome math),
because kitty images composite ABOVE text cells) and the head ask renders as
a centered rounded card (refs' ~55% content width) on the `CARD` ground in
the dock's slot — and the answer field moves INTO the card (user decision,
2026-08-11): the same single input, permanent focus intact, renders as the
card's last row while a question is open, and the bottom row keeps only its
quiet rule. The card is where you read AND where you answer.
a centered rounded card (refs' ~55% content width) on the `CARD` ground,
**floating over the room** (amended 2026-08-12, with §3.3's overlay
amendment): the card is absolutely positioned a gap row above the bottom
rule and takes no rows from the layout, so nothing behind it moves — the
room dims, it never rearranges — and the answer field moves INTO the card
(user decision, 2026-08-11): the same single input, permanent focus intact,
renders as the card's last row while a question is open, and the bottom row
keeps only its quiet rule. The card is where you read AND where you answer.
Kind picks the frame: warm/ember for a question (with a client-side `#n`
counter in the title), periwinkle for a consent (` · optional` in the title)
— the listener's color, because the decision is theirs; a consent card
Expand Down Expand Up @@ -438,9 +441,16 @@ now-playing stays in the status strip. Same four regions either way; only
the composition moves, which is exactly the §6.1 licence. **Overlays and the
band**: the settings pane reclaims the band's rows outright (a mode the
listener opened is their own full attention); a spotlight card keeps the sky
on stage dimmed per §3.2-B, and the band steps off only when the card's top
row (the renderer's own math, `cardTopRow`) would climb into the scene — the
short-terminal case where both cannot fit. **The starfield is
on stage dimmed per §3.2-B ~~, and the band steps off only when the card's
top row (the renderer's own math, `cardTopRow`) would climb into the scene —
the short-terminal case where both cannot fit~~ — **amended 2026-08-12 (user
report: the card is a mask, not a sibling — a checklist card tall enough to
trip the yield collapsed the whole scene and threw the wordmark to the top
of the frame): the card floats (yoga absolute, above the text layer), takes
no rows from the composition, and the band never steps off while the pane is
closed; only the raster layers yield, per-rectangle, where the card's own
rows reach (`stagePlan` / `waveRowsFor` against `cardTopRow`), because kitty
images composite above text cells and would otherwise cover the card. **The starfield is
retired** (2026-08-12 design session): at character resolution the scatter
read as noise, so the night behind the wave and the figure stays empty and
the scene's texture budget goes to the wave alone.
Expand Down
26 changes: 16 additions & 10 deletions tui/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -410,12 +410,11 @@ export function App({ subscribe, wire }: { subscribe: Subscribe; wire: Wire }):
const { scene: sceneRows } = sceneSplit(Math.max(dims.height - 5, 10))
const sceneWidth = cols - 2
// Whether the scene band holds the stage. The settings pane always reclaims
// its rows (a mode the listener opened is their own full attention); a
// spotlight card keeps the sky dimmed beside it (§3.2-B: a sky going dark
// under every consent reads as broken) unless the terminal is too short to
// host both — the card's own top row, from the renderer's math, is the
// judge. The paint loops read this through a ref, like the card row.
const sceneShown = wide && !paneOpen && (cardTop === null || cardTop > 3 + sceneRows)
// its rows (a mode the listener opened is their own full attention). The
// spotlight card takes none: it floats over the room (§3.2-B), so the sky
// stays on stage dimmed beneath it — only the raster layers yield, and only
// where the card's own rows reach (stagePlan / waveRowsFor via the refs).
const sceneShown = wide && !paneOpen
const sceneShownRef = useRef(sceneShown)
sceneShownRef.current = sceneShown
// The newest broadcast line carries the bullet (concept 04); older lines
Expand Down Expand Up @@ -761,8 +760,11 @@ export function App({ subscribe, wire }: { subscribe: Subscribe; wire: Wire }):

{/* The spotlight card (§3.2-B as built): the oldest pending ask grows
into a centered rounded card while the room around it is hushed.
Kind picks the frame: warm for a question, periwinkle for a consent —
the listener's color, because the decision is theirs. */}
It FLOATS — absolutely positioned over the log, one gap row above
the bottom rule, taking no rows from the layout: the room behind it
dims but never rearranges. Kind picks the frame: warm for a
question, periwinkle for a consent — the listener's color, because
the decision is theirs. */}
{asks.length > 0 &&
(() => {
const head = asks[0]!
Expand All @@ -786,13 +788,17 @@ export function App({ subscribe, wire }: { subscribe: Subscribe; wire: Wire }):
borderColor: frame,
titleColor: consent ? PERIWINKLE : EMBER,
flexDirection: 'column',
alignSelf: 'center',
position: 'absolute',
// Yoga anchors absolute insets to the parent's border box, so
// the ultrawide gutter must be added back to stay centered.
left: gutter + Math.floor((cols - width) / 2),
bottom: 2,
zIndex: 100,
width,
paddingLeft: 2,
paddingRight: 2,
paddingTop: 1,
paddingBottom: 1,
marginBottom: 1,
backgroundColor: CARD,
}}
>
Expand Down
7 changes: 4 additions & 3 deletions tui/src/dock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,13 @@ export function cardRows(text: string, cols: number): number {
rows += 2 // the action row (its top margin + the line)
rows += 2 // the in-card answer field (its top margin + the input)
rows += 4 // border (2) + vertical padding (2)
rows += 1 // the card's bottom margin
rows += 1 // the gap row between the floating card and the bottom rule
return rows
}

// The first terminal row the card can touch: it stands on the window's bottom
// row (the quiet rule that keeps the frame closed). Rasters end above this.
// The first terminal row the card can touch: the card floats anchored to the
// window's bottom rule (the quiet line that keeps the frame closed), so its
// top is the window height minus its own rows. Rasters end above this.
export function cardTopRow(text: string, cols: number, height: number): number {
return Math.max(1, height - 1 - cardRows(text, cols))
}
Expand Down
Loading