"""Regression tests for #1694 approval/clarify prompt ownership. Prompt state belongs to the session that owns the running stream. A background session's approval/clarify event must not render over or hide the currently active pane's card, but the pending prompt should remain available when the user switches back to that session. """ from pathlib import Path REPO_ROOT = Path(__file__).parent.parent MESSAGES_JS = (REPO_ROOT / "static" / "messages.js").read_text(encoding="utf-8") SESSIONS_JS = (REPO_ROOT / "static" / "sessions.js").read_text(encoding="utf-8") def _body_from_brace(src: str, brace: int, label: str) -> str: assert brace >= 0, f"body opening brace not found for: {label}" depth = 1 i = brace + 1 while i < len(src) and depth: ch = src[i] if ch == "{": depth += 1 elif ch == "}": depth -= 1 i += 1 assert depth == 0, f"body did not close for: {label}" return src[brace + 1 : i - 1] def _brace_body_after(src: str, marker: str) -> str: start = src.find(marker) assert start >= 0, f"marker not found: {marker}" brace = src.find("{", start) return _body_from_brace(src, brace, marker) def _function_body(src: str, name: str) -> str: marker = f"function {name}(" start = src.find(marker) assert start >= 0, f"function not found: {name}" signature_end = src.find(")", start) assert signature_end >= 0, f"function signature not found: {name}" brace = src.find("{", signature_end) return _body_from_brace(src, brace, name) def _event_body(event_name: str) -> str: return _brace_body_after(MESSAGES_JS, f"source.addEventListener('{event_name}'") def test_stream_prompt_events_use_session_owned_show_helpers(): """Background stream prompts should be cached by owner before pane render.""" approval_body = _event_body("approval") clarify_body = _event_body("clarify") assert "showApprovalForSession(activeSid" in approval_body assert "showApprovalCard(d, 1)" not in approval_body assert "showClarifyForSession(activeSid" in clarify_body assert "showClarifyCard(d)" not in clarify_body def test_approval_card_render_is_gated_to_active_session_and_cached(): body = _function_body(MESSAGES_JS, "showApprovalCard") assert "_rememberApprovalPending(" in body assert "_approvalPromptBelongsToActiveSession(sid)" in body assert "syncTopbar()" in body assert "return;" in body assert "let _approvalPendingBySession" in MESSAGES_JS assert "function activeSessionHasPendingPromptAttention()" in MESSAGES_JS assert "function _renderPendingPromptsForActiveSession()" in MESSAGES_JS def test_clarify_card_render_is_gated_to_active_session_and_cached(): body = _function_body(MESSAGES_JS, "showClarifyCard") assert "_rememberClarifyPending(" in body assert "_clarifyPromptBelongsToActiveSession(sid)" in body assert "syncTopbar()" in body assert "return;" in body assert "let _clarifyPendingBySession" in MESSAGES_JS assert "function _renderPendingPromptsForActiveSession()" in MESSAGES_JS def test_polling_empty_state_clears_only_the_owner_prompt(): approval_poll = _function_body(MESSAGES_JS, "startApprovalPolling") approval_fallback = _function_body(MESSAGES_JS, "_startApprovalFallbackPoll") clarify_poll = _function_body(MESSAGES_JS, "startClarifyPolling") clarify_fallback = _function_body(MESSAGES_JS, "_startClarifyFallbackPoll") combined = "\n".join([approval_poll, approval_fallback, clarify_poll, clarify_fallback]) assert "_clearApprovalPendingForSession(sid)" in combined assert "_hideApprovalCardIfOwner(sid" in combined assert "_clearClarifyPendingForSession(sid)" in combined assert "_hideClarifyCardIfOwner(sid" in combined assert "else { hideApprovalCard(); }" not in combined assert "else { hideClarifyCard(false, 'expired'); }" not in combined assert "stopApprovalPolling(); hideApprovalCard(true); return;" not in combined assert "stopClarifyPolling(); hideClarifyCard(true, 'session'); return;" not in combined def test_load_session_rerenders_cached_prompt_for_new_active_session(): body = _function_body(SESSIONS_JS, "loadSession") assert "_renderPendingPromptsForActiveSession();" in body def test_prompt_rerender_hides_previous_session_cards_without_clearing_cache(): approval_body = _function_body(MESSAGES_JS, "_renderPendingApprovalForActiveSession") clarify_body = _function_body(MESSAGES_JS, "_renderPendingClarifyForActiveSession") rerender_body = _function_body(MESSAGES_JS, "_renderPendingPromptsForActiveSession") clear_approval_body = _function_body(MESSAGES_JS, "_clearApprovalPendingForSession") clear_clarify_body = _function_body(MESSAGES_JS, "_clearClarifyPendingForSession") assert "_approvalSessionId && _approvalSessionId !== sid" in approval_body assert "hideApprovalCard(true)" in approval_body assert "_clarifySessionId && _clarifySessionId !== sid" in clarify_body assert "hideClarifyCard(true, 'session')" in clarify_body assert "syncTopbar()" in rerender_body assert "activeSessionHasPendingPromptAttention()" in rerender_body assert "if (sid) {" in clear_approval_body assert "syncTopbar()" in clear_approval_body assert "if (sid) {" in clear_clarify_body assert "syncTopbar()" in clear_clarify_body