From 0f2bf101d8460d7617c32491b148c725bf47d80f Mon Sep 17 00:00:00 2001 From: heznpc Date: Thu, 4 Jun 2026 20:03:59 +0900 Subject: [PATCH] fix(tutor): propagate chatStream bridge-not-ready as a rejection, not a string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Confirmed by a service-quality audit. chatStream's try/catch caught the synchronous `throw new Error('Bridge not ready')` (Puter bridge handshake not finished — reachable on cold start / slow or blocked CDN / non-trusted host) and RESOLVED to a localized error string. But the sole caller (sidebar-chat.js:381) discards chatStream's return value and relies on a thrown error to render the error bubble + retry button. So on bridge-not-ready: the caller's await resolved normally, its catch never ran, onChunk never fired — leaving the "thinking…" spinner stranded forever with no error and no retry. The E2E helpers even document the intended behavior as "chatStream throws 'Bridge not ready'". Fix: re-throw from the catch instead of returning a string, so the synchronous setup failure propagates as a promise rejection and the caller's existing error+retry path handles it. Promise-path failures (timeout, abort, success:false) already reject and are unaffected. Added a regression test asserting chatStream rejects (not resolves) when isReady is false. 493 tests, eslint, prettier green. --- src/lib/translator.js | 12 ++++++++---- tests/translator.test.js | 12 ++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/lib/translator.js b/src/lib/translator.js index 684c2df..5ae67b6 100644 --- a/src/lib/translator.js +++ b/src/lib/translator.js @@ -735,11 +735,15 @@ User: ${userMessage}`; ); }); } catch (err) { + // Synchronous setup failures (most importantly `!this.isReady` — the Puter + // bridge hasn't finished its handshake) must PROPAGATE, not resolve to a + // string. The sole caller (sidebar-chat) discards chatStream's return value + // and relies on a thrown error to render the error bubble + retry button; + // returning a string here left the "thinking…" spinner stranded forever + // with no error and no retry. (Promise-path failures — timeout, abort, + // success:false — already reject and are unaffected.) console.error('[SkillBridge] Chat stream error:', err); - return ( - (typeof CHAT_ERROR_LABELS !== 'undefined' && CHAT_ERROR_LABELS[targetLang]) || - 'Sorry, I could not generate a response. Please try again.' - ); + throw err; } } diff --git a/tests/translator.test.js b/tests/translator.test.js index 2a92c55..68283cf 100644 --- a/tests/translator.test.js +++ b/tests/translator.test.js @@ -287,3 +287,15 @@ describe('Language JSON files', () => { }); }); }); + +describe('chatStream — bridge-not-ready propagates as a rejection', () => { + test('rejects (does not silently resolve to a string) when the bridge is not ready', async () => { + const t = new SkilljarTranslator(); + t.isReady = false; + // The sole caller (sidebar-chat) discards chatStream's return value and + // relies on a thrown error to render the error bubble + retry button. If + // this resolves to a string instead, the "thinking…" spinner is stranded + // forever with no error and no retry. + await expect(t.chatStream('hello', 'ko', '', () => {}, {})).rejects.toThrow('Bridge not ready'); + }); +});