From 1f1194983505ddd359e5af14edb817bc2d23d359 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Mon, 7 Sep 2026 14:19:22 -0700 Subject: [PATCH] test(terminal): pin the per-PTY ACK-silence skip in the write-off lane MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review ablation found the skip at accounting.ts unguarded: deleting it left all 13 heal cases green. The existing sibling test does not reach it, because its live pane ACKs everything main managed to send — the per-PTY send cap means sentChars equals ackedChars, so zero debt makes the earlier guard skip that pty first. A PARTIAL ack is what reaches it: the live pane keeps real debt while having answered a moment ago, which is exactly the state the skip protects. Without it that pane is written off in the same pass as its wedged sibling, because the heal is legitimately open and nothing else distinguishes them. --- src/main/ipc/pty-delivery-health-heal.test.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/main/ipc/pty-delivery-health-heal.test.ts b/src/main/ipc/pty-delivery-health-heal.test.ts index c5f4dce39fb..0c013ecd259 100644 --- a/src/main/ipc/pty-delivery-health-heal.test.ts +++ b/src/main/ipc/pty-delivery-health-heal.test.ts @@ -406,6 +406,39 @@ describe('registerPtyHandlers', () => { vi.useRealTimers() } }) + it('spares a PTY still round-tripping ACKs while a sibling is written off', () => { + vi.useFakeTimers() + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + try { + const provider = installObservableDaemonTestProvider() + registerPtyHandlers(mainWindow as never) + provider.emitData('pty-wedged', 'x'.repeat(600 * 1024)) + provider.emitData('pty-live', 'x'.repeat(600 * 1024)) + vi.advanceTimersByTime(2) + for (let index = 0; index < 400; index++) { + vi.advanceTimersByTime(1) + } + + // A PARTIAL ACK, so the live pane keeps real debt rather than settling to zero. That is + // the state the per-PTY silence skip exists for: the heal is legitimately open because + // the wedged pane is silent, and only the skip stops the same pass from writing off a + // pane that answered a moment ago and would have repaid the rest itself. + getPtyAckDataListener()(null, { id: 'pty-live', processedChars: 256 * 1024 }) + + const healed = reportRendererDeliveryState({ + receivedCharsByPty: { 'pty-wedged': 512 * 1024 }, + processedCharsByPty: {}, + parkedCharsByPty: { 'pty-wedged': 512 * 1024 }, + heal: true, + rendererPtyDataListenerCount: 1 + }) + + expect(healed.writtenOff?.map((entry) => entry.id)).toEqual(['pty-wedged']) + } finally { + warnSpy.mockRestore() + vi.useRealTimers() + } + }) it('reports per-PTY debt so the renderer can tell which pane is wedged', async () => { vi.useFakeTimers() const mockProc = createMockProc()