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>
93 lines
4.2 KiB
Python
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)"
|
|
)
|