Turns the read-only Phase-1 view into an interactive shell, split into focused view components (spec v4 §2 layout): - view/transcript.tsx: ONE full-height <scrollbox> with a reactive <For> (opencode's no-scrollback model). Applies the §8 #2 gotchas exactly: minHeight:0 on the wrapper AND the scrollbox, NO flexDirection on the scrollbox root, stickyScroll + stickyStart="bottom". - view/composer.tsx: a native <textarea> captured by ref — flexShrink:0, focus-on-mount, Enter->submit via keyBindings, imperative .clear() on submit, and a `submitting` re-entrancy guard. Wired by the entry to fire prompt.submit (Effect.runFork on the in-hand service value); it's now the PRIMARY input, with the HERMES_TUI_PROMPT stand-in kept only for launch-with-prompt. - view/header.tsx + view/messageLine.tsx: extracted, themed (no hardcoded styles). MessageLine stays flat-text this slice; ordered parts (§7) land in 2b. test/lib/render.ts now flushes 3 renderOnce passes before capture — a <scrollbox> needs more than one pass to measure content + apply sticky, else the transcript row paints blank. Verified: bun run check green (12 tests / 4 files / 31 expects). Live tmux drive: typed into the composer -> cleared -> user row -> streamed reply ("Here are three words"); Ctrl+C quits cleanly even with the textarea focused, no orphan child. Composer placeholder rendered the live skin's welcome string (skin->theme live). Smoke P2a + parity matrix updated. Phase 2b (ordered parts/tool render/markdown) is the next slice.
28 lines
1.2 KiB
TypeScript
28 lines
1.2 KiB
TypeScript
/**
|
|
* Transcript — the scrolling message pane (spec v4 §2 `view/transcript.tsx`).
|
|
*
|
|
* ONE full-height <scrollbox> with a reactive <For> (opencode's model — the
|
|
* viewport clips growing output so terminal scrollback is never corrupted; no
|
|
* `writeToScrollback`). Carries the §8 #2 gotchas EXACTLY:
|
|
* - `minHeight:0` on BOTH the wrapper box AND the <scrollbox> (so the flex
|
|
* child can shrink below content height instead of pushing the composer off),
|
|
* - NO `flexDirection` on the <scrollbox> ROOT style (it has internal
|
|
* viewport/content children; setting it there breaks content-height
|
|
* measurement → phantom scroll offset that clips the top + leaves a gap),
|
|
* - `stickyScroll` + `stickyStart="bottom"` to pin the latest line.
|
|
*/
|
|
import { For } from 'solid-js'
|
|
|
|
import type { SessionStore } from '../logic/store.ts'
|
|
import { MessageLine } from './messageLine.tsx'
|
|
|
|
export function Transcript(props: { store: SessionStore }) {
|
|
return (
|
|
<box style={{ flexGrow: 1, minHeight: 0, marginTop: 1 }}>
|
|
<scrollbox style={{ flexGrow: 1, minHeight: 0 }} stickyScroll stickyStart="bottom">
|
|
<For each={props.store.state.messages}>{message => <MessageLine message={message} />}</For>
|
|
</scrollbox>
|
|
</box>
|
|
)
|
|
}
|