Files
hermes-webui/tests/test_live_activity_timeline.py
T
nesquena-hermesandnesquena-hermes 0b5458f3da Release v0.51.226 — Release GT (stage-p9 — mobile composer context-usage ring #3062 + activity-feed default-expand setting #3080) (#3459)
* feat(composer): replace mobile config-button sliders icon with context-usage ring (#3062, @NottheGuy007)

Squashed net diff of #3062 (the PR branch's tip commits were deletes of files not
present in our tree). Replaces the composerMobileCtxBadge text badge with an SVG
progress ring (ctx-arc + ctx-num) showing real-time context-window usage: ring
fill via stroke-dashoffset, centered percentage, color-coded green<=50%
orange<=85% red>85%. Ring resets to 0%/green on new session.

* feat(activity): add 'expand activity feed by default' appearance setting (#3080, @AJV20)

Squashed net diff of #3080. Adds a Settings -> Appearance checkbox
(activity_feed_expanded_default, default off) to expand new Activity disclosures
by default; preserves manual per-turn collapse/expand (explicit toggle still
wins); live 'Waiting on model' rows explain what the agent is doing before/after
tool calls. i18n keys for all locales.

* docs(changelog): v0.51.226 — context-usage ring (#3062) + activity-feed default-expand setting (#3080)

* test(mobile): update touch-target test for #3062 ring (badge removed)

#3062 replaced the composerMobileCtxBadge text badge with the SVG context-usage
ring (composerMobileCtxRing) but left 3 stale references in tests/test_mobile_layout.py.
The full suite caught test_mobile_composer_primary_controls_keep_touch_friendly_sizing
asserting the removed .composer-mobile-ctx-badge CSS rule + #composerMobileCtxBadge
element. Updated the assertion to the new ring: confirm composerMobileCtxRing exists,
the old badge is fully gone (not dangling), and the ring SVG is aria-hidden so it
stays a decorative overlay that doesn't steal the config button's 44px touch target
(which is still asserted via .composer-mobile-config-btn above). 56/56 mobile-layout
tests pass. Codex: no production JS dereferences the removed badge.

---------

Co-authored-by: nesquena-hermes <[email protected]>
2026-06-02 19:48:01 -07:00

80 lines
3.8 KiB
Python

"""Regression coverage for live Activity timeline UX.
The live Activity disclosure should surface observable run telemetry instead of a
blank Thinking placeholder while preserving the quiet tool/thinking metadata
family.
"""
import pathlib
REPO = pathlib.Path(__file__).parent.parent
UI_JS = (REPO / "static" / "ui.js").read_text(encoding="utf-8")
MESSAGES_JS = (REPO / "static" / "messages.js").read_text(encoding="utf-8")
STYLE_CSS = (REPO / "static" / "style.css").read_text(encoding="utf-8")
def test_live_activity_group_has_observable_baseline_events():
assert "function _ensureLiveActivityBaseline(group)" in UI_JS
assert "Run started" in UI_JS
assert "Observable activity will appear here as the agent works." in UI_JS
assert "Model: ${modelLabel}" in UI_JS
assert "_ensureLiveActivityBaseline(group);" in UI_JS
def test_empty_thinking_placeholder_becomes_status_row_not_raw_thinking_card():
assert "data-activity-event-id=\"thinking-placeholder\"" in UI_JS
assert "Starting agent" in UI_JS
assert "Creating the stream and sending your message…" in UI_JS
assert "Waiting for first model token" in UI_JS
assert "Stream connected; no model output has arrived yet." in UI_JS
assert "Waiting on model" in UI_JS
assert "Reviewing the prompt and context, then choosing the next action or composing the response." in UI_JS
assert "Reviewing prompt and context" in UI_JS
assert "Waiting on tool result" in UI_JS
assert "Last step: ${action} (${toolName}); now choosing the next action or composing a response." in UI_JS
assert "_thinkingActivityNode(thinkingText, false)" in UI_JS
def test_stream_start_refreshes_waiting_status_after_stream_id_arrives():
active_idx = MESSAGES_JS.find("S.activeStreamId = streamId;")
assert active_idx != -1
refresh_idx = MESSAGES_JS.find("appendThinking('',{pending:true})", active_idx)
attach_idx = MESSAGES_JS.find("attachLiveStream(activeSid, streamId, uploadedNames);", active_idx)
assert refresh_idx != -1
assert attach_idx != -1
assert refresh_idx < attach_idx
def test_activity_feed_default_expand_setting_is_wired():
index_html = (REPO / "static" / "index.html").read_text(encoding="utf-8")
panels_js = (REPO / "static" / "panels.js").read_text(encoding="utf-8")
boot_js = (REPO / "static" / "boot.js").read_text(encoding="utf-8")
config_py = (REPO / "api" / "config.py").read_text(encoding="utf-8")
assert 'id="settingsActivityFeedExpandedDefault"' in index_html
assert "settings_label_activity_feed_expanded_default" in index_html
assert '"activity_feed_expanded_default": False' in config_py
assert "activity_feed_expanded_default" in panels_js
assert "window._activityFeedExpandedDefault=!!s.activity_feed_expanded_default;" in boot_js
assert "if(window._activityFeedExpandedDefault===true) collapsed=false;" in UI_JS
finalize_fn = UI_JS.split("function finalizeThinkingCard")[1].split("\nfunction ")[0]
assert "_activityFeedExpandedDefault" in finalize_fn
assert "_liveActivityUserExpanded !== false" in finalize_fn
def test_tool_events_update_activity_timeline_and_summary():
assert "Tool finished: ${toolName}" in UI_JS
assert "Running tool: ${toolName}" in UI_JS
assert "No recent activity for ${_formatActiveElapsedTimer(idleAge)}" in UI_JS
assert "Activity · Running" in UI_JS
assert "Working for ${label}" in UI_JS
def test_activity_status_rows_have_quiet_metadata_styling():
assert ".agent-activity-status{" in STYLE_CSS
assert "grid-template-columns:18px minmax(0,1fr) auto" in STYLE_CSS
assert ".agent-activity-status-detail" in STYLE_CSS
assert ".agent-activity-status-time" in STYLE_CSS
assert ".agent-activity-status-error .agent-activity-status-label{color:var(--error);}" in STYLE_CSS