* Wait for the next screen update. * * Handles re-renders within the same prompt (e.g., validation errors, * async updates) and prompt transitions in multi-prompt flows * (automatically waits for the next prompt). * * Note: The initial prompt render is available immediately via get
()
| 61 | * — no next() call is needed before reading it. |
| 62 | */ |
| 63 | async next(): Promise<void> { |
| 64 | if (this.#activePromise) { |
| 65 | const currentPromise = this.#activePromise; |
| 66 | |
| 67 | // Consume any renders that happened synchronously (e.g., loading state). |
| 68 | // We want to wait for the next meaningful state change, not an intermediate render. |
| 69 | this.#rendersConsumed = this.#currentOutput?.writeCount ?? 0; |
| 70 | |
| 71 | // Race: a future render (validation error, async update) vs the promise settling |
| 72 | // (prompt completed, possibly synchronously before any new render arrives). |
| 73 | const renderPromise = this.#waitForNextRender(); |
| 74 | const settlePromise = currentPromise.then( |
| 75 | () => 'settled' as const, |
| 76 | () => 'settled' as const, |
| 77 | ); |
| 78 | |
| 79 | const result = await Promise.race([ |
| 80 | renderPromise.then(() => 'render' as const), |
| 81 | settlePromise, |
| 82 | ]); |
| 83 | |
| 84 | if (result === 'settled') { |
| 85 | if (this.#activePromise !== currentPromise) { |
| 86 | // New prompt already started — its render was caught by the race's renderPromise. |
| 87 | this.#rendersConsumed = this.#currentOutput?.writeCount ?? 0; |
| 88 | } else { |
| 89 | // Prompt settled but no new prompt yet. Wait for the next prompt's first render. |
| 90 | await this.#waitForNextRender(); |
| 91 | } |
| 92 | } else { |
| 93 | // Got a render. Check if the prompt also completed (making this a "done" render). |
| 94 | // Microtasks (promise settlement) always drain before macrotasks (setImmediate), |
| 95 | // so if the prompt resolved, its .then wins the race. |
| 96 | const settled = await Promise.race([ |
| 97 | currentPromise.then( |
| 98 | () => true, |
| 99 | () => true, |
| 100 | ), |
| 101 | new Promise<false>((resolve) => setImmediate(() => resolve(false))), |
| 102 | ]); |
| 103 | |
| 104 | if (settled) { |
| 105 | if (this.#activePromise !== currentPromise) { |
| 106 | // New prompt already started — its render was caught by the race. |
| 107 | this.#rendersConsumed = this.#currentOutput?.writeCount ?? 0; |
| 108 | } else { |
| 109 | // Prompt completed but no new prompt yet. Wait for it. |
| 110 | await this.#waitForNextRender(); |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | } else { |
| 115 | // No active promise — wait for a render (e.g., prompt hasn't started yet) |
| 116 | await this.#waitForNextRender(); |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | async #waitForNextRender(): Promise<void> { |
no test coverage detected