( cli: CliSurface, options: TheaterOptions, body: (theater: ChatTheater) => Effect.Effect<A, E, R>, )
| 115 | // --------------------------------------------------------------------------- |
| 116 | |
| 117 | const ptyTheater = <A, E, R>( |
| 118 | cli: CliSurface, |
| 119 | options: TheaterOptions, |
| 120 | body: (theater: ChatTheater) => Effect.Effect<A, E, R>, |
| 121 | ): Effect.Effect<A, E, R> => |
| 122 | Effect.gen(function* () { |
| 123 | const runDir = dirname(options.record); |
| 124 | const queue: TheaterEvent[] = []; |
| 125 | let closed = false; |
| 126 | const push = (event: TheaterEvent) => |
| 127 | Effect.sync(() => { |
| 128 | if (!closed) { |
| 129 | // Chatting IS focusing the terminal window. |
| 130 | if (event.type !== "done") markFocus(runDir, "terminal"); |
| 131 | queue.push(event); |
| 132 | } |
| 133 | }); |
| 134 | |
| 135 | const pump = cli.session( |
| 136 | ["bun", RENDERER, options.title], |
| 137 | async (term) => { |
| 138 | // Don't type until the renderer has painted its header — before that |
| 139 | // it hasn't set raw mode yet, and the PTY would echo the event line |
| 140 | // into the recording. |
| 141 | await term.screen.waitForText(options.title, { timeoutMs: 30_000 }); |
| 142 | // The cast's clock started with the PTY moments ago; anchor it for |
| 143 | // the run's focus timeline (scripts/film.ts cuts on these). |
| 144 | markRecordingStart(runDir, "terminal"); |
| 145 | const deadline = Date.now() + 30 * 60 * 1000; |
| 146 | for (;;) { |
| 147 | const event = queue.shift(); |
| 148 | if (event) { |
| 149 | await term.keyboard.type(encode(event)); |
| 150 | if (event.type === "done") break; |
| 151 | continue; |
| 152 | } |
| 153 | if (Date.now() > deadline) break; |
| 154 | await sleep(40); |
| 155 | } |
| 156 | await term.screen.waitForText("session complete", { timeoutMs: 60_000 }); |
| 157 | }, |
| 158 | { |
| 159 | record: options.record, |
| 160 | viewport: options.viewport ?? { cols: 100, rows: 32 }, |
| 161 | }, |
| 162 | ); |
| 163 | const pumpFiber = yield* Effect.forkChild(Effect.exit(pump)); |
| 164 | |
| 165 | const result = yield* Effect.exit(body(makeTheater(push))); |
| 166 | yield* push({ type: "done" }); |
| 167 | closed = true; |
| 168 | yield* Fiber.join(pumpFiber); |
| 169 | return yield* result; |
| 170 | }); |
| 171 | |
| 172 | // --------------------------------------------------------------------------- |
| 173 | // Desk mode: renderer in a visible xterm on the virtual desktop; events over |
no test coverage detected