diff --git a/tests/test_action_menu_order.py b/tests/test_action_menu_order.py index c4e957054..84472662e 100644 --- a/tests/test_action_menu_order.py +++ b/tests/test_action_menu_order.py @@ -161,13 +161,13 @@ def test_email_settings_show_away_account_and_compact_display_controls() -> None assert "enabled ? 'Show' : 'Hide'" in source assert "email-settings-inline-link" in source assert "email-auto-reply-exclude" not in source - assert "_emailWritingStyleHtml(writingStyle) + _emailDisplaySettingsHtml()" in source + assert "_emailWritingStyleHtml(writingStyle) + _emailDisplaySettingsHtml(cfg)" in source assert ".email-settings-status.is-success" in style assert "var(--color-success, #4caf50)" in style assert ".email-style-settings-extract svg" in style assert "export async function mountEmailSettings(host)" in source assert "_openGlobalEmailSettings('show-tags')" in source - assert source.count('class="admin-card email-settings-section') == 4 + assert source.count('class="admin-card email-settings-section') == 5 assert 'id="settings-email-default-card"' in (ROOT / "static/index.html").read_text(encoding="utf-8") assert "multipleAccounts" in source diff --git a/tests/test_builtin_themes_static.py b/tests/test_builtin_themes_static.py index dde1514fd..f1fbcee60 100644 --- a/tests/test_builtin_themes_static.py +++ b/tests/test_builtin_themes_static.py @@ -8,7 +8,7 @@ THEME_JS = (ROOT / "static/js/theme.js").read_text(encoding="utf-8") def test_five_distinct_builtin_themes_are_available() -> None: expected = { - "eclipse": "constellations", + "eclipse": "starfield-depth", "porcelain": "dots", "arcade": "synapse", "blueprint": "dots", diff --git a/tests/test_chat_ttft_timer_static.py b/tests/test_chat_ttft_timer_static.py index d5d28b430..303f66699 100644 --- a/tests/test_chat_ttft_timer_static.py +++ b/tests/test_chat_ttft_timer_static.py @@ -25,8 +25,9 @@ def test_measured_ttft_is_shown_in_message_stats(): def test_compact_footer_and_details_show_real_performance_counters(): assert "`${Number(tps).toFixed(2)} tok/s`" in RENDERER - assert "`${Number(ttft).toFixed(3)}s TTFT`" in RENDERER - assert "`${Number(injectedTokens).toLocaleString()} in`" in RENDERER + assert "const visibleTtft = metrics.client_ttft ?? metrics.time_to_first_token" in RENDERER + assert "${Number(visibleTtft).toFixed(3)}s" in RENDERER + assert "${Number(injectedTokens).toLocaleString()}" in RENDERER assert 'Input' in RENDERER assert 'Injected' in RENDERER assert 'all rounds' not in RENDERER diff --git a/tests/test_clawmm_r47_malformed_write_body.py b/tests/test_clawmm_r47_malformed_write_body.py index ea2b36ee8..00a35b800 100644 --- a/tests/test_clawmm_r47_malformed_write_body.py +++ b/tests/test_clawmm_r47_malformed_write_body.py @@ -87,7 +87,7 @@ async def test_malformed_text_artifact_write_uses_one_bounded_raw_body_handoff(m events = [json.loads(chunk[6:]) for chunk in raw if '[DONE]' not in chunk] assert len(requests) == 2 assert 'tools' not in requests[1] - assert requests[1]['max_tokens'] == 4096 + assert requests[1]['max_tokens'] == 8192 assert len(executed) == 1 assert executed[0].tool_type == 'write_file' assert executed[0].content == ( diff --git a/tests/test_compare_mobile_tabs_static.py b/tests/test_compare_mobile_tabs_static.py index 15d8366ce..14270050b 100644 --- a/tests/test_compare_mobile_tabs_static.py +++ b/tests/test_compare_mobile_tabs_static.py @@ -12,7 +12,7 @@ def test_mobile_compare_mounts_accessible_tabs_without_removing_panes(): index = _read("static/js/compare/index.js") panes = _read("static/js/compare/panes.js") - assert "mountMobilePaneTabs(container, grid)" in index + assert "mountMobilePaneTabs(container, grid, (anchor) => _addPane(anchor))" in index assert "role', 'tablist'" in panes assert "role', 'tab'" in panes assert "role', 'tabpanel'" in panes diff --git a/tests/test_docs_no_orphan_images.py b/tests/test_docs_no_orphan_images.py index f6ed43560..02e0c4494 100644 --- a/tests/test_docs_no_orphan_images.py +++ b/tests/test_docs_no_orphan_images.py @@ -83,7 +83,8 @@ def test_pages_site_owns_its_entrypoint_and_media(): assert REPO / "website/index.html" in website_files assert REPO / "docs/index.html" not in docs_files - assert not [p for p in docs_files if p.suffix.lower() in VIDEO_EXTS | {".md"}] + assert not [p for p in docs_files if p.suffix.lower() in VIDEO_EXTS] + assert not [p for p in docs_files if p.name in PUBLIC_GUIDES] website_paths = {p.relative_to(REPO / "website").as_posix() for p in website_files} assert PUBLIC_GUIDES <= website_paths diff --git a/tests/test_document_library_expanded_export.py b/tests/test_document_library_expanded_export.py index 812048752..9d9fc5ecc 100644 --- a/tests/test_document_library_expanded_export.py +++ b/tests/test_document_library_expanded_export.py @@ -18,7 +18,7 @@ def test_expanded_document_card_has_export_beside_clone(): def test_expanded_export_reuses_download_function_without_proxy_click(): - assert "const exportDocumentFile = async () =>" in DOC_LIBRARY_JS + assert "const exportDocumentFile = async (format = 'original') =>" in DOC_LIBRARY_JS assert "await exportDocumentFile();" in DOC_LIBRARY_JS assert "exportItem.click();" not in DOC_LIBRARY_JS assert "exportItem.type = 'button';" in DOC_LIBRARY_JS diff --git a/tests/test_document_library_mobile_footer.py b/tests/test_document_library_mobile_footer.py index 186d50be1..e690ecc30 100644 --- a/tests/test_document_library_mobile_footer.py +++ b/tests/test_document_library_mobile_footer.py @@ -10,24 +10,26 @@ SOURCE = (ROOT / "static/js/documentLibrary.js").read_text(encoding="utf-8") STYLE = (ROOT / "static/style.css").read_text(encoding="utf-8") -def test_mobile_footer_exposes_open_and_more_only(): +def test_mobile_footer_exposes_delete_open_and_more(): assert "doclib-expanded-open-btn" in SOURCE assert "doclib-expanded-mobile-more" in SOURCE assert "label: 'Open in new chat'" in SOURCE assert "'Open in original' : 'Open document'" in SOURCE - assert "label: 'Export file'" in SOURCE + assert "label: 'Export file ›'" in SOURCE + assert "label: 'Original format'" in SOURCE + assert "label: 'Markdown (.md)'" in SOURCE assert "'Restore document' : 'Archive document'" in SOURCE assert "label: 'Delete document'" in SOURCE - mobile_css = STYLE.split("The Documents preview footer only exposes Open and More", 1)[1] + mobile_css = STYLE.split("On phones, keep Delete explicit", 1)[1] mobile_css = mobile_css.split("/* Chat top bar", 1)[0] for hidden_action in ( - ".doclib-expanded-delete-btn", ".doclib-expanded-archive-btn", ".doclib-expanded-clone-btn", ".doclib-expanded-export-btn", ): assert hidden_action in mobile_css + assert ".doclib-expanded-delete-btn {\n display: inline-flex !important" in mobile_css assert ".doclib-expanded-mobile-more" in mobile_css assert "display: inline-flex" in mobile_css assert "box-sizing: border-box" in mobile_css diff --git a/tests/test_editor_canvas_navigation_js.py b/tests/test_editor_canvas_navigation_js.py index e61eaee05..f20568cbf 100644 --- a/tests/test_editor_canvas_navigation_js.py +++ b/tests/test_editor_canvas_navigation_js.py @@ -91,13 +91,15 @@ def test_apply_pan_updates_state_canvas_overlay_and_cursor_classes(): def test_hand_navigation_is_wired_to_toolbar_keyboard_and_offline_graph(): toolbar = (ROOT / "static/js/editor/build/toolbar.js").read_text(encoding="utf-8") + shortcuts = (ROOT / "static/js/editor/tool-shortcuts.js").read_text(encoding="utf-8") keyboard = (ROOT / "static/js/editor/keyboard-shortcuts.js").read_text(encoding="utf-8") events = (ROOT / "static/js/editor/canvas-events.js").read_text(encoding="utf-8") editor = (ROOT / "static/js/galleryEditor.js").read_text(encoding="utf-8") service_worker = (ROOT / "static/sw.js").read_text(encoding="utf-8") assert "{ id: 'hand', label: 'Hand'" in toolbar - assert "key: 'H'" in toolbar + assert "t.key = TOOL_SHORTCUTS[t.id]" in toolbar + assert "hand: 'H'" in shortcuts assert "e.code === 'Space'" in keyboard assert "setTemporaryPan?.(true)" in keyboard assert "isDirectPanIntent(state.tool, state.spacePanActive" in events diff --git a/tests/test_editor_layer_thumbnail_render.py b/tests/test_editor_layer_thumbnail_render.py index 3af077197..aa9e283e1 100644 --- a/tests/test_editor_layer_thumbnail_render.py +++ b/tests/test_editor_layer_thumbnail_render.py @@ -20,8 +20,8 @@ def test_hover_thumbnail_uses_rendered_layer_output(): def test_inline_layer_rows_share_the_common_thumbnail_renderer(): panel = (ROOT / "static/js/editor/layer-panel.js").read_text() - assert "const previewCanvas = renderLayer?.(layer) || layer.canvas;" in panel - assert "const thumb = createInlineThumbnail(previewCanvas, `${layer.name} preview`);" in panel + assert "const thumb = createInlineThumbnail(() => renderLayer?.(layer) || layer.canvas, `${layer.name} preview`);" in panel + assert "thumb._refreshPreview = draw;" in panel assert "thumbCtx.fillStyle = ((x / tile + y / tile) & 1)" not in panel diff --git a/tests/test_editor_text_layers_js.py b/tests/test_editor_text_layers_js.py index 9cd5f4815..bf61dd880 100644 --- a/tests/test_editor_text_layers_js.py +++ b/tests/test_editor_text_layers_js.py @@ -119,6 +119,7 @@ def test_rasterize_text_layer_clears_retained_metadata_only_for_text(): def test_text_tool_is_retained_across_editor_subsystems(): editor = (ROOT / "static/js/galleryEditor.js").read_text(encoding="utf-8") toolbar = (ROOT / "static/js/editor/build/toolbar.js").read_text(encoding="utf-8") + shortcuts = (ROOT / "static/js/editor/tool-shortcuts.js").read_text(encoding="utf-8") controls = (ROOT / "static/js/editor/build/controls.js").read_text(encoding="utf-8") codec = (ROOT / "static/js/editor/document-codec.js").read_text(encoding="utf-8") geometry = (ROOT / "static/js/editor/document-geometry.js").read_text(encoding="utf-8") @@ -127,7 +128,8 @@ def test_text_tool_is_retained_across_editor_subsystems(): merge = (ROOT / "static/js/editor/wire-merge-buttons.js").read_text(encoding="utf-8") assert "{ id: 'text', label: 'Text'" in toolbar - assert "key: 'T'" in toolbar + assert "t.key = TOOL_SHORTCUTS[t.id]" in toolbar + assert "text: 'T'" in shortcuts assert 'id="ge-text-section"' in controls assert "if (state.tool === 'text') return _placeText(e);" in editor assert "kind: l.kind || 'raster'" in editor diff --git a/tests/test_editor_topbar_static.py b/tests/test_editor_topbar_static.py index 0f26b42d5..8f2ed3d50 100644 --- a/tests/test_editor_topbar_static.py +++ b/tests/test_editor_topbar_static.py @@ -32,15 +32,15 @@ def test_stacked_labels_share_one_size_and_position_rule(): assert "top: 2px;" in STYLE -def test_narrow_topbar_reflows_essential_actions_instead_of_clipping_them(): - assert "ge-topbar-overflow" in OVERFLOW - assert "topbar.classList.add('ge-topbar-overflow')" in OVERFLOW - assert "(max-width: 700px)" in OVERFLOW - assert ".ge-topbar.ge-topbar-overflow" in STYLE - assert "flex: 1 0 100%;" in STYLE - right_start = STYLE.index(".ge-topbar-overflow .ge-topbar-right") - overflow_right = STYLE[right_start:STYLE.index("}", right_start)] - assert "justify-content: flex-start;" in overflow_right +def test_narrow_topbar_scrolls_essential_actions_and_hides_ai_group_first(): + assert "if (topbar.scrollWidth > topbar.clientWidth)" in OVERFLOW + assert "aiGroup.forEach(el => { el.style.display = 'none'; });" in OVERFLOW + assert "new ResizeObserver(() => syncOverflow())" in OVERFLOW + mobile = STYLE[STYLE.index("/* Editor topbar — scrolls horizontally") :] + topbar = mobile[mobile.index(".ge-topbar {") : mobile.index("}", mobile.index(".ge-topbar {"))] + assert "overflow-x: auto;" in topbar + assert "flex-wrap: nowrap;" in topbar + assert "justify-content: flex-start;" in topbar def test_desktop_tool_rail_keeps_long_tool_names_readable(): diff --git a/tests/test_email_attachment_search.py b/tests/test_email_attachment_search.py index 98dc39f78..02470334b 100644 --- a/tests/test_email_attachment_search.py +++ b/tests/test_email_attachment_search.py @@ -160,8 +160,10 @@ def test_attachment_cache_backfill_preserves_message_id(tmp_path, monkeypatch): def test_single_email_tag_has_no_more_control(): library = open("static/js/emailLibrary.js", encoding="utf-8").read() - - assert 'class="email-tags-more email-tags-more-single"' not in library + group = library[library.index("function _emailTagGroupHtml("):library.index("function _fitEmailCardTags(")] + assert "if (visible.length === 1) return visible[0];" in group + assert "if (visible.length === 2) return visible.join('');" in group + assert group.index("if (visible.length === 2)") < group.index("email-tags-more-single") def test_email_folder_and_filter_pickers_treat_their_buttons_as_inside_clicks(): @@ -181,7 +183,7 @@ def test_empty_reply_has_two_editable_rows_and_reply_survives_compact_toolbar(): fit_start = library.index("function _fitReaderActions") fit_end = library.index("const _readerActionFitObserver", fit_start) fit = library[fit_start:fit_end] - assert '[data-act="ai-reply"], [data-act="reply-all"]' in fit + assert '[data-act="reply-all"], [data-act="forward"]' in fit assert '[data-act="reply"]' not in fit diff --git a/tests/test_email_folder_chip_static.py b/tests/test_email_folder_chip_static.py index 41f6d0a3b..193cfa6aa 100644 --- a/tests/test_email_folder_chip_static.py +++ b/tests/test_email_folder_chip_static.py @@ -16,7 +16,7 @@ def test_folder_chip_stays_with_date_and_moves_down(): folder_rule_start = css.index(".email-folder-chip {", first_folder_rule + 1) folder_rule = css[folder_rule_start:][:220] assert "position: relative;" in folder_rule - assert "top: 2px;" in folder_rule + assert "top: 4px;" in folder_rule group_rule = css[css.index(".email-meta-date-group {"):][:180] assert "display: inline-flex;" in group_rule assert "white-space: nowrap;" in group_rule diff --git a/tests/test_email_open_dedup_js.py b/tests/test_email_open_dedup_js.py index b5fea966a..9e5bd9f1c 100644 --- a/tests/test_email_open_dedup_js.py +++ b/tests/test_email_open_dedup_js.py @@ -127,15 +127,16 @@ def test_attachment_warning_only_checks_authored_reply_text(): assert "text.split(/^>|^On .* wrote:/m)" not in helper -def test_email_send_saves_recovery_draft_before_send_and_closes_only_on_success(): +def test_email_send_saves_recovery_draft_before_send_and_retains_it_on_failure(): source = (_REPO / "static/js/document.js").read_text(encoding="utf-8") send = source[source.index("async function _sendEmail"):source.index("\n\n async function _saveDraft", source.index("async function _sendEmail"))] assert "async function _saveEmailDraftForRecovery" in source assert send.index("_saveEmailDraftForRecovery({") < send.index("fetch(`${API_BASE}/api/email/send`") + assert send.index("if (isLibraryOpen()) closeLibrary();") > send.index("const sendRequest = fetch(") + assert send.index("if (isLibraryOpen()) closeLibrary();") < send.index("const res = await sendRequest") success_branch = send[send.index("if (data.success) {"):] - assert "closeLibrary()" in success_branch - assert send.index("closeLibrary()") > send.index("const res = await sendRequest") + assert "fetch(`${API_BASE}/api/document/${sendDocId}`, { method: 'DELETE' })" in success_branch assert "Draft kept in Drafts." in send assert "recoveryDraft.draft_uid" in send diff --git a/tests/test_email_picker_exclusivity_static.py b/tests/test_email_picker_exclusivity_static.py index 18f217cfa..365514f43 100644 --- a/tests/test_email_picker_exclusivity_static.py +++ b/tests/test_email_picker_exclusivity_static.py @@ -10,8 +10,11 @@ def test_email_folder_and_filter_pickers_are_exclusive_and_escape_safe(): assert source.count("const filterMenu = document.getElementById('email-filter-menu');") == 1 assert source.count("const folderMenu = document.getElementById('email-folder-menu');") == 1 - assert source.count("e.stopImmediatePropagation();") >= 2 - assert "_wireEmailPickerEscapeGuard();" in source - assert "window.addEventListener('keydown'" in source + assert source.count("e.stopImmediatePropagation?.();") >= 2 + assert "filterMenu?._dismiss?.();" in source + assert "folderMenu?._dismiss?.();" in source + escape_handler = source[source.index("state._libInnerEscHandler = (e) => {"):source.index("window.addEventListener('keydown', state._libInnerEscHandler, true)")] + assert "if (dismissTopMenu()) {" in escape_handler + assert "window.addEventListener('keydown', state._libInnerEscHandler, true)" in source inbox = (ROOT / "static/js/emailInbox.js").read_text(encoding="utf-8") assert re.search(r"from './emailLibrary\.js\?v=[A-Za-z0-9_-]+'", inbox) diff --git a/tests/test_harness_version.py b/tests/test_harness_version.py index d83c5fff0..999491e8f 100644 --- a/tests/test_harness_version.py +++ b/tests/test_harness_version.py @@ -5,10 +5,9 @@ import re ROOT = Path(__file__).resolve().parents[1] -def test_original_harness_version_is_canonical_semver() -> None: +def test_harness_version_is_canonical_semver() -> None: version = (ROOT / "HARNESS_VERSION").read_text(encoding="utf-8").strip() - assert version == "0.20.18" assert re.fullmatch(r"\d+\.\d+\.\d+", version) diff --git a/tests/test_live_thinking_chat_integration.py b/tests/test_live_thinking_chat_integration.py index ca6e1c864..e8636d310 100644 --- a/tests/test_live_thinking_chat_integration.py +++ b/tests/test_live_thinking_chat_integration.py @@ -145,15 +145,15 @@ def test_final_response_skips_duplicate_end_of_stream_render(): "// --- Final render (skip if stream was ever backgrounded or currently in background) ---", "} // end if (!_isBgFinal)", ) - assert "if (!terminalFinalResponseRendered) _renderStream();" in post_loop + assert "if (!terminalFinalResponseRendered && !_turnRendering.isVisible(roundHolder.querySelector('.body'), _terminalAnswerHtml(roundText, roundHolder.querySelector('.body')))) _renderStream();" in post_loop assert "const finalDisplay = terminalFinalResponseRendered ? '' : _streamDisplayText" in post_loop def test_only_rich_completed_turns_reload_saved_canonical_assistant_row(): assert "if (_canonicalTerminalSaved && _savedAssistantMessageId && !_pendingContinue)" not in _CHAT assert "if (!_pendingContinue)" in _CHAT - assert "fall back to its latest" in _CHAT - assert ": [...history].reverse().find(msg => msg && msg.role === 'assistant');" in _CHAT + assert "if (!streamSessionId || !_savedAssistantMessageId) return false;" in _CHAT + assert "String(msg.metadata?._db_id || '') === _savedAssistantMessageId" in _CHAT assert "const _needsCanonicalTurnRebuild" in _CHAT assert "lastToolThread" in _CHAT assert "_streamTurnMarker.remove();" in _CHAT diff --git a/tests/test_llm_core_ollama_thinking.py b/tests/test_llm_core_ollama_thinking.py index d7d1485ec..305cd6ac8 100644 --- a/tests/test_llm_core_ollama_thinking.py +++ b/tests/test_llm_core_ollama_thinking.py @@ -172,4 +172,4 @@ class TestThinkSuppression: "qwen35-9b-tool-router-v4-firstaction-noschema-adapter", ) assert "think" not in payload - assert payload["max_tokens"] == 1024 + assert payload["max_tokens"] == llm_core.LLMConfig.DEFAULT_MAX_TOKENS diff --git a/tests/test_notes_mobile_checklist_toggle_static.py b/tests/test_notes_mobile_checklist_toggle_static.py index 1ff792f38..eb8dfdaa9 100644 --- a/tests/test_notes_mobile_checklist_toggle_static.py +++ b/tests/test_notes_mobile_checklist_toggle_static.py @@ -87,6 +87,12 @@ def test_mobile_bulk_select_long_press_is_shared_across_card_types(): assert ".skill-card[data-skill-name]" in helper assert ".task-card[data-id]" in helper assert "const HOLD_MS = 450" in helper - assert "body:has(#memory-select-btn.active)" in css - assert "body:has(#skills-select-btn.active)" in css - assert "body:has(#tasks-select-btn.active)" in css + shared_buttons = ( + "#memory-select-btn,", "#skills-select-btn,", + "#notes-select-btn,", "#tasks-select-btn,", + ) + shared_css = css.split("/* Shared bulk-selection trigger.", 1)[1] + selectors = re.findall(r":is\(([^)]*)\)(?:\.active)?::before", shared_css) + assert len(selectors) == 2 + for selector in selectors: + assert all(button in selector for button in shared_buttons) diff --git a/tests/test_notes_sidebar_new_note.py b/tests/test_notes_sidebar_new_note.py index c60bbefb2..6caf9f9a3 100644 --- a/tests/test_notes_sidebar_new_note.py +++ b/tests/test_notes_sidebar_new_note.py @@ -9,7 +9,7 @@ def test_notes_sidebar_new_action_matches_library_structure_and_animation(): css = (ROOT / "static/style.css").read_text(encoding="utf-8") assert 'class="list-item-plus-btn sidebar-new-item-btn" id="notes-new-note-btn"' in html - assert 'note' in html + assert 'new' in html[html.index('id="notes-new-note-btn"'):] assert "#tool-notes-btn:hover #notes-new-note-btn" in css assert "#notes-new-note-btn:hover svg" in css @@ -72,7 +72,7 @@ def test_notes_body_and_tag_arrow_are_nudged_down(): assert css.count("padding: 10px 8px 8px") >= 2 assert ".notes-pane-body .doclib-chip-scroll-arrow.right" in css - assert "top: calc(50% - 2px) !important" in css + assert "top: calc(50% - 6px) !important" in css[css.index(".notes-pane-body .doclib-chip-scroll-arrow.right"):] def test_notes_tag_strip_cannot_grow_into_blank_space(): diff --git a/tests/test_pr6020_rebase_regressions.py b/tests/test_pr6020_rebase_regressions.py index cacd0ebc0..c46669a27 100644 --- a/tests/test_pr6020_rebase_regressions.py +++ b/tests/test_pr6020_rebase_regressions.py @@ -15,6 +15,11 @@ NOTES_TOOLS = { "ask_user", "update_plan", } +GENERAL_COMPACT_TOOLS = {"ask_user", "web_search", "web_fetch"} +CALENDAR_COMPACT_TOOLS = { + "ask_user", "bash", "get_workspace", "manage_calendar", "manage_notes", + "python", "read_file", "web_fetch", "web_search", +} def _collect(generator): @@ -102,9 +107,7 @@ def test_odysseus_notes_mode_clamps_without_overriding_caller_denials(monkeypatc assert {"manage_notes", "manage_calendar", "manage_tasks"} <= route["disabled_tools"] -def test_odysseus_general_mode_disables_every_tool(monkeypatch): - from src.tool_policy import known_tool_names - +def test_odysseus_general_mode_uses_compact_core(monkeypatch): prompt_calls, _ = _install_route_probe(monkeypatch) _run_probe( @@ -113,11 +116,10 @@ def test_odysseus_general_mode_disables_every_tool(monkeypatch): ) route = prompt_calls[0] - assert route["relevant_tools"] == set() - assert known_tool_names() <= route["disabled_tools"] + assert route["relevant_tools"] == GENERAL_COMPACT_TOOLS -def test_odysseus_calendar_intent_uses_notes_mode(monkeypatch): +def test_odysseus_calendar_intent_uses_compact_calendar_route(monkeypatch): prompt_calls, _ = _install_route_probe(monkeypatch) _run_probe( @@ -125,10 +127,10 @@ def test_odysseus_calendar_intent_uses_notes_mode(monkeypatch): relevant_tools={"manage_notes", "manage_calendar", "manage_tasks", "bash"}, ) - assert prompt_calls[0]["relevant_tools"] == NOTES_TOOLS + assert prompt_calls[0]["relevant_tools"] == CALENDAR_COMPACT_TOOLS -def test_odysseus_calendar_followup_keeps_notes_mode(monkeypatch): +def test_odysseus_calendar_followup_keeps_compact_calendar_route(monkeypatch): prompt_calls, _ = _install_route_probe(monkeypatch) messages = [ {"role": "user", "content": "Add lunch tomorrow to my calendar."}, @@ -153,7 +155,7 @@ def test_odysseus_calendar_followup_keeps_notes_mode(monkeypatch): relevant_tools={"manage_notes", "manage_calendar", "manage_tasks", "bash"}, ) - assert prompt_calls[0]["relevant_tools"] == NOTES_TOOLS + assert prompt_calls[0]["relevant_tools"] == CALENDAR_COMPACT_TOOLS def test_agent_route_passes_workspace_to_system_prompt(monkeypatch): @@ -169,7 +171,7 @@ def test_agent_route_passes_workspace_to_system_prompt(monkeypatch): assert prompt_calls[0]["workspace"] == "/tmp/example-repo" -def test_odysseus_qwen_temperature_is_capped_for_agent_requests(monkeypatch): +def test_odysseus_compact_primary_uses_deterministic_temperature(monkeypatch): _, stream_calls = _install_route_probe(monkeypatch) _run_probe( @@ -178,7 +180,7 @@ def test_odysseus_qwen_temperature_is_capped_for_agent_requests(monkeypatch): temperature=1.2, ) - assert stream_calls[0]["temperature"] == 0.2 + assert stream_calls[0]["temperature"] == 0.0 def test_qwen_fallback_candidate_gets_capped_temperature(monkeypatch): @@ -214,7 +216,7 @@ def test_non_qwen_fallback_keeps_requested_temperature(monkeypatch): fallbacks=[("https://backup.example/v1", "gpt-4o", {})], ) - assert stream_calls[0]["temperature"] == 0.2 + assert stream_calls[0]["temperature"] == 0.0 factory = stream_calls[0]["candidate_request_factory"] request = asyncio.run(factory(1, "https://backup.example/v1", "gpt-4o", {})) assert request["kwargs"]["temperature"] == 1.2 diff --git a/tests/test_research_history_expansion.py b/tests/test_research_history_expansion.py index 948dbdaca..d1c692f17 100644 --- a/tests/test_research_history_expansion.py +++ b/tests/test_research_history_expansion.py @@ -1,6 +1,7 @@ """Regression coverage for full-height Deep Research history expansion.""" from pathlib import Path +import re ROOT = Path(__file__).resolve().parent.parent @@ -11,7 +12,11 @@ def test_history_list_participates_in_shared_library_expansion_layout(): css = (ROOT / "static/style.css").read_text(encoding="utf-8") assert 'id="research-past-list" class="doclib-grid memory-list research-jobs-list"' in panel - assert "width:min(560px, 90vw);max-height:78vh" in panel + card = css[css.index("#research-pane .research-history-card {"):] + card = card[:card.index("}")] + assert "flex: 1 1 0;" in card + assert "min-height: 0;" in card + assert "overflow: hidden;" in card assert 'class="memory-tab-panel research-tab-panel"' in panel assert 'class="admin-card research-new-job"' in panel assert 'id="research-history-filters" class="skills-summary-strip"' in panel @@ -21,7 +26,11 @@ def test_history_list_participates_in_shared_library_expansion_layout(): assert "pane.classList.toggle('research-results-view', tab !== 'research')" in panel assert "display: flex; flex-direction: column; gap: 10px;" in css assert ".research-tabs {\n flex: 0 0 auto;\n margin: -4px -4px 0;" in css - assert "font-size:11px; letter-spacing:0;" in css + title = css[css.index("#research-pane .research-history-title-row h2 {"):] + title = title[:title.index("}")] + assert "letter-spacing: 0;" in title + desc = css[css.index("#research-pane .research-history-desc {"):] + assert "font-size: 11px;" in desc[:desc.index("}")] assert "font: inherit; font-size: 12px; cursor: pointer; text-align: left;" in css assert "#research-pane .research-history-search-wrap .memory-search-input" in css assert "height: 30px;" in css @@ -65,8 +74,8 @@ def test_research_panel_uses_one_versioned_module_instance(): renderer = (ROOT / "static/js/chatRenderer.js").read_text(encoding="utf-8") index = (ROOT / "static/index.html").read_text(encoding="utf-8") - version = "20260902researchhistoryalign34" - assert f"research/panel.js?v={version}" in app - assert renderer.count(f"research/panel.js?v={version}") == 2 - assert f"style.css?v={version}" in index - assert f"app.js?v={version}" in index + panel_version = re.search(r"research/panel\.js\?v=([^'\"]+)", app) + asset_version = re.search(r"/static/app\.js\?v=([^'\"]+)", index) + style_version = re.search(r"/static/style\.css\?v=([^'\"]+)", index) + assert panel_version and renderer.count(f"research/panel.js?v={panel_version.group(1)}") == 2 + assert asset_version and style_version and asset_version.group(1) == style_version.group(1) diff --git a/tests/test_research_panel_ui.py b/tests/test_research_panel_ui.py index 0cfa5a4e3..304b273a7 100644 --- a/tests/test_research_panel_ui.py +++ b/tests/test_research_panel_ui.py @@ -25,18 +25,18 @@ def test_research_settings_use_custom_pickers_and_shared_provider_icons(): assert "provider === 'google' ? 'google_pse'" in icons first_picker_offset = style.split(".research-setting:has(#research-rounds),", 1)[1].split("}", 1)[0] assert ".research-setting:has(#research-category)" in first_picker_offset - assert "top: -4px;" in first_picker_offset + assert "top: 0;" in first_picker_offset def test_completed_research_keeps_primary_and_utility_actions_visible_without_format_icon(): panel = (ROOT / "static/js/research/panel.js").read_text(encoding="utf-8") assert "research-job-format-icon" not in panel - assert "Visual Report" in panel - assert "Discuss" in panel - assert 'data-action="copy" title="Copy report to clipboard"' in panel - assert 'data-action="dismiss" title="Clear from list"' in panel - assert 'data-action="delete" title="Delete from disk"' in panel + assert 'data-action="report" title="Open visual report"' in panel + assert 'data-action="chat" title="Open follow-up chat with this research as context"' in panel + assert "{ action: 'copy', icon: _copyIcon, label: 'Copy report' }" in panel + assert "{ action: 'dismiss', icon: _cancelIcon, label: 'Hide from list' }" in panel + assert "{ action: 'delete', icon: _trashIcon, label: 'Delete from disk', danger: true }" in panel def test_research_format_survives_live_and_reconnected_jobs(): diff --git a/tests/test_tasks_completed_default_static.py b/tests/test_tasks_completed_default_static.py index ae37bdedc..44ae48fac 100644 --- a/tests/test_tasks_completed_default_static.py +++ b/tests/test_tasks_completed_default_static.py @@ -40,7 +40,8 @@ def test_tasks_filter_chips_include_active_paused_switch(): assert "mkChip(`active (${activeCount})`, 'active', _taskStatusFilter === 'active', 'status');" in src assert "mkChip(`paused (${pausedCount})`, 'paused', _taskStatusFilter === 'paused', 'status');" in src assert "if (_taskStatusFilter && String(t.status || '').toLowerCase() !== _taskStatusFilter) return false;" in src - assert "if (value === null) _taskStatusFilter = null;" in src + assert "_taskStatusFilter = _taskStatusFilter === value ? null : value;" in src + assert "_taskStatusFilter = null;" in src[src.index("if (kind === 'status')"):src.index("_renderList();", src.index("if (kind === 'status')"))] def test_tasks_completed_view_exposes_active_paused_shortcuts(): diff --git a/tests/test_tool_approval_frontend_routing.py b/tests/test_tool_approval_frontend_routing.py index ecf7f3b33..8b2f66d9a 100644 --- a/tests/test_tool_approval_frontend_routing.py +++ b/tests/test_tool_approval_frontend_routing.py @@ -155,12 +155,12 @@ def test_every_changed_approval_module_is_cache_busted_together(): assert found, f"missing cache-busted reference for {module_name}" assert len(set(found)) == 1, f"split module graph for {module_name}: {found}" - # These shared modules are imported throughout the graph. Keep their URL - # canonical and unversioned; mixing a query URL with plain relative imports - # creates a second singleton with separate state and listeners. - for module_name in ("sessions.js", "ui.js", "memory.js", "markdown.js", "models.js"): + # Shared modules must have one URL apiece. ui.js is consistently versioned + # throughout the graph; the other shared modules remain unversioned. + for module_name in ("sessions.js", "memory.js", "markdown.js", "models.js"): assert any(module_name in source for source in sources) assert not versions(module_name), f"split module graph for {module_name}" + assert len(set(versions("ui.js"))) == 1 compare_stream = (root / "static/js/compare/stream.js").read_text(encoding="utf-8") compare_vote = (root / "static/js/compare/vote.js").read_text(encoding="utf-8")