diff --git a/ui-tui-opentui-v2/src/entry/main.tsx b/ui-tui-opentui-v2/src/entry/main.tsx index 5afbda28013..98bda9de7d1 100644 --- a/ui-tui-opentui-v2/src/entry/main.tsx +++ b/ui-tui-opentui-v2/src/entry/main.tsx @@ -92,6 +92,25 @@ export const run = Effect.fn('Tui.run')(function* (input: TuiInput) { const gateway = yield* GatewayService yield* gateway.subscribe(event => store.apply(event)) + // Composer submit: a plain callback the Solid view calls. The service value + // is already in hand, so `gateway.request(...)` is Effect<…, never> — fire it + // detached with runFork; failures are logged, never thrown into the view. + const submit = (text: string) => { + store.pushUser(text) + const sid = gateway.sessionId() + if (!sid) { + getLog().warn('submit', 'no session yet — dropping prompt', { text }) + return + } + Effect.runFork( + gateway + .request('prompt.submit', { session_id: sid, text }) + .pipe( + Effect.catchCause(cause => Effect.sync(() => getLog().warn('submit', 'failed', { cause: String(cause) }))) + ) + ) + } + // Live backend: drive a session (create + optional initial prompt) concurrently. if (!input.fake) yield* Effect.forkScoped(bootstrapSession(gateway, store, input)) @@ -101,7 +120,7 @@ export const run = Effect.fn('Tui.run')(function* (input: TuiInput) { render( () => ( store.state.theme}> - + ), renderer diff --git a/ui-tui-opentui-v2/src/test/render.test.tsx b/ui-tui-opentui-v2/src/test/render.test.tsx index c8119da5a3e..dbe70cd930c 100644 --- a/ui-tui-opentui-v2/src/test/render.test.tsx +++ b/ui-tui-opentui-v2/src/test/render.test.tsx @@ -29,12 +29,13 @@ describe('App render (Phase 1, themed)', () => { ), - { width: 60, height: 8 } + { width: 60, height: 16 } ) expect(frame).toContain('Hermes Agent') // default brand.name expect(frame).toContain('ready') expect(frame).toContain('Hi there, glitch!') + expect(frame).toContain('Type your message') // composer placeholder (brand.welcome) }) test('applying a skin re-themes the brand name (skinnable, no hardcoding)', async () => { @@ -48,7 +49,7 @@ describe('App render (Phase 1, themed)', () => { ), - { width: 60, height: 8 } + { width: 60, height: 16 } ) expect(frame).toContain('Zephyr') diff --git a/ui-tui-opentui-v2/src/view/App.tsx b/ui-tui-opentui-v2/src/view/App.tsx index cec5419ebcb..d79dbbb2f25 100644 --- a/ui-tui-opentui-v2/src/view/App.tsx +++ b/ui-tui-opentui-v2/src/view/App.tsx @@ -1,49 +1,33 @@ /** - * App — the Solid view shell (spec v4 §2 `view/App.tsx`). Phase 1: header + - * transcript, fully themed via `useTheme()` — NO hardcoded styles (spec §7.5). - * The streamed message + skin both come from the store; the boundary feeds them. + * App — the Solid view shell (spec v4 §2 `view/App.tsx`). Phase 2: header + + * scrolling transcript + composer, composed in a flex column. Fully themed via + * the ThemeProvider — NO hardcoded styles (§7.5). * - * Rich text uses / children, never an attributes bitmask (gotcha §8 #1). - * Inline color goes in `style={{ fg }}` on ; accepts `fg` directly. + * header flexShrink:0 (top chrome line) + * transcript flexGrow:1, minHeight:0 (the one ; §8 #2 gotchas) + * composer flexShrink:0 (the