phase: 99_kb_tree_table_and_back_nav
All verification is complete and green. Final report: **Phase 99 — final verification pass (all 3 tasks already in `complete/`)** - Verified the one-line Description clamp: `.kb-desc-cell` flex wrapper + `.kb-desc-text` ellipsis triad + `nowrap` column + in-cell Edit button + hover `title` + untouched level block (source diffs match D1; unit pins present) - Verified the back-button breadcrumb nav: `applyTarget(target, push)` state-only `pushState`, no-duplicate gate, popstate adopt/reset, `bor:view-refresh` alignment before `loadTree()`, anonymous gate listener-free, router.js untouched (D2) - `uv run pytest --cov=app`: **2180 passed**, coverage **99%** (>90% gate) - `uv run ruff check .`: clean; `uv run pyright`: **0 errors, 0 warnings** - `uv run pytest tests/e2e/test_kb_tree_nav.py -v --no-cov` (isolated, DB up): **4/4 passed** (back-walk, jump-then-back, fresh-nav/active-reclick, measured clamp incl. row-height parity ±4px, computed styles, full text in DOM/title/level block) - Regression suites isolated: `test_kb_tree.py` 8✓, `test_ls_tree_drilldown.py` 3✓, `test_navbar_refresh.py` 7✓, plus `test_sync_summary_visibility.py` 3✓ (marker pin updated to `kb-desc-text kb-summary-pending` — the sanctioned class-pair change) and `test_edit_summaries.py` 4✓ - Criteria 1–4: **met** (each as above). Criterion 5 (atomic commit): left to the harness per executor rules — no `git add`/`commit` run; all changes left in the working tree - No defects found in prior phases; no deviations. Stray `__pycache__/test_zz_smoke_tree_ui.*.pyc` is a leftover (no `.py` on disk) — inert, not touched - Next pending phase: **none** — `todo/` will be empty once the harness moves this phase
This commit is contained in:
+326
-20
@@ -357,16 +357,40 @@ def test_breadcrumb_links_go_up_and_the_top_reset() -> None:
|
||||
assert "span.textContent = label" in cur
|
||||
|
||||
|
||||
def test_navigation_is_client_side_only() -> None:
|
||||
"""goTo() is the ONLY navigation: set `current`, re-render. No
|
||||
fetch (the tree is already in memory), no URL change (history is
|
||||
untouched — the deep-link contract of the shell is unchanged)."""
|
||||
def test_drill_navigation_pushes_state_only_entries() -> None:
|
||||
"""Phase 99 (task 02, D2): every user-initiated drill is a history
|
||||
entry — ``goTo`` is the push variant of ``applyTarget``: when the
|
||||
target DIFFERS from the current level (a re-click of the current
|
||||
row/segment pushes NO duplicate entry), it FIRST records a
|
||||
STATE-ONLY entry — ``history.pushState({ view: "rag", kb: target },
|
||||
"")`` — the second arg is ``""`` and there is no third (the URL
|
||||
stays the shell's pathname: no new route, the phase-76 deep-link
|
||||
surface is untouched) — then sets ``current`` + re-renders. The
|
||||
drill still never fetches (the tree is in memory) and the module
|
||||
never ``replaceState``s."""
|
||||
js = _js()
|
||||
body = js[js.find("function goTo(") : js.find("function makeSourceRow(")]
|
||||
assert "current = { source: target.source, folder: target.folder };" in body
|
||||
assert "renderLevel();" in body
|
||||
start = js.find("function applyTarget(")
|
||||
assert start != -1, "applyTarget must exist (the two-step drill)"
|
||||
body = js[start : js.find("function makeSourceRow(")]
|
||||
gate_i = body.find(
|
||||
"if (push && (target.source !== current.source || target.folder !== current.folder)) {"
|
||||
)
|
||||
push_i = body.find('history.pushState({ view: "rag", kb: target }, "")')
|
||||
set_i = body.find("current = { source: target.source, folder: target.folder };")
|
||||
render_i = body.find("renderLevel();")
|
||||
assert -1 < gate_i < push_i < set_i < render_i, (
|
||||
"push gate (target differs from current) → pushState → set current → render"
|
||||
)
|
||||
# STATE-ONLY: exactly ONE pushState call site — the "" second arg,
|
||||
# no third (no URL change).
|
||||
assert _code(body).count("pushState") == 1, (
|
||||
"ONE pushState call site — the state-only drill entry"
|
||||
)
|
||||
assert "replaceState" not in _code(js), "no replaceState anywhere in the module"
|
||||
# goTo is the push variant — every drill call site keeps calling it.
|
||||
go = body[body.find("function goTo(") :]
|
||||
assert "applyTarget(target, true)" in go, "goTo pushes (the user-initiated drill)"
|
||||
assert "fetch(" not in body, "the drill never fetches (client-side only)"
|
||||
assert "pushState" not in body and "replaceState" not in body, "no URL change"
|
||||
|
||||
|
||||
# ---------- the level rendering ----------
|
||||
@@ -577,16 +601,30 @@ def test_load_tree_wired_at_exactly_the_three_refresh_points_plus_boot() -> None
|
||||
assert _code(js).count("loadTree()") == 5, (
|
||||
"the definition + the 4 call sites, no others (code, not comments)"
|
||||
)
|
||||
# 1. the view-refresh listener, armed in the admin branch.
|
||||
# 1. the view-refresh listener, armed in the admin branch — since
|
||||
# Phase 99 (task 02, D2) its body is the ALIGNMENT step (a
|
||||
# kb-carrying history.state entry keeps the drill — the
|
||||
# active-link re-click; a kb-less entry starts at the top — a
|
||||
# fresh nav visit) BEFORE the re-fetch.
|
||||
listener = js.find('addEventListener("bor:view-refresh"')
|
||||
assert listener != -1
|
||||
gate = js.find("const admin = await fetchIsAdmin();")
|
||||
assert -1 < gate < listener, "armed after the whoami gate (the phase-77 pin)"
|
||||
assert "() => loadTree()" in js[listener : listener + 120]
|
||||
listener_body = js[listener : js.find('window.addEventListener("popstate"')]
|
||||
state_i = listener_body.find("history.state && history.state.kb")
|
||||
adopt_i = listener_body.find("applyTarget(kb, false)")
|
||||
reset_i = listener_body.find("applyTarget({ source: null, folder: null }, false)")
|
||||
load_i = listener_body.find("loadTree();")
|
||||
assert -1 < state_i < adopt_i < reset_i < load_i, (
|
||||
"align with history.state (adopt kb / reset to top) BEFORE the re-fetch"
|
||||
)
|
||||
assert "pushState" not in _code(listener_body), ("the alignment adopts only — no push")
|
||||
# 2. the boot load — the last statement of mount, right after the
|
||||
# listener is armed (the mount's own load is the first fetch).
|
||||
assert 'root.addEventListener("bor:view-refresh", () => loadTree());\n loadTree();' in js, (
|
||||
"the boot load follows the listener"
|
||||
# listeners are armed (the mount's own load is the first fetch).
|
||||
pop = js.find('window.addEventListener("popstate"')
|
||||
boot = js[js.find("});", pop) + 3 :] # past the popstate listener's close
|
||||
assert boot.strip().startswith("loadTree();"), (
|
||||
"the boot load follows the listeners (no pushState on boot)"
|
||||
)
|
||||
# 3. applySyncSuccess (the sync's terminal — the KB just changed).
|
||||
success = js[js.find("function applySyncSuccess(") : js.find("function applySyncFailure(")]
|
||||
@@ -746,13 +784,16 @@ def test_row_description_cell_builds_text_and_always_present_edit() -> None:
|
||||
the text gates on the node state, the button never does). The
|
||||
button is a real type=button with a human aria-label, and the
|
||||
shared editor is wired with a CONSTANT target { node, source,
|
||||
folder }."""
|
||||
folder }. Phase 99 (task 01, D1): text + button live in ONE
|
||||
`div.kb-desc-cell` flex wrapper (the <td> holds only the wrapper),
|
||||
and the editor's container IS the wrapper (the open/close swaps
|
||||
fill it — the cell layout survives the editor swap)."""
|
||||
body = _fn(_js(), "makeDescCell")
|
||||
# The Edit button is built unconditionally — the whole button
|
||||
# block (from its creation to the append) carries no `if` gate on
|
||||
# the stored description or the pending flag.
|
||||
btn_block = body[
|
||||
body.find("const btn = document.createElement") : body.find("td.append(text, btn)")
|
||||
body.find("const btn = document.createElement") : body.find("wrap.append(text, btn)")
|
||||
]
|
||||
assert "if (" not in btn_block, (
|
||||
"always present: no gate on the stored description"
|
||||
@@ -761,10 +802,20 @@ def test_row_description_cell_builds_text_and_always_present_edit() -> None:
|
||||
assert 'btn.className = "kb-summary-edit"' in body
|
||||
assert 'btn.textContent = "Edit"' in body
|
||||
assert 'btn.setAttribute("aria-label", `Edit description: ${label}`)' in body
|
||||
assert "td.append(text, btn)" in body
|
||||
assert 'wrap.className = "kb-desc-cell"' in body, "the ONE flex wrapper"
|
||||
assert "wrap.append(text, btn)" in body, "text + button, in order, in the wrapper"
|
||||
assert "td.append(wrap)" in body, "the <td> holds the wrapper (no new td class)"
|
||||
assert "container: wrap" in body, (
|
||||
"the editor's container is the WRAPPER (Phase 99 — the swap fills it)"
|
||||
)
|
||||
assert "getTarget: () => ({ node, source, folder })" in body, (
|
||||
"a row's target is a constant (its own node)"
|
||||
)
|
||||
# The level block's editor is UNTOUCHED — its container is still
|
||||
# .kb-level-body (the D1 escape hatch keeps its full text).
|
||||
js = _js()
|
||||
wire = js[js.find("levelEditor = wireDescriptionEdit({") : js.find("/* ---------- view boot")]
|
||||
assert "container: levelBody" in wire
|
||||
|
||||
|
||||
def test_desc_cell_pending_marker_branch() -> None:
|
||||
@@ -786,7 +837,12 @@ def test_desc_cell_pending_marker_branch() -> None:
|
||||
assert "kb-summary-pending" not in stored, "a stored summary is NEVER the marker"
|
||||
assert "Summary pending" not in stored
|
||||
pending = body[pending_i:empty_i]
|
||||
assert 'text.className = "kb-summary-pending"' in pending
|
||||
assert 'text.classList.add("kb-summary-pending")' in pending, (
|
||||
"Phase 99: the marker TOGGLES onto the .kb-desc-text base class"
|
||||
)
|
||||
assert 'text.className = "kb-summary-pending"' not in body, (
|
||||
"a bare className re-assignment would drop the clamp's base class"
|
||||
)
|
||||
assert 'text.textContent = "Summary pending"' in pending, "the D4 marker copy"
|
||||
assert (
|
||||
'text.title = "No stored description yet — the next sync will generate one."'
|
||||
@@ -841,13 +897,23 @@ def test_close_editor_rederives_the_pending_display() -> None:
|
||||
close = body[body.find("function closeEditor(") : body.find("function openEditor()")]
|
||||
flag_i = close.find('const pending = node !== null && stored === "" && node.summary_pending;')
|
||||
value_i = close.find("const value = pending && pendingText ? pendingText : stored;")
|
||||
class_i = close.find('textEl.className = pending ? "kb-summary-pending" : "";')
|
||||
class_i = close.find('textEl.classList.toggle("kb-summary-pending", pending);')
|
||||
title_i = close.find("if (pendingTitle) textEl.title = pendingTitle;")
|
||||
hover_i = close.find('} else if (stored && textEl.classList.contains("kb-desc-text")) {')
|
||||
full_i = close.find("textEl.title = stored;")
|
||||
clear_title_i = close.find('textEl.removeAttribute("title")')
|
||||
render_i = close.find("textEl.textContent = value")
|
||||
assert -1 < flag_i < value_i < class_i < title_i < clear_title_i < render_i, (
|
||||
"flag → value → class → title (set/clear) → text, in order"
|
||||
assert -1 < flag_i < value_i < class_i < title_i < hover_i < full_i, (
|
||||
"flag → value → class toggle → title (pending / hover-full), in order"
|
||||
)
|
||||
assert full_i < clear_title_i < render_i, "title-set → title-clear → text, in order"
|
||||
# Phase 99 (task 01): the class line is a TOGGLE (the base classes
|
||||
# survive — the row's span keeps .kb-desc-text, the clamp; the
|
||||
# level's <p> keeps none), and the CLAMPED row span's title is
|
||||
# re-derived from the stored text (the D1 hover escape hatch — a
|
||||
# stale pre-edit title cannot survive a save; the unclamped
|
||||
# level's <p> is gated out by the kb-desc-text check).
|
||||
assert 'textEl.className' not in close, "no bare className re-assignment"
|
||||
# The row passes its marker copy + the EXACT D4 tooltip…
|
||||
cell = _fn(_js(), "makeDescCell")
|
||||
assert 'pendingText: "Summary pending"' in cell
|
||||
@@ -1212,3 +1278,243 @@ def test_module_docstring_documents_the_pending_markers() -> None:
|
||||
in header
|
||||
), "the level block's pending note"
|
||||
assert "node.summary_pending = false" in header, "the in-place clear"
|
||||
|
||||
|
||||
# ---------- the one-line Description clamp (phase 99, task 01) ----------
|
||||
# D1: the clamp is VISUAL only — the row's Description cell is ONE flex
|
||||
# row (text flexes + ellipsizes, the Edit button stays fixed), the full
|
||||
# text stays in the DOM + on the span's hover title, and the level
|
||||
# block keeps the full unclamped description. The measured-height proof
|
||||
# lands in the phase's E2E (task 03); this module pins the structure
|
||||
# and the CSS.
|
||||
|
||||
|
||||
def _css_rule(css_text: str, selector: str) -> str:
|
||||
"""The declarations of the rule with exactly this selector (the
|
||||
task-named classes own their base rule — no compound matches)."""
|
||||
m = re.search(r"(?<![\w-])" + re.escape(selector) + r"\s*\{([^}]*)\}", css_text)
|
||||
assert m, f"styles.css must define {selector}"
|
||||
return m.group(1)
|
||||
|
||||
|
||||
def test_desc_cell_hover_title_covers_real_description_text_only() -> None:
|
||||
"""Phase 99 (task 01, D1): the text span (`.kb-desc-text` — the
|
||||
base class, set UNCONDITIONALLY before the three-state branch) AL-
|
||||
WAYS carries `title` = the FULL description text for real
|
||||
(non-empty) stored text — the hover escape hatch of the visual
|
||||
clamp; the phase-98 marker keeps its OWN D4 title (never over-
|
||||
ridden by the hover rule); the empty cell carries no title at all.
|
||||
textContent/title only — the house rule (no innerHTML)."""
|
||||
body = _fn(_js(), "makeDescCell")
|
||||
base_i = body.find('text.className = "kb-desc-text"')
|
||||
stored_i = body.find("if (node && node.summary) {")
|
||||
pending_i = body.find("else if (node && node.summary_pending) {")
|
||||
empty_i = body.find("} else {")
|
||||
assert -1 < base_i < stored_i, "the base clamp class is set before the state branch"
|
||||
stored = body[stored_i:pending_i]
|
||||
assert "text.title = node.summary;" in stored, "stored → title = the full text (hover)"
|
||||
assert "Summary pending" not in stored, "the stored branch never touches the marker"
|
||||
pending = body[pending_i:empty_i]
|
||||
assert (
|
||||
'text.title = "No stored description yet — the next sync will generate one."'
|
||||
in pending
|
||||
), "the marker keeps its OWN D4 title"
|
||||
empty = body[empty_i : empty_i + 200]
|
||||
assert "text.title" not in empty, "the empty cell carries no title"
|
||||
code = re.sub(r"//.*?$|/\*.*?\*/", "", body, flags=re.S | re.M)
|
||||
assert "innerHTML" not in code, "the house rule: no innerHTML in the cell builder"
|
||||
|
||||
|
||||
def test_close_editor_rederives_the_hover_title_from_the_stored_text() -> None:
|
||||
"""Phase 99 (task 01, D1): the hover title is the escape hatch for
|
||||
the CLAMPED text, so a Save/Cancel must re-derive it — a stale
|
||||
pre-edit title cannot survive a save (the span is re-inserted
|
||||
into the wrapper by the swap, attributes included). The re-
|
||||
derivation is gated on the kb-desc-text class: only the clamped
|
||||
row span gets the full-text title; the unclamped level's <p>
|
||||
(full text visible) keeps its no-title behavior, and an emptied
|
||||
(cleared) cell loses its title."""
|
||||
body = _fn(_js(), "wireDescriptionEdit")
|
||||
close = body[body.find("function closeEditor(") : body.find("function openEditor()")]
|
||||
gate_i = close.find('} else if (stored && textEl.classList.contains("kb-desc-text")) {')
|
||||
set_i = close.find("textEl.title = stored;")
|
||||
clear_i = close.find('textEl.removeAttribute("title")')
|
||||
assert -1 < gate_i < set_i < clear_i, "gated set (stored + clamped span) → clear, in order"
|
||||
assert close.count("textEl.title =") == 2, (
|
||||
"exactly: pendingTitle (the marker's own) + stored (the hover full text) — nothing else"
|
||||
)
|
||||
|
||||
|
||||
def test_styles_carry_the_one_line_clamp() -> None:
|
||||
"""Phase 99 (task 01, D1): the Description cell is one flex row —
|
||||
.kb-desc-cell (flex + center + the 0.4rem gap that replaces the
|
||||
button's old margin-left + min-width: 0) and .kb-desc-text (flex:
|
||||
1 1 auto + min-width: 0 — the flex item may shrink — + the
|
||||
ellipsis triad). The column itself is white-space: nowrap (was
|
||||
normal — the column width is the clamp's budget; the min/max width
|
||||
+ font-size stay). The button's margin-left rule is GONE, the
|
||||
level block's margin-top rule is untouched, and the open editor
|
||||
fills the flex wrapper (flex: 1 1 auto + min-width: 0). .kb-level
|
||||
p is UNTOUCHED (the full unclamped description — D1), and the
|
||||
phase-97 block carries no new hue (the phase-92 invariant)."""
|
||||
css = _text(STYLES_CSS)
|
||||
cell = _css_rule(css, ".kb-desc-cell")
|
||||
for prop in ("display: flex", "align-items: center", "gap: 0.4rem", "min-width: 0"):
|
||||
assert prop in cell, f".kb-desc-cell must carry {prop!r}"
|
||||
text = _css_rule(css, ".kb-desc-text")
|
||||
for prop in (
|
||||
"flex: 1 1 auto",
|
||||
"min-width: 0",
|
||||
"overflow: hidden",
|
||||
"text-overflow: ellipsis",
|
||||
"white-space: nowrap",
|
||||
):
|
||||
assert prop in text, f".kb-desc-text must carry {prop!r}"
|
||||
col_i = css.find(".kb-folders-table td:nth-child(3) {")
|
||||
assert col_i != -1
|
||||
col = css[col_i : col_i + 400]
|
||||
col = col[: col.find("\n}")]
|
||||
assert "white-space: nowrap" in col, "the column is one line (was: normal)"
|
||||
assert "white-space: normal" not in col
|
||||
for prop in ("min-width: 18rem", "max-width: 44rem", "font-size: 0.88rem"):
|
||||
assert prop in col, f"the column keeps {prop!r} (the clamp's budget)"
|
||||
assert ".kb-folders-table .kb-summary-edit" not in css, (
|
||||
"the button's margin-left is gone — the wrapper's gap replaces it"
|
||||
)
|
||||
lvl_i = css.find(".kb-level-body .kb-summary-edit")
|
||||
assert lvl_i != -1
|
||||
assert "margin-top: 0.5rem" in css[lvl_i : lvl_i + 80], (
|
||||
"the level block's spacing rule is untouched"
|
||||
)
|
||||
fill_i = css.find(".kb-desc-cell .kb-summary-editor")
|
||||
assert fill_i != -1
|
||||
fill = css[fill_i : fill_i + 80]
|
||||
assert "flex: 1 1 auto" in fill and "min-width: 0" in fill, (
|
||||
"the open editor fills the flex wrapper"
|
||||
)
|
||||
# The level block keeps the FULL unclamped description (D1) — its
|
||||
# rule is unchanged (no white-space/ellipsis additions).
|
||||
m = re.search(r"(?<![\w-])\.kb-level p\s*\{([^}]*)\}", css)
|
||||
assert m, "the level block's <p> rule must exist"
|
||||
assert "white-space" not in m.group(1) and "text-overflow" not in m.group(1)
|
||||
block = _css_block(css, "KB drill-down tree (phase 97", "Git sources page (phase 35)")
|
||||
assert ".kb-desc-cell" in block and ".kb-desc-text" in block, (
|
||||
"the clamp lives in the phase-97 tree region"
|
||||
)
|
||||
assert not re.search(r"#[0-9a-fA-F]{3,8}\b", block), "no new hue (phase-92 invariant)"
|
||||
assert not re.search(r"rgba?\(", block), "no new hue (phase-92 invariant)"
|
||||
|
||||
|
||||
def test_module_docstring_documents_the_one_line_clamp() -> None:
|
||||
"""The house per-phase module-note convention: the phase-99
|
||||
task-01 section records the flex wrapper structure (text flexes +
|
||||
ellipsizes, button fixed), the VISUAL-only clamp (full text in the
|
||||
DOM + on the hover title), the marker's own title, and the wrapper
|
||||
as the editor's container (the level block's container stays
|
||||
.kb-level-body)."""
|
||||
js = _js()
|
||||
header = js[: js.find("import { fetchIsAdmin }")]
|
||||
assert "Phase 99 (task 01, D1)" in header
|
||||
assert "kb-desc-cell" in header and "kb-desc-text" in header
|
||||
assert "VISUAL only" in header, "the clamp is documented as visual only"
|
||||
assert "title" in header, "the hover escape hatch is documented"
|
||||
assert "kb-level-body" in header, "the level block's container is documented as kept"
|
||||
|
||||
|
||||
# ---------- the history integration (phase 99, task 02, D2) ----------
|
||||
# The drill state IS the history state: every drill pushes a state-
|
||||
# only entry (no URL change), Back/Forward adopt the entry's kb (or
|
||||
# reset to the top), the re-show aligns before the re-fetch, and the
|
||||
# anonymous gate installs no listeners. The browser proof lands in
|
||||
# the phase's E2E (task 03); this module pins the wiring.
|
||||
|
||||
|
||||
def test_popstate_adopts_the_entry_kb_or_resets_to_top() -> None:
|
||||
"""Phase 99 (task 02, D2): the window popstate — armed exactly
|
||||
ONCE, in the ADMIN branch (after the whoami gate, next to the
|
||||
refresh listener) — ADOPTS the popped entry's ``event.state.kb``
|
||||
via the no-push variant (the browser owns its entries —
|
||||
adopt/reset never push), and an entry WITHOUT a kb (the boot
|
||||
entry, the router's view entries, any foreign state) RESETS the
|
||||
drill to the TOP level. Rendering a currently-hidden view is
|
||||
harmless: the router's own popstate (registered earlier, at shell
|
||||
boot) owns the view switch for foreign entries; rag-internal
|
||||
entries never change the pathname, so this listener's render is
|
||||
the visible one."""
|
||||
js = _js()
|
||||
assert js.count('window.addEventListener("popstate"') == 1, (
|
||||
"armed exactly once (mount-once — the router mounts a view ONCE)"
|
||||
)
|
||||
pop = js.find('window.addEventListener("popstate"')
|
||||
gate = js.find("const admin = await fetchIsAdmin();")
|
||||
refresh = js.find('root.addEventListener("bor:view-refresh"')
|
||||
assert -1 < gate < refresh < pop, (
|
||||
"armed in the admin branch, after the gate, next to the refresh listener"
|
||||
)
|
||||
body = js[pop : js.find("});", pop)]
|
||||
kb_i = body.find("event.state && event.state.kb")
|
||||
adopt_i = body.find("applyTarget(kb, false)")
|
||||
reset_i = body.find("applyTarget({ source: null, folder: null }, false)")
|
||||
assert -1 < kb_i < adopt_i < reset_i, (
|
||||
"read event.state.kb → adopt (no push) / reset to the top"
|
||||
)
|
||||
assert "pushState" not in _code(body), ("adopt/reset never push (the browser owns its entries)")
|
||||
|
||||
|
||||
def test_refresh_alignment_adopts_history_state_before_the_refetch() -> None:
|
||||
"""Phase 99 (task 02, D2): the bor:view-refresh listener (the
|
||||
phase-77 re-show) aligns ``current`` with ``history.state``
|
||||
BEFORE ``loadTree()`` — a kb-carrying top entry (the active-link
|
||||
re-click pushed NOTHING — the drilled entry is still on top)
|
||||
KEEPS the drill (``applyTarget(kb, false)``); a kb-less entry (a
|
||||
fresh nav visit) RESETS to the top level. The alignment adopts —
|
||||
it never pushes."""
|
||||
js = _js()
|
||||
listener = js.find('root.addEventListener("bor:view-refresh"')
|
||||
assert listener != -1
|
||||
body = js[listener : js.find("});", listener)]
|
||||
state_i = body.find("history.state && history.state.kb")
|
||||
adopt_i = body.find("applyTarget(kb, false)")
|
||||
reset_i = body.find("applyTarget({ source: null, folder: null }, false)")
|
||||
load_i = body.find("loadTree();")
|
||||
assert -1 < state_i < adopt_i < reset_i < load_i, (
|
||||
"align (adopt kb / reset to top) BEFORE the re-fetch"
|
||||
)
|
||||
assert "pushState" not in _code(body), "the alignment never pushes"
|
||||
|
||||
|
||||
def test_anonymous_branch_installs_no_navigation_listeners() -> None:
|
||||
"""Phase 99 (task 02): the anonymous gate installs NO listeners at
|
||||
all — no popstate, no refresh (the phase-16 rule: anonymous shows
|
||||
the gate, fetches nothing, and owns no drill state — both
|
||||
listeners are armed only in the admin branch)."""
|
||||
js = _js()
|
||||
start = js.find("if (!admin) {")
|
||||
end = js.find("if (gateEl) gateEl.hidden = true;")
|
||||
assert -1 < start < end, "the anonymous branch must exist"
|
||||
branch = js[start:end]
|
||||
assert "addEventListener" not in branch, (
|
||||
"the anonymous branch arms no listeners (no popstate, no refresh)"
|
||||
)
|
||||
|
||||
|
||||
def test_module_docstring_documents_the_history_integration() -> None:
|
||||
"""The house per-phase module-note convention: the phase-99
|
||||
task-02 section records the push/adopt/reset rules — the
|
||||
STATE-ONLY push (the URL STAYS PUT, no new route — the phase-76
|
||||
deep-link surface untouched), the duplicate-push skip, the
|
||||
popstate adopt/reset, the refresh alignment before the re-fetch,
|
||||
the no-boot-push contract, the UNCHANGED (no-push)
|
||||
resetVanishedLocation, and the UNTOUCHED router module."""
|
||||
js = _js()
|
||||
header = js[: js.find("import { fetchIsAdmin }")]
|
||||
assert "Phase 99 (task 02, D2)" in header
|
||||
assert "STATE-ONLY" in header, "the state-only push is documented"
|
||||
assert "STAYS PUT" in header, "the URL-stays-put note"
|
||||
assert "no duplicate history" in header, "the duplicate-push skip"
|
||||
assert "ADOPTS" in header and "popstate" in header, "the popstate adopt"
|
||||
assert "RESETS the drill to the TOP level" in header, "the reset branch"
|
||||
assert "BEFORE the re-fetch" in header, "the refresh alignment"
|
||||
assert "Boot pushes NOTHING" in header, "no pushState on boot"
|
||||
assert "resetVanishedLocation is UNCHANGED" in header
|
||||
assert "module is UNTOUCHED" in header, "the router module is untouched"
|
||||
|
||||
Reference in New Issue
Block a user