fix(render): count multi-line <div> opening tags in dj-root boundary scan (#1749)#1750
Conversation
…scan (#1749) render_full_template locates the dj-root region's closing </div> by counting <div> depth, but the opening-tag check only matched the exact forms "<div " and "<div>". A multi-line opening tag ("<div\n class=...>" / "<div\t...") was not counted, so each missed open under-counted depth and a later </div> closed the dj-root region EARLY. The full rendered view was then spliced in place of the truncated region, leaving the tail of the page OUTSIDE <div dj-root> (and duplicated). Because dj-navigate swaps only the [dj-root] subtree, that ejected content was never cleared on navigation (leaked onto the next page). Fix: match "<div" followed by any tag-boundary char (whitespace, '>', '/'). Adds test_render_full_template_multiline_div_boundary.py with gate-off proof (fails on the old 2-form check: tail marker appears twice). Same family as #1746. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stage-7 Adversarial Review — PR #1750 (fix #1749)Verdict: APPROVE (with one 🟡 latent-mirror-bug finding tracked as a follow-up, not a merge blocker) The fix is correct, minimal, and the regression test is genuinely behavior-coupled (proven by an independent gate-off). The one substantive finding is a latent close-side symmetry gap that this PR does not introduce and does not worsen — it is the symmetric twin of the bug being fixed and exists identically in a sibling counter. Flagging per the #1646 parallel-path-drift canon, but it is lower-probability than the open-side bug and out of scope for this fix. Mandatory checks
Predicate correctness (empirically replicated, not eyeballed)🟢 Standalone replication of the new open-check over crafted strings confirmed:
🟡 Close-side mirror bug — LATENT (
|
Retrospective — PR #1750 (#1749)Task: fix render_full_template ejecting page content outside Quality: 5/5Tight one-predicate fix, root-caused empirically against a live downstream symptom, gate-off-proven test, systemic remainder tracked. What went well
What didn't
Verified
Follow-ups
|
…rs (#1751) (#1754) * fix(render): close-side </div> whitespace tolerance + consolidate dj-root scanners (#1751) Completes the #1749/#1750 dj-root-boundary fix class. - `_find_closing_div_pos` (the shared scanner, 6 call sites) hardcoded the close tag as `</div>`, missing `</div >` / `</div\n>`. That is the close-side twin of #1749's open-side under-count: a whitespace close over-counts depth so the close is never found. Now matches `</div\s*>` (`close_match.end()` consumes the full tag, so splice points stay correct). - Replaced `render_full_template`'s separate hand-rolled depth loop (its open side was patched in #1750) with a call to `_find_closing_div_pos`. The helper is multi-line-safe on the open side (`<div\b`) and now whitespace-tolerant on the close side; the rendered shell has no `{% %}` tags so the helper's if/else handling is inert. Removing the duplicate scanner closes the parallel-path-drift (#1646) that let the open-side bug exist in one copy only. Adds close-side gate-off test (reverting `</div\s*>`→`</div>` makes the whitespace test fail); existing multi-line + render-path suites stay green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(changelog): record #1751 close-side </div> tolerance + scanner consolidation --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes #1749.
Problem
render_full_templatefinds thedj-rootregion's closing</div>by counting<div>depth, but the opening-tag check only matched the exact byte forms"<div "and"<div>". A multi-line opening tag —<div\n class="...">/<div\t...>— was not counted as an open. Each missed open under-counted depth, so a later</div>drove depth to 0 early: thedj-rootregion closed before its real end, the full rendered view was spliced in place of the truncated region, and the tail of the page rendered outside<div dj-root>(and duplicated).Because
dj-navigateswaps only the[dj-root]subtree on SPA navigation, that ejected content was never cleared → it leaked onto the next page. Reproduced on djust.org/examples/(demo sections authored as<div\n class="demo-section">): navigating examples→home left all demos on the home page. Single-line<divcontent (e.g. home) was unaffected; only triggers on a fresh GET (the SPA-mount path puts content cleanly insidedj-root). Same family as #1746.Fix
Match
<divfollowed by any tag-boundary char (whitespace,>,/) — one predicate inrender_full_template's boundary scan (python/djust/mixins/template.py).Verification
/examples/: 484 real<divopens, only 481 counted (3<div\nmissed) → demos ejected. After fix: all 17 demos insidedj-root; examples→home navigation clean (no leak, correct title/dj-view).test_render_full_template_multiline_div_boundary.py(2 cases) with gate-off proof: reverting the predicate makes the tail marker appear 2× (ejected/duplicated) → test fails; with the fix it appears once, insidedj-root. Control: single-line<div>content stays green.test_theme_tags_rust_engine_1721failures inmake test-pythonare pre-existing test-ordering pollution (identical on cleanorigin/main; pass in isolation; CI runs tests in separate jobs).Follow-up (separate, not in this PR)
dj-navigateSPA nav doesn't update page chrome outsidedj-root(active-nav highlight,document.title, thedj-rootdj-viewattribute). Tracking separately — relates to ADR-021.🤖 Generated with Claude Code