Files
hermes-webui/tests/test_issue4891_detail_header_visibility.py
T
nesquena-hermesandluandnh 856aa1d48d fix(#4891): show header in read-only Memory sections + Profiles help view
Gate (Codex) caught two SILENT regressions in the original fix: explicitly
hiding the header in the 'else'/'empty' branches also hid populated read views.
- _setMemoryHeaderButtons('read') now shows the header for ALL read views
  (incl. read-only Project Context / External Notes), gating only the Edit btn.
- _renderProfileConceptHelp() now uses a new 'help' header mode (shows header,
  hides all action buttons) instead of 'empty' (which hid the help title).
Adds tests/test_issue4891_detail_header_visibility.py (proven non-vacuous).

Co-authored-by: luandnh <luandnh@users.noreply.github.com>
2026-06-25 06:20:20 +00:00

93 lines
4.2 KiB
Python

"""Regression guard for #4891: detail-panel headers must stay visible in read
modes that have a populated title.
#4891 fixed task-detail action buttons being invisible on mobile PWA (the
`:has(.main-view-title:empty)` pseudo-class didn't re-evaluate after dynamic
textContent on some WebViews) by explicitly toggling `header.style.display` in
the per-panel header-button setters. The first cut of that fix introduced two
SILENT regressions (caught by the gate): read-only Memory sections (Project
Context / External Notes) and the Profiles "Profiles vs workspaces" help view
both have a populated title but fell into an `else`/`empty` branch that set the
header to `display:none`, hiding their header+title.
This guards the invariant: any setter branch that corresponds to a populated
read/help view must show the header (flex), never hide it.
"""
from __future__ import annotations
import re
from pathlib import Path
PANELS = (Path(__file__).resolve().parent.parent / "static" / "panels.js").read_text(encoding="utf-8")
def _fn_body(name: str) -> str:
i = PANELS.find(f"function {name}(")
assert i != -1, f"{name} not found in panels.js"
# crude brace matcher from the first '{' after the signature
start = PANELS.index("{", i)
depth = 0
for j in range(start, len(PANELS)):
if PANELS[j] == "{":
depth += 1
elif PANELS[j] == "}":
depth -= 1
if depth == 0:
return PANELS[start : j + 1]
raise AssertionError(f"unbalanced braces in {name}")
def test_memory_read_mode_shows_header_even_for_readonly_sections():
"""_setMemoryHeaderButtons('read') must show the header for ALL read views
(incl. read-only Project Context / External Notes), only gating the Edit
button on editability — not hide the whole header (#4891)."""
body = _fn_body("_setMemoryHeaderButtons")
# The 'read' branch must set the header to flex unconditionally (the title
# is populated in every read view).
m = re.search(r"if\s*\(\s*mode\s*===\s*'read'\s*\)\s*\{", body)
assert m, "_setMemoryHeaderButtons must have a dedicated mode==='read' branch"
read_branch = body[m.end(): m.end() + 400]
assert "header.style.display = 'flex'" in read_branch, (
"memory 'read' branch must show the header (flex) so read-only sections "
"keep their header/title (#4891 regression)"
)
# The read-only gating must apply to the Edit button, not the whole header.
assert "external_notes" in read_branch and "readOnly" in read_branch, (
"memory 'read' branch must gate only the Edit button on read-only/"
"external_notes, not the header visibility (#4891)"
)
def test_profile_help_view_keeps_header_visible():
"""The 'Profiles vs workspaces' help view sets a title, so it must use a
header mode that shows the header — not the 'empty' mode that hides it."""
help_body = _fn_body("_renderProfileConceptHelp")
assert "_setProfileHeaderButtons('empty')" not in help_body, (
"profile help view must NOT call _setProfileHeaderButtons('empty') — "
"that hides the populated help title (#4891 regression)"
)
assert "_setProfileHeaderButtons('help')" in help_body, (
"profile help view must use the 'help' header mode (shows header, hides "
"action buttons) (#4891)"
)
setter = _fn_body("_setProfileHeaderButtons")
m = re.search(r"mode\s*===\s*'help'\s*\)\s*\{", setter)
assert m, "_setProfileHeaderButtons must handle a 'help' mode"
help_branch = setter[m.end(): m.end() + 240]
assert "header.style.display = 'flex'" in help_branch, (
"'help' header mode must show the header (flex) (#4891)"
)
def test_task_detail_header_shown_in_read_and_edit():
"""The original #4891 fix: task-detail header is explicitly shown in read/
create/edit and hidden only in the closed/empty state."""
body = _fn_body("_setCronHeaderButtons")
assert body.count("header.style.display = 'flex'") >= 2, (
"_setCronHeaderButtons must explicitly show the header in read and "
"create/edit modes (#4891)"
)
assert "header.style.display = 'none'" in body, (
"_setCronHeaderButtons must hide the header in the closed/empty state (#4891)"
)