diff --git a/static/boot.js b/static/boot.js index c1411f6c..1b63827c 100644 --- a/static/boot.js +++ b/static/boot.js @@ -99,6 +99,30 @@ async function _savedSessionSidebarOnlyState(sid){ } // ── Mobile navigation ────────────────────────────────────────────────────── +// URL prefill boot helpers. +function _prefillHasDraftText(prefillIntent){ + return !!(prefillIntent&&prefillIntent.hasText); +} +function _rootPrefillNeedsFreshComposer(urlSession, savedLocal, prefillIntent){ + return !urlSession&&!!savedLocal&&_prefillHasDraftText(prefillIntent); +} +async function _applyComposerPrefillOnBoot(prefillIntent){ + if(!prefillIntent||!prefillIntent.hasText) return; + const msg=(typeof $==='function')?$('msg'):document.getElementById('msg'); + if(!msg) return; + const text=String(prefillIntent.text||''); + msg.value=text; + if(typeof autoResize==='function') autoResize(); + else if(typeof updateSendBtn==='function') updateSendBtn(); +} +async function _finalizeComposerPrefillOnBoot(prefillIntent){ + if(prefillIntent&&prefillIntent.hasParams&&typeof _consumeComposerPrefillParamsFromLocation==='function'){ + _consumeComposerPrefillParamsFromLocation(); + } + await _applyComposerPrefillOnBoot(prefillIntent); +} + +// Mobile navigation. let _workspacePanelMode='closed'; // 'closed' | 'browse' | 'preview' function _isCompactWorkspaceViewport(){ @@ -224,7 +248,8 @@ function handleWorkspaceClose(){ closeWorkspacePanel(); } -async function _maybeBindFreshDefaultWorkspaceSession(){ +async function _maybeBindFreshDefaultWorkspaceSession(prefillIntent=null){ + if(_prefillHasDraftText(prefillIntent)) return false; if(S.session) return false; if(_workspacePanelMode!=='browse') return false; if(!S._profileDefaultWorkspace) return false; @@ -2424,6 +2449,7 @@ window._applyTitlebarProfileVisibility=_applyTitlebarProfileVisibility; (async()=>{ // Load send key preference let _bootSettings={}; + const prefillIntent=(typeof _composerPrefillIntentFromLocation==='function')?_composerPrefillIntentFromLocation():null; try{ const s=await api('/api/settings'); _bootSettings=s; @@ -2822,7 +2848,7 @@ window._applyTitlebarProfileVisibility=_applyTitlebarProfileVisibility; try{Promise.resolve(_startBootModelDropdown()).catch(()=>{});}catch(_){} } S._bootReady=true; - syncTopbar();syncWorkspacePanelState();await renderSessionList();if(typeof startGatewaySSE==='function')startGatewaySSE();return; + syncTopbar();syncWorkspacePanelState();await renderSessionList();await _finalizeComposerPrefillOnBoot(prefillIntent);if(typeof startGatewaySSE==='function')startGatewaySSE();return; }catch(e){console.warn('[pwa] new-chat launch action failed', e);} } const savedLocal=localStorage.getItem('hermes-webui-session'); @@ -2840,7 +2866,19 @@ window._applyTitlebarProfileVisibility=_applyTitlebarProfileVisibility; S._bootReady=true; syncTopbar();syncWorkspacePanelState(); $('emptyState').style.display=''; - await renderSessionList();if(typeof startGatewaySSE==='function')startGatewaySSE(); + await renderSessionList();await _finalizeComposerPrefillOnBoot(prefillIntent);if(typeof startGatewaySSE==='function')startGatewaySSE(); + return; + } + if(_rootPrefillNeedsFreshComposer(urlSession, savedLocal, prefillIntent)){ + S.session=null; S.messages=[]; S.activeStreamId=null; S.busy=false; + S._bootReady=true; + const _ephPanelPref=localStorage.getItem('hermes-webui-workspace-panel-pref')==='open' + || localStorage.getItem('hermes-webui-workspace-panel')==='open'; + if(_ephPanelPref&&!_isCompactWorkspaceViewport()) _workspacePanelMode='browse'; + await _maybeBindFreshDefaultWorkspaceSession(prefillIntent); + syncTopbar();syncWorkspacePanelState(); + $('emptyState').style.display=''; + await renderSessionList();await _finalizeComposerPrefillOnBoot(prefillIntent);if(typeof startGatewaySSE==='function')startGatewaySSE(); return; } await loadSession(saved, {preserveActiveInput:true}); @@ -2875,10 +2913,10 @@ window._applyTitlebarProfileVisibility=_applyTitlebarProfileVisibility; const _ephPanelPref=localStorage.getItem('hermes-webui-workspace-panel-pref')==='open' || localStorage.getItem('hermes-webui-workspace-panel')==='open'; if(_ephPanelPref&&!_isCompactWorkspaceViewport()) _workspacePanelMode='browse'; - await _maybeBindFreshDefaultWorkspaceSession(); + await _maybeBindFreshDefaultWorkspaceSession(prefillIntent); syncTopbar();syncWorkspacePanelState(); $('emptyState').style.display=''; - await renderSessionList();if(typeof startGatewaySSE==='function')startGatewaySSE(); + await renderSessionList();await _finalizeComposerPrefillOnBoot(prefillIntent);if(typeof startGatewaySSE==='function')startGatewaySSE(); return; } // Restore the panel from localStorage when the session has a workspace. @@ -2890,7 +2928,7 @@ window._applyTitlebarProfileVisibility=_applyTitlebarProfileVisibility; _workspacePanelMode='browse'; } S._bootReady=true; - syncTopbar();syncWorkspacePanelState();await renderSessionList();if(typeof startGatewaySSE==='function')startGatewaySSE();await checkInflightOnBoot(saved);return;} + syncTopbar();syncWorkspacePanelState();await renderSessionList();if(typeof startGatewaySSE==='function')startGatewaySSE();await checkInflightOnBoot(saved);await _finalizeComposerPrefillOnBoot(prefillIntent);return;} catch(e){localStorage.removeItem('hermes-webui-session');} } // no saved session - show empty state, wait for user to hit + @@ -2901,10 +2939,10 @@ window._applyTitlebarProfileVisibility=_applyTitlebarProfileVisibility; const _freshPanelPref=localStorage.getItem('hermes-webui-workspace-panel-pref')==='open' || localStorage.getItem('hermes-webui-workspace-panel')==='open'; if(_freshPanelPref&&!_isCompactWorkspaceViewport()) _workspacePanelMode='browse'; - await _maybeBindFreshDefaultWorkspaceSession(); + await _maybeBindFreshDefaultWorkspaceSession(prefillIntent); syncWorkspacePanelState(); $('emptyState').style.display=''; - await renderSessionList(); + await renderSessionList();await _finalizeComposerPrefillOnBoot(prefillIntent); // Start real-time gateway session sync if setting is enabled if(typeof startGatewaySSE==='function') startGatewaySSE(); })().catch(e=>{ diff --git a/static/sessions.js b/static/sessions.js index edb29242..7eb6a679 100644 --- a/static/sessions.js +++ b/static/sessions.js @@ -3303,6 +3303,38 @@ function _sessionIdFromLocation(){ return qs.get('session')||qs.get('session_id')||null; }catch(_e){return null;} } +function _composerPrefillIntentFromLocation(){ + const empty={hasParams:false,hasText:false,text:'',autoSend:false}; + if(typeof window==='undefined'||!window.location) return empty; + try{ + const qs=new URLSearchParams(window.location.search||''); + const hasQ=qs.has('q'); + const hasPrompt=qs.has('prompt'); + const hasSend=qs.has('send'); + if(!hasQ&&!hasPrompt&&!hasSend) return empty; + const text=hasQ?(qs.get('q')||''):(hasPrompt?(qs.get('prompt')||''):''); + return { + hasParams:true, + hasText:!!String(text).trim(), + text, + autoSend:false + }; + }catch(_e){return empty;} +} +function _consumeComposerPrefillParamsFromLocation(){ + if(typeof window==='undefined'||!window.location||!window.history||typeof window.history.replaceState!=='function') return; + try{ + const current=new URL(window.location.href); + const before=current.searchParams.toString(); + current.searchParams.delete('q'); + current.searchParams.delete('prompt'); + current.searchParams.delete('send'); + const after=current.searchParams.toString(); + if(after===before) return; + const next=current.pathname+(after?`?${after}`:'')+(current.hash||''); + window.history.replaceState(window.history.state||null,'',next); + }catch(_e){} +} function _appRootPath(){ try{ const base = new URL(document.baseURI||window.location.origin+'/', window.location.origin); @@ -3318,6 +3350,9 @@ function _sessionUrlForSid(sid){ const current=new URL(window.location.href); current.searchParams.delete('session'); current.searchParams.delete('session_id'); + current.searchParams.delete('q'); + current.searchParams.delete('prompt'); + current.searchParams.delete('send'); base.search=current.searchParams.toString(); base.hash=current.hash; }catch(_e){} diff --git a/tests/test_4961_url_query_prefill.py b/tests/test_4961_url_query_prefill.py new file mode 100644 index 00000000..78cee566 --- /dev/null +++ b/tests/test_4961_url_query_prefill.py @@ -0,0 +1,312 @@ +"""Regression tests for #4961 URL query composer prefill behavior.""" +import json +import shutil +import subprocess +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).parent.parent.resolve() +SESSIONS_JS_PATH = REPO_ROOT / "static" / "sessions.js" +BOOT_JS_PATH = REPO_ROOT / "static" / "boot.js" +SESSIONS_JS = SESSIONS_JS_PATH.read_text(encoding="utf-8") +BOOT_JS = BOOT_JS_PATH.read_text(encoding="utf-8") +NODE = shutil.which("node") + +pytestmark = pytest.mark.skipif(NODE is None, reason="node not on PATH") + + +def _run_node(source: str) -> str: + result = subprocess.run( + [NODE], + input=source, + cwd=str(REPO_ROOT), + capture_output=True, + encoding="utf-8", + text=True, + timeout=30, + ) + if result.returncode != 0: + raise RuntimeError(result.stderr) + return result.stdout.strip() + + +def _node_prelude() -> str: + return f""" +const sessionsSrc = {SESSIONS_JS!r}; +const bootSrc = {BOOT_JS!r}; +function extractFunc(src, name) {{ + const re = new RegExp('(?:async\\\\s+)?function\\\\s+' + name + '\\\\s*\\\\('); + const start = src.search(re); + if (start < 0) throw new Error(name + ' not found'); + let i = src.indexOf('{{', start); + let depth = 1; i++; + while (depth > 0 && i < src.length) {{ + if (src[i] === '{{') depth++; + else if (src[i] === '}}') depth--; + i++; + }} + return src.slice(start, i); +}} +function evalSession(name) {{ + globalThis[name] = (0, eval)('(' + extractFunc(sessionsSrc, name) + ')'); +}} +function evalBoot(name) {{ + globalThis[name] = (0, eval)('(' + extractFunc(bootSrc, name) + ')'); +}} +""" + + +def test_prefill_intent_parses_q_prompt_and_send_flag(): + source = _node_prelude() + """ +global.window = { location: { search: '?prompt=backup&q=hello%20world&send=YES' } }; +evalSession('_composerPrefillIntentFromLocation'); +const first = _composerPrefillIntentFromLocation(); +window.location.search = '?prompt=from+prompt'; +const second = _composerPrefillIntentFromLocation(); +window.location.search = '?q=%20%20&send=1'; +const third = _composerPrefillIntentFromLocation(); +console.log(JSON.stringify({ first, second, third })); +""" + payload = json.loads(_run_node(source)) + assert payload["first"] == { + "hasParams": True, + "hasText": True, + "text": "hello world", + "autoSend": False, + } + assert payload["second"] == { + "hasParams": True, + "hasText": True, + "text": "from prompt", + "autoSend": False, + } + assert payload["third"] == { + "hasParams": True, + "hasText": False, + "text": " ", + "autoSend": False, + } + + +def test_prefill_cleanup_removes_only_consumed_query_params(): + source = _node_prelude() + """ +function applyUrl(rel) { + const next = new URL(rel, 'https://example.test'); + window.location.href = next.href; + window.location.pathname = next.pathname; + window.location.search = next.search; + window.location.hash = next.hash; +} +global.window = { + location: {}, + history: { + state: { from: 'test' }, + calls: [], + replaceState(state, title, url) { + this.calls.push({ state, title, url }); + this.state = state; + applyUrl(url); + } + } +}; +global.document = { baseURI: 'https://example.test/app/' }; +applyUrl('/app/?q=hello&prompt=backup&send=1&session_id=target&keep=1#frag'); +evalSession('_consumeComposerPrefillParamsFromLocation'); +evalSession('_sessionUrlForSid'); +_consumeComposerPrefillParamsFromLocation(); +const cleaned = window.history.calls[0]; +const promoted = _sessionUrlForSid('abc 123'); +console.log(JSON.stringify({ cleaned, promoted })); +""" + payload = json.loads(_run_node(source)) + assert payload["cleaned"]["url"] == "/app/?session_id=target&keep=1#frag" + assert payload["cleaned"]["state"] == {"from": "test"} + assert payload["promoted"] == "/app/session/abc%20123?keep=1#frag" + + +def test_root_prefill_keeps_saved_local_sidebar_only(): + source = _node_prelude() + """ +evalBoot('_prefillHasDraftText'); +evalBoot('_rootPrefillNeedsFreshComposer'); +const result = { + savedLocalWins: _rootPrefillNeedsFreshComposer(null, 'saved-local', { hasText: true }), + explicitSessionWins: _rootPrefillNeedsFreshComposer('url-session', 'saved-local', { hasText: true }), + blankPrefillIgnored: _rootPrefillNeedsFreshComposer(null, 'saved-local', { hasText: false }) +}; +console.log(JSON.stringify(result)); +""" + payload = json.loads(_run_node(source)) + assert payload == { + "savedLocalWins": True, + "explicitSessionWins": False, + "blankPrefillIgnored": False, + } + prefill_guard = BOOT_JS.find("if(_rootPrefillNeedsFreshComposer(urlSession, savedLocal, prefillIntent)){") + saved_state_pos = BOOT_JS.find("const savedSidebarOnlyState=(!urlSession&&savedLocal)") + saved_guard = BOOT_JS.find( + "if(savedSidebarOnlyState&&savedSidebarOnlyState.sidebarOnly){", + saved_state_pos, + ) + load_pos = BOOT_JS.find("await loadSession(saved, {preserveActiveInput:true});") + assert prefill_guard >= 0 + assert saved_state_pos >= 0 + assert saved_guard > saved_state_pos + assert prefill_guard > saved_guard + assert load_pos > prefill_guard + + +def test_fresh_default_workspace_bind_skips_prefill_draft_boot(): + source = _node_prelude() + """ +(async () => { + evalBoot('_prefillHasDraftText'); + evalBoot('_maybeBindFreshDefaultWorkspaceSession'); + global.S = { session: null, _profileDefaultWorkspace: 'D:/workspace' }; + global._workspacePanelMode = 'browse'; + const calls = []; + global.newSession = async () => { calls.push('newSession'); }; + const skipped = await _maybeBindFreshDefaultWorkspaceSession({ hasText: true }); + const bound = await _maybeBindFreshDefaultWorkspaceSession({ hasText: false }); + console.log(JSON.stringify({ skipped, bound, calls })); +})().catch(err => { + console.error(err); + process.exit(1); +}); +""" + payload = json.loads(_run_node(source)) + assert payload == { + "skipped": False, + "bound": True, + "calls": ["newSession"], + } + + +def test_apply_prefill_updates_composer_without_autosending(): + source = _node_prelude() + """ +(async () => { + evalBoot('_applyComposerPrefillOnBoot'); + const counts = { autoResize: 0, updateSendBtn: 0, send: 0 }; + const msg = { value: '' }; + global.document = { + getElementById(id) { + return id === 'msg' ? msg : null; + } + }; + global.$ = (id) => document.getElementById(id); + global.autoResize = () => { counts.autoResize++; }; + global.updateSendBtn = () => { counts.updateSendBtn++; }; + global.send = async () => { counts.send++; }; + await _applyComposerPrefillOnBoot({ hasText: true, text: 'hello world', autoSend: true }); + delete global.autoResize; + await _applyComposerPrefillOnBoot({ hasText: true, text: 'second pass', autoSend: false }); + await _applyComposerPrefillOnBoot({ hasText: false, text: ' ', autoSend: true }); + console.log(JSON.stringify({ counts, value: msg.value })); +})().catch(err => { + console.error(err); + process.exit(1); +}); +""" + payload = json.loads(_run_node(source)) + assert payload["counts"] == {"autoResize": 1, "updateSendBtn": 1, "send": 0} + assert payload["value"] == "second pass" + + +def test_terminal_prefill_step_consumes_params_after_boot_state_is_ready(): + source = _node_prelude() + """ +(async () => { + evalBoot('_applyComposerPrefillOnBoot'); + evalBoot('_finalizeComposerPrefillOnBoot'); + const counts = { autoResize: 0, updateSendBtn: 0, consume: 0, send: 0 }; + const msg = { value: '' }; + global.document = { + getElementById(id) { + return id === 'msg' ? msg : null; + } + }; + global.$ = (id) => document.getElementById(id); + global.autoResize = () => { counts.autoResize++; }; + global.updateSendBtn = () => { counts.updateSendBtn++; }; + global.send = async () => { counts.send++; }; + global._consumeComposerPrefillParamsFromLocation = () => { counts.consume++; }; + await _finalizeComposerPrefillOnBoot({ hasParams: true, hasText: true, text: 'hello world', autoSend: false }); + delete global.autoResize; + await _finalizeComposerPrefillOnBoot({ hasParams: false, hasText: true, text: 'second pass', autoSend: false }); + console.log(JSON.stringify({ counts, value: msg.value })); +})().catch(err => { + console.error(err); + process.exit(1); +}); +""" + payload = json.loads(_run_node(source)) + assert payload["counts"] == { + "autoResize": 1, + "updateSendBtn": 1, + "consume": 1, + "send": 0, + } + assert payload["value"] == "second pass" + + +def test_explicit_session_url_keeps_target_session_with_prefill(): + source = _node_prelude() + """ +global.window = { + location: { + pathname: '/session/url-target', + search: '?q=hello%20world&send=1', + hash: '' + } +}; +evalSession('_sessionIdFromLocation'); +evalBoot('_prefillHasDraftText'); +evalBoot('_rootPrefillNeedsFreshComposer'); +console.log(JSON.stringify({ + urlSession: _sessionIdFromLocation(), + bypassRootOverride: _rootPrefillNeedsFreshComposer(_sessionIdFromLocation(), 'saved-local', { hasText: true }) +})); +""" + payload = json.loads(_run_node(source)) + assert payload == {"urlSession": "url-target", "bypassRootOverride": False} + query_source = _node_prelude() + """ +global.window = { + location: { + pathname: '/', + search: '?session_id=query-target&q=hello%20world&send=1', + hash: '' + } +}; +evalSession('_sessionIdFromLocation'); +evalBoot('_prefillHasDraftText'); +evalBoot('_rootPrefillNeedsFreshComposer'); +console.log(JSON.stringify({ + urlSession: _sessionIdFromLocation(), + bypassRootOverride: _rootPrefillNeedsFreshComposer(_sessionIdFromLocation(), 'saved-local', { hasText: true }) +})); +""" + query_payload = json.loads(_run_node(query_source)) + assert query_payload == {"urlSession": "query-target", "bypassRootOverride": False} + prefill_pos = BOOT_JS.find( + "const prefillIntent=(typeof _composerPrefillIntentFromLocation==='function')?_composerPrefillIntentFromLocation():null;" + ) + first_await_pos = BOOT_JS.find("const s=await api('/api/settings');", prefill_pos) + active_profile_pos = BOOT_JS.find( + "const activeProfileState = await _resolveActiveProfileBootstrapState();", + first_await_pos, + ) + new_pos = BOOT_JS.find("await newSession(true);", active_profile_pos) + load_pos = BOOT_JS.find("await loadSession(saved, {preserveActiveInput:true});", active_profile_pos) + saved_pos = BOOT_JS.find("const saved=urlSession||savedLocal;") + check_pos = BOOT_JS.find("await checkInflightOnBoot(saved);", load_pos) + apply_pos = BOOT_JS.find("await _finalizeComposerPrefillOnBoot(prefillIntent);", check_pos) + assert saved_pos >= 0 + assert prefill_pos >= 0 + assert active_profile_pos > first_await_pos + assert "_consumeComposerPrefillParamsFromLocation();" not in BOOT_JS[prefill_pos:first_await_pos] + assert 0 <= active_profile_pos < new_pos + assert 0 <= active_profile_pos < load_pos + assert 0 <= check_pos < apply_pos + assert BOOT_JS.find("await _maybeBindFreshDefaultWorkspaceSession(prefillIntent);", prefill_pos) > prefill_pos + zero_message_pos = BOOT_JS.find( + "await renderSessionList();await _finalizeComposerPrefillOnBoot(prefillIntent);if(typeof startGatewaySSE==='function')startGatewaySSE();", + load_pos, + ) + assert zero_message_pos > load_pos diff --git a/tests/test_issue1488_composer_voice_buttons.py b/tests/test_issue1488_composer_voice_buttons.py index 7cd02c2f..134b0990 100644 --- a/tests/test_issue1488_composer_voice_buttons.py +++ b/tests/test_issue1488_composer_voice_buttons.py @@ -232,6 +232,31 @@ class TestVoiceModePreferenceGate: "so the button appears/disappears immediately." +class TestVoiceModeRuntimePreferences: + """Voice mode runtime prefs must stay wired to localStorage-backed settings.""" + + def test_voice_mode_silence_pref_reads_localstorage_with_floor(self): + """boot.js must keep the silence timeout configurable without allowing + tiny or invalid values to auto-send instantly.""" + src = _src("boot.js") + assert "localStorage.getItem('hermes-voice-silence-ms')" in src, ( + "voice mode must read hermes-voice-silence-ms from localStorage " + "so pause timing survives reloads." + ) + assert "Math.max(200,_silenceMsRaw)" in src, ( + "voice mode must floor the configured silence timeout at 200ms " + "instead of trusting tiny values." + ) + + def test_voice_mode_continuous_pref_reads_localstorage(self): + """boot.js must preserve the continuous-recognition preference across reloads.""" + src = _src("boot.js") + assert "localStorage.getItem('hermes-voice-continuous')==='true'" in src, ( + "voice mode must read hermes-voice-continuous from localStorage " + "instead of hardcoding continuous recognition off." + ) + + class TestActiveStateTooltips: """When recording / in voice mode, tooltips should flip to the 'stop' variants so the affordance is honest.""" diff --git a/tests/test_issue4877_fresh_blank_boot_workspace_bind.py b/tests/test_issue4877_fresh_blank_boot_workspace_bind.py index 7917f3ac..e15b0bee 100644 --- a/tests/test_issue4877_fresh_blank_boot_workspace_bind.py +++ b/tests/test_issue4877_fresh_blank_boot_workspace_bind.py @@ -20,6 +20,9 @@ node_test = pytest.mark.skipif(NODE is None, reason="node not on PATH") def _extract_async_function(source: str, name: str) -> str: marker = f"async function {name}(" start = source.find(marker) + if start < 0: + marker = f"function {name}(" + start = source.find(marker) assert start >= 0, f"{name}() function must exist" brace = source.find("{", source.find(")", start)) assert brace > start, f"{name}() function body must start" @@ -79,6 +82,7 @@ def _run_node(script: str) -> dict: def _helper_driver(panel_mode: str, default_workspace: str | None, *, reject: bool = False) -> dict: + helper_dep = _extract_async_function(BOOT_JS.read_text(encoding="utf-8"), "_prefillHasDraftText") helper = _extract_async_function(BOOT_JS.read_text(encoding="utf-8"), "_maybeBindFreshDefaultWorkspaceSession") default_workspace_repr = json.dumps(default_workspace) script = textwrap.dedent( @@ -94,6 +98,7 @@ def _helper_driver(panel_mode: str, default_workspace: str | None, *, reject: bo calls.push({{arg0:a,arg1:b}}); return shouldReject ? Promise.reject(new Error('bind-failed')) : Promise.resolve({{}}); }} + {helper_dep} {helper} _maybeBindFreshDefaultWorkspaceSession().then((bound)=>{{ process.stdout.write(JSON.stringify({{bound,calls}})); @@ -150,7 +155,7 @@ def test_no_saved_session_branch_restores_panel_pref_before_bind_attempt(): fresh_pref = "if(_freshPanelPref&&!_isCompactWorkspaceViewport()) _workspacePanelMode='browse';" pref_idx = segment.find(fresh_pref) assert pref_idx >= 0, "no-saved-session path must restore panel preference" - bind_call = "await _maybeBindFreshDefaultWorkspaceSession();" + bind_call = "await _maybeBindFreshDefaultWorkspaceSession(prefillIntent);" bind_idx = segment.find(bind_call) assert bind_idx >= 0, "no-saved-session path must attempt to bind after pref restore" assert pref_idx < bind_idx, "panel preference restoration must happen before bind attempt" @@ -167,7 +172,7 @@ def test_ephemeral_blank_session_branch_restores_panel_pref_before_bind_attempt( eph_pref = "if(_ephPanelPref&&!_isCompactWorkspaceViewport()) _workspacePanelMode='browse';" pref_idx = segment.find(eph_pref) assert pref_idx >= 0, "ephemeral blank-session path must restore panel preference" - bind_call = "await _maybeBindFreshDefaultWorkspaceSession();" + bind_call = "await _maybeBindFreshDefaultWorkspaceSession(prefillIntent);" bind_idx = segment.find(bind_call) assert bind_idx >= 0, "ephemeral blank-session path must attempt to bind before returning" assert pref_idx < bind_idx, "ephemeral panel preference restoration must happen before bind attempt" diff --git a/tests/test_new_chat_default_model_frontend.py b/tests/test_new_chat_default_model_frontend.py index da960125..e46ffb65 100644 --- a/tests/test_new_chat_default_model_frontend.py +++ b/tests/test_new_chat_default_model_frontend.py @@ -60,7 +60,8 @@ def test_hard_refresh_hydrates_saved_session_model_before_revealing_model_chip() boot_js = Path("static/boot.js").read_text(encoding="utf-8") load_marker = "await loadSession(saved, {preserveActiveInput:true});" assert load_marker in boot_js - saved_restore = boot_js[boot_js.index(load_marker) : boot_js.index("await checkInflightOnBoot(saved);return;", boot_js.index(load_marker))] + restore_end = "await checkInflightOnBoot(saved);" + saved_restore = boot_js[boot_js.index(load_marker) : boot_js.index(restore_end, boot_js.index(load_marker))] assert "await _startBootModelDropdown();" in saved_restore assert saved_restore.index("await _startBootModelDropdown();") > saved_restore.index(load_marker) assert saved_restore.index("await _startBootModelDropdown();") < saved_restore.index("S._bootReady=true;"), (