feat(client): JS-owned region escape hatch for morph (B4, #22)#45
Conversation
Add a data-no-morph attribute that lets component authors mark a subtree as owned by other JavaScript (third-party widgets, canvases, manually mounted library instances) so idiomorph never touches it. Wires beforeNodeMorphed/beforeNodeRemoved callbacks into the Idiomorph.morph() config shared by update() and rollback(), confirmed against idiomorph's source semantics: returning false from beforeNodeMorphed skips a node's attributes and children entirely (protects the whole subtree, not just the root), and false from beforeNodeRemoved keeps the node attached even if the server's HTML has no matching node at that position. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pi1LT1PQ8qo9GcyLeDLiut
Adversarial review — PR #45 (B4:
|
…e-region # Conflicts: # src/component_framework/static/component_framework/js/component-client.js
|
Merged |
…case caveats (B4, #22)
…e-region # Conflicts: # src/component_framework/static/component_framework/js/component-client.js
|
Two follow-up commits since the last update:
All 39 JS tests pass (7 original + B2's 9 + B3's 9 + B4's own), 496 Python tests pass, ruff clean. Waiting on this run's CI before final merge. |
Epic B (DOM Morphing & Rendering Fidelity, #22) is complete: Idiomorph-based morphing (#43), focus/scroll/in-flight input preservation (#44), stable data-key list reconciliation (#46), and a data-no-morph escape hatch for JS-owned regions (#45). Combined with Epic A (#21, HMAC state signing + locked fields + CSRF/CSWSH audit), this closes out the 0.6.0b - Hardening Foundation milestone. Backfills missing CHANGELOG entries for B1/B2/B4 (only B3 had one) and corrects README claims left over from before the morph work landed (the known-limitations bullet, the architecture-overview paragraph, and the roadmap's 0.6.0b line, which had mis-attributed Epic D's 422-re-render item). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pi1LT1PQ8qo9GcyLeDLiut
Summary
Implements B4 of Epic B (#22 — DOM Morphing & Rendering Fidelity):
A component author can now mark an element with
data-no-morph(presence of the attribute is all that matters) to tell the client "idiomorph must never touch this element or its descendants" — for third-party widgets, manually mounted JS library instances, canvases, etc. that a component doesn't want touched/replaced on every server patch.What changed
src/component_framework/static/component_framework/js/component-client.js_morphConfig()helper builds the sharedIdiomorph.morph()config for bothupdate()androllback(), wiring:beforeNodeMorphed→ returnsfalsefordata-no-morphelements (and their descendants), which idiomorph's own source (confirmed by direct read ofvendor/idiomorph.js,morphNode()~line 645) short-circuits before copying attributes or recursing into children — the whole subtree is left untouched, not just the root's own attributes.beforeNodeRemoved→ returnsfalsefor the same nodes, so idiomorph won't delete an ignored node outright when the incoming server HTML has no matching node at that position (removeNode()~line 528)._isIgnoredNode()helper:closest('[data-no-morph]') !== null, guarding against non-Element nodes (e.g. text nodes) that don't exposeclosest().vendor/idiomorph.jsitself is untouched — the callback is wired via the config object passed intoIdiomorph.morph(), per the vendoring convention established in feat(client): adopt Idiomorph morph swap in component-client.js (B1, #22) #43.tests/js/ignore-region.test.mjs(new) — TDD, written and confirmed failing before implementation. Since this repo has no jsdom/bundler (permorph.test.mjs's stated philosophy — idiomorph ships its own upstream test suite; this repo only tests the integration seam), these tests mockIdiomorph.morph()to capture the configupdate()/rollback()build, then exercise the capturedbeforeNodeMorphed/beforeNodeRemovedcallbacks directly against hand-rolled node stubs (including an ancestor-chain stub to prove descendant protection, and a text-node stub to prove no crash on non-Element nodes).docs/CLIENT_MORPHING.md(new) — component-author documentation for the morphing integration and thedata-no-morphconvention, semantics, and caveats (don't put it on the component root; it's a client-side-only concern; scope it tightly since nested[data-event]/[data-component]inside an ignored region never get server-driven updates).README.md— added the new doc to the documentation table.Out of scope (left untouched)
data-key) are separate checklist items in Epic B: DOM Morphing & Rendering Fidelity #22, not addressed here.vendor/idiomorph.jswas not hand-edited.Test plan
node --test "tests/js/**/*.test.mjs"— 21/21 pass (7 old + new ignore-region tests), confirmed red before the implementation, green after.uv run pytest— 496 passed.uv run ruff check ./uv run ruff format --check .— clean.Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01Pi1LT1PQ8qo9GcyLeDLiut