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>
This commit is contained in:
parent
561369a261
commit
856aa1d48d
2 changed files with 106 additions and 2 deletions
|
|
@ -4614,7 +4614,14 @@ function _setMemoryHeaderButtons(mode) {
|
|||
const cancelBtn = $('btnCancelMemoryDetail');
|
||||
const saveBtn = $('btnSaveMemoryDetail');
|
||||
const meta = _memorySectionMeta(_currentMemorySection);
|
||||
if (mode === 'read' && _currentMemorySection !== 'external_notes' && !meta.readOnly) { if (header) header.style.display = 'flex'; show(editBtn); hide(cancelBtn); hide(saveBtn); }
|
||||
if (mode === 'read') {
|
||||
// Any read view has a populated title → header must be visible. Only the
|
||||
// Edit affordance is gated on the section being editable (read-only
|
||||
// sections like Project Context / External Notes still show the header).
|
||||
if (header) header.style.display = 'flex';
|
||||
if (_currentMemorySection !== 'external_notes' && !meta.readOnly) show(editBtn); else hide(editBtn);
|
||||
hide(cancelBtn); hide(saveBtn);
|
||||
}
|
||||
else if (mode === 'edit') { if (header) header.style.display = 'flex'; hide(editBtn); show(cancelBtn); show(saveBtn); }
|
||||
else { if (header) header.style.display = 'none'; hide(editBtn); hide(cancelBtn); hide(saveBtn); }
|
||||
}
|
||||
|
|
@ -5788,7 +5795,7 @@ function _renderProfileConceptHelp(activeName){
|
|||
if (empty) empty.style.display = 'none';
|
||||
_profileMode = 'read';
|
||||
_currentProfileDetail = null;
|
||||
_setProfileHeaderButtons('empty');
|
||||
_setProfileHeaderButtons('help');
|
||||
}
|
||||
|
||||
function _renderProfileDetail(p, activeName){
|
||||
|
|
@ -5848,6 +5855,11 @@ function _setProfileHeaderButtons(mode, p, activeName){
|
|||
} else if (mode === 'create') {
|
||||
if (header) header.style.display = 'flex';
|
||||
hide(actBtn); hide(delBtn); show(cancelBtn); show(saveBtn);
|
||||
} else if (mode === 'help') {
|
||||
// Read-only help/concept view: title is populated, so show the header but
|
||||
// hide every action button (no profile to act on).
|
||||
if (header) header.style.display = 'flex';
|
||||
[actBtn, delBtn, cancelBtn, saveBtn].forEach(hide);
|
||||
} else {
|
||||
if (header) header.style.display = 'none';
|
||||
[actBtn, delBtn, cancelBtn, saveBtn].forEach(hide);
|
||||
|
|
|
|||
92
tests/test_issue4891_detail_header_visibility.py
Normal file
92
tests/test_issue4891_detail_header_visibility.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
"""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)"
|
||||
)
|
||||
Loading…
Reference in a new issue