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'); + }); +});