diff --git a/console/web/src/demo/LandingDemo.tsx b/console/web/src/demo/LandingDemo.tsx index 9bb72d16f..2a9397d69 100644 --- a/console/web/src/demo/LandingDemo.tsx +++ b/console/web/src/demo/LandingDemo.tsx @@ -408,16 +408,18 @@ function DemoChrome({ replay {/* Embedded only: the host page listens for iii-demo-close and scrolls - the console away. Inverted so the way out is unmissable. */} + the console away. Red, the loudest thing in the frame, because a + reader who wants out should not have to look for it. The exact red + and its text color are in demo.css. */} {window.self !== window.top && ( )} diff --git a/console/web/src/demo/demo.css b/console/web/src/demo/demo.css index 545c93c66..94b7f2c3d 100644 --- a/console/web/src/demo/demo.css +++ b/console/web/src/demo/demo.css @@ -62,3 +62,12 @@ transform: none; } } + +/* The way out of the embedded console. Taken down from the raw alert red, so + it reads as an exit rather than as something having gone wrong, and dark + enough that white holds AA at the 10px the chrome bar uses. Derived from the + token, identical in both themes: the button means the same thing in each. */ +.demo-close { + background: color-mix(in oklab, var(--color-alert) 72%, #1a0206); + color: #fff; +} diff --git a/console/web/src/demo/scenario.ts b/console/web/src/demo/scenario.ts index 1d7b08ad9..da0d16497 100644 --- a/console/web/src/demo/scenario.ts +++ b/console/web/src/demo/scenario.ts @@ -132,6 +132,11 @@ function readingDwellMs(body: string): number { return Math.min(2800, Math.max(600, tokenize(body).length * 10)) } +/** Mean per-token delay that streams `body` in about `totalMs`. */ +function paceOver(body: string, totalMs: number): number { + return totalMs / tokenize(body).length +} + async function* thought( body: string, signal?: AbortSignal, @@ -1139,6 +1144,10 @@ would have resumed from the last completed step, into the same trace.` /* ── the script ───────────────────────────────────────────────────────── */ +/** Streamed on a 1.1s budget: the reader is waiting on the first tool call. */ +const OPENING_THOUGHT = + 'The user wants a payments ledger with durable storage. Before I write a line of it I should look at what is already connected to this engine. iii keeps a live catalog of every running worker, so a durable database may already be here. If it is, there is nothing to scaffold and nothing to deploy alongside.' + export async function* runScenario( opts: ScenarioOptions, ): AsyncGenerator { @@ -1153,10 +1162,7 @@ export async function* runScenario( /* step 1 — look at what is already running */ yield* step( 1, - thought( - 'The user wants a payments ledger with durable storage. Before I write a line of it I should look at what is already connected to this engine. iii keeps a live catalog of every running worker, so a durable database may already be here. If it is, there is nothing to scaffold and nothing to deploy alongside.', - signal, - ), + thought(OPENING_THOUGHT, signal, paceOver(OPENING_THOUGHT, 1100)), signal, (stepSpan) => call({ diff --git a/console/web/src/demo/usePlayer.ts b/console/web/src/demo/usePlayer.ts index 3ca43b367..1b46ef301 100644 --- a/console/web/src/demo/usePlayer.ts +++ b/console/web/src/demo/usePlayer.ts @@ -41,7 +41,8 @@ export type Phase = 'idle' | 'typing' | 'streaming' | 'done' /** How long the finished turn stays up before the loop restarts. */ const HOLD_MS = 14000 -const TYPE_MS_PER_CHAR = 42 +/** Fast: the prompt is the preamble, the run is what the reader came for. */ +const TYPE_MS_PER_CHAR = 18 /** Repaint cadence while a span is still open, so its bar grows. */ const PENDING_TICK_MS = 120 /** The gate releases itself if nobody clicks approve. */