test(baseline): align UI and model profile expectations

This commit is contained in:
Alexandre Teixeira
2026-09-22 23:27:17 +01:00
parent 9bc1cdcab0
commit c0a1cecc00
26 changed files with 108 additions and 77 deletions
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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",
+3 -2
View File
@@ -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 '<span class="ctx-label">Input</span>' in RENDERER
assert '<span class="ctx-label">Injected</span>' in RENDERER
assert 'all rounds' not in RENDERER
@@ -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 == (
+1 -1
View File
@@ -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
+2 -1
View File
@@ -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
@@ -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
+6 -4
View File
@@ -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
+3 -1
View File
@@ -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
+2 -2
View File
@@ -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
+3 -1
View File
@@ -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
+9 -9
View File
@@ -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():
+5 -3
View File
@@ -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
+1 -1
View File
@@ -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
+4 -3
View File
@@ -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
@@ -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)
+1 -2
View File
@@ -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)
+3 -3
View File
@@ -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
+1 -1
View File
@@ -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
@@ -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)
+2 -2
View File
@@ -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 '<span class="list-item-plus-label">note</span>' in html
assert '<span class="list-item-plus-label">new</span>' 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():
+14 -12
View File
@@ -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
+16 -7
View File
@@ -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)
+6 -6
View File
@@ -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():
+2 -1
View File
@@ -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():
+4 -4
View File
@@ -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")