Skip to content

refactor: simplify hydration markers to data-free sequential format#7474

Merged
janechu merged 24 commits intoreleases/fast-element-v3from
users/janechu/update-hydration-markers
Apr 22, 2026
Merged

refactor: simplify hydration markers to data-free sequential format#7474
janechu merged 24 commits intoreleases/fast-element-v3from
users/janechu/update-hydration-markers

Conversation

@janechu
Copy link
Copy Markdown
Collaborator

@janechu janechu commented Apr 22, 2026

Pull Request

📖 Description

Replace the verbose, index-embedded hydration marker system with a compact, data-free sequential format. This is a breaking change to the SSR output format.

Why: The old markers embedded binding indices and scope IDs into HTML comments (73+ chars per pair) and required regex parsing on the client. Since both the SSR template compiler and client hydration walker traverse the DOM in identical DFS order, these embedded indices are redundant — a simple sequential pointer suffices.

What changed:

  • Content binding markers:
    Old: fe-b$$start$$<factoryIndex>$$<scopeId>$$fe-b / fe-b$$end$$...
    New: fe:b / fe:/b

  • Repeat item markers:
    Old: fe-repeat$$start$$<itemIndex>$$fe-repeat / fe-repeat$$end$$...
    New: fe:r / fe:/r

  • Element boundary markers:
    Old: fe-eb$$start$$<elementId>$$fe-eb / fe-eb$$end$$...
    New: fe:e / fe:/e

  • Attribute binding markers:
    Old: three formats — data-fe-b="0 1 2" (space-separated indices), data-fe-b-0 (enumerated), data-fe-c-0-3 (compact start+count)
    New: single format — data-fe="N" (binding count only)

  • Parsing: Six regex patterns → string equality checks

  • Pairing: ID-based start/end matching → balanced depth counting

  • Walker: Index-based factory lookup → sequential factory pointer

  • Rust SSR: HydrationScope simplified — removed scope_prefix, child(), parameterized marker methods

  • Benchmarks: Replaced hardcoded render.ts functions with WASM-based SSR via @microsoft/fast-build, ensuring benchmark output always matches the actual crate

👩‍💻 Reviewer Notes

  • The core architectural change is in target-builder.ts — the hydration walker now advances a factoryPointer sequentially rather than parsing indices from comment data.
  • repeat.ts hydration walks backward from items.length - 1 using fe:/r/fe:r end/start markers with balanced depth counting. Empty repeat items (adjacent markers with no content) are handled explicitly.
  • Rust HydrationScope methods repeat_start_marker()/repeat_end_marker() are currently unused — repeat markers are emitted as string literals in render_repeat_items. These could be removed or wired in as a follow-up.
  • Element boundary markers (fe:e/fe:/e) are supported on the TS client side but not emitted by the Rust crate — DSD (<template shadowrootmode="open">) provides natural isolation. Both start and end boundary markers are cleared after hydration.
  • All markers now use the fe: namespace prefix, consistent with the data-fe attribute naming.
  • parseAttributeBindingCount() validates strictly (digit-only regex, positive integer) and throws FAST.error(Message.invalidHydrationAttributeMarker) for malformed values.
  • Content binding and element boundary paths throw HydrationTargetElementError when factories are exhausted or end markers are missing.
  • Repeat hydration validates both underflow (fewer markers than items) and overflow (more markers than items).

📑 Test Plan

  • All Rust unit tests pass (cargo test in crates/microsoft-fast-build)
  • All 852 Playwright tests pass across Chromium, Firefox, and WebKit
  • Test fixtures regenerated via npm run build:fixtures -w @microsoft/fast-html using rebuilt WASM binary
  • Verified no old marker format remains in fixture HTML or WASM binary via grep/strings
  • Rust test assertions use matches().count() for multi-occurrence markers to verify exact counts

✅ Checklist

General

  • I have included a change request file using $ npm run change
  • I have added tests for my changes.
  • I have tested my changes.
  • I have updated the project documentation to reflect my changes.
  • I have read the CONTRIBUTING documentation and followed the standards for this project.

⏭ Next Steps

  • Add focused Playwright hydration tests for the repeat marker scanning, depth counting, and empty-item edge cases.
  • Consider removing unused repeat_start_marker()/repeat_end_marker() methods from HydrationScope in Rust, or wiring them into render_repeat_items instead of hardcoded strings.
  • Consider whether the Rust crate should emit fe:e/fe:/e element boundary markers for non-DSD scenarios.
  • Evaluate viewport-deferred hydration (see PROPOSAL_VIEWPORT_DEFERRED_HYDRATION.md) as a higher-impact optimization for reducing TTI.

@janechu janechu added the fast-element-v3 Pertains to fast-element-v3 label Apr 22, 2026
@janechu janechu requested a review from Copilot April 22, 2026 03:47
@janechu janechu marked this pull request as ready for review April 22, 2026 03:50
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new data-free, sequential hydration marker format across FAST SSR and client hydration, replacing index/ID-encoded comment markers and multi-format attribute markers. It also updates benchmarks and documentation to reflect the new SSR output, and updates the Rust @microsoft/fast-build renderer + tests accordingly.

Changes:

  • Replace hydration markers with fixed strings (fe:b, fe:/b, fe:r, fe:/r, fe:e, fe:/e) and replace attribute marker variants with data-fe="N".
  • Refactor client hydration walking to consume factories sequentially (no regex parsing / embedded indices).
  • Update Rust SSR emission + tests, benchmark SSR rendering to use the WASM renderer, and update migration/docs.

Reviewed changes

Copilot reviewed 42 out of 42 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
sites/website/src/docs/3.x/migration-guide.md Adds 3.x migration note for the new hydration marker format
sites/benchmarks/vite.config.ts Switches benchmark SSR generation to use the WASM renderer with per-scenario caching
sites/benchmarks/src/scenarios/when/hydration/state.json Adds scenario state for WASM-based SSR rendering
sites/benchmarks/src/scenarios/when/hydration/render.ts Removes hardcoded HTML SSR output (replaced by WASM rendering)
sites/benchmarks/src/scenarios/when/hydration/entry.html Adds scenario entry HTML used by WASM renderer
sites/benchmarks/src/scenarios/repeat/hydration/state.json Adds scenario state for WASM-based SSR rendering
sites/benchmarks/src/scenarios/repeat/hydration/render.ts Removes hardcoded HTML SSR output (replaced by WASM rendering)
sites/benchmarks/src/scenarios/repeat/hydration/entry.html Adds scenario entry HTML used by WASM renderer
sites/benchmarks/src/scenarios/ref-slotted/hydration/state.json Adds scenario state for WASM-based SSR rendering
sites/benchmarks/src/scenarios/ref-slotted/hydration/render.ts Removes hardcoded HTML SSR output (replaced by WASM rendering)
sites/benchmarks/src/scenarios/ref-slotted/hydration/entry.html Adds scenario entry HTML used by WASM renderer
sites/benchmarks/src/scenarios/dot-syntax/hydration/state.json Adds scenario state for WASM-based SSR rendering
sites/benchmarks/src/scenarios/dot-syntax/hydration/render.ts Removes hardcoded HTML SSR output (replaced by WASM rendering)
sites/benchmarks/src/scenarios/dot-syntax/hydration/entry.html Adds scenario entry HTML used by WASM renderer
sites/benchmarks/src/scenarios/bind-event/hydration/state.json Adds scenario state for WASM-based SSR rendering
sites/benchmarks/src/scenarios/bind-event/hydration/render.ts Removes hardcoded HTML SSR output (replaced by WASM rendering)
sites/benchmarks/src/scenarios/bind-event/hydration/entry.html Adds scenario entry HTML used by WASM renderer
sites/benchmarks/src/scenarios/basic/hydration/state.json Adds scenario state for WASM-based SSR rendering
sites/benchmarks/src/scenarios/basic/hydration/render.ts Removes hardcoded HTML SSR output (replaced by WASM rendering)
sites/benchmarks/src/scenarios/basic/hydration/entry.html Adds scenario entry HTML used by WASM renderer
sites/benchmarks/src/scenarios/attr-reflect/hydration/state.json Adds scenario state for WASM-based SSR rendering
sites/benchmarks/src/scenarios/attr-reflect/hydration/render.ts Removes hardcoded HTML SSR output (replaced by WASM rendering)
sites/benchmarks/src/scenarios/attr-reflect/hydration/entry.html Adds scenario entry HTML used by WASM renderer
packages/fast-html/RENDERING_LIFECYCLE.md Updates lifecycle docs to show new comment markers
packages/fast-html/RENDERING.md Updates rendering docs for new markers and data-fe="N"
packages/fast-html/MIGRATION.md Adds migration section mapping old markers to new markers
packages/fast-html/DESIGN.md Updates design docs examples/diagrams for new markers
packages/fast-element/src/templating/repeat.ts Refactors repeat hydration to pair fe:r / fe:/r using depth counting
packages/fast-element/src/templating/TEMPLATE-BINDINGS.md Updates hydration marker documentation/flowcharts for new format
packages/fast-element/src/hydration/target-builder.ts Refactors hydration target-building to sequential factory consumption with data-fe="N" and fe:* comment markers
packages/fast-element/src/components/hydration.ts Replaces regex-based marker parsing with fixed-string marker utilities and parseAttributeBindingCount
packages/fast-element/MIGRATION.md Adds v3 migration notes for the new hydration marker format and API changes
crates/microsoft-fast-build/tests/hydration.rs Updates Rust unit tests to assert the new marker format
crates/microsoft-fast-build/src/node.rs Updates SSR emission to use count markers and data-free content markers
crates/microsoft-fast-build/src/lib.rs Updates crate docs to reflect new count marker format
crates/microsoft-fast-build/src/hydration.rs Simplifies HydrationScope to data-free marker helpers and removes scoped naming
crates/microsoft-fast-build/src/directive.rs Updates directive emission (when, repeat, custom elements) to new marker format
crates/microsoft-fast-build/src/attribute.rs Replaces compact marker injection with data-fe="N" marker injection
crates/microsoft-fast-build/README.md Updates documentation examples to new markers
crates/microsoft-fast-build/DESIGN.md Updates design documentation to new marker format
change/@microsoft-fast-html-fe4f7812-d5d7-40a1-bb1b-31abe4d47aec.json Records prerelease change note for @microsoft/fast-html
change/@microsoft-fast-element-9dd7b073-98cc-401b-a04d-0c02bee14306.json Records major breaking change note for @microsoft/fast-element

Comment thread packages/fast-element/src/components/hydration.ts Outdated
Comment thread packages/fast-element/src/components/hydration.ts Outdated
Comment thread packages/fast-element/src/hydration/target-builder.ts
Comment thread packages/fast-element/src/hydration/target-builder.ts Outdated
Comment thread crates/microsoft-fast-build/tests/hydration.rs Outdated
Comment thread crates/microsoft-fast-build/tests/hydration.rs Outdated
Comment thread sites/website/src/docs/3.x/migration-guide.md
Comment thread packages/fast-element/src/templating/repeat.ts
Comment thread packages/fast-element/src/templating/TEMPLATE-BINDINGS.md Outdated
@janechu janechu marked this pull request as draft April 22, 2026 04:00
@janechu janechu requested a review from Copilot April 22, 2026 04:23
@janechu janechu marked this pull request as ready for review April 22, 2026 04:23
@janechu janechu force-pushed the users/janechu/update-hydration-markers branch from db4ac60 to f510b47 Compare April 22, 2026 04:24
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 42 out of 42 changed files in this pull request and generated 5 comments.

Comment thread packages/fast-element/src/hydration/target-builder.ts
Comment thread packages/fast-element/src/templating/repeat.ts
Comment thread crates/microsoft-fast-build/tests/hydration.rs Outdated
Comment thread packages/fast-element/MIGRATION.md Outdated
Comment thread packages/fast-element/src/components/hydration.ts
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 42 out of 42 changed files in this pull request and generated 2 comments.

Comment thread sites/benchmarks/vite.config.ts
Comment thread packages/fast-element/src/hydration/target-builder.ts Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 43 out of 43 changed files in this pull request and generated 2 comments.

Comment thread packages/fast-element/src/templating/repeat.ts
Comment thread packages/fast-element/src/components/hydration.ts
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 44 out of 44 changed files in this pull request and generated 2 comments.

Comment thread sites/benchmarks/vite.config.ts Outdated
Comment thread packages/fast-element/src/interfaces.ts
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 45 out of 45 changed files in this pull request and generated 2 comments.

Comment thread packages/fast-element/src/templating/repeat.ts Outdated
Comment thread packages/fast-element/src/templating/repeat.ts
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 45 out of 45 changed files in this pull request and generated 3 comments.

Comment thread packages/fast-element/src/templating/repeat.ts Outdated
Comment thread packages/fast-element/src/templating/repeat.ts
Comment thread packages/fast-element/src/templating/repeat.ts
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 66 out of 66 changed files in this pull request and generated no new comments.

janechu and others added 21 commits April 22, 2026 12:28
Replace verbose HTML comment markers (73+ chars per pair) with compact
data-free markers (f:b, f:/b, f:r, f:/r, f:e, f:/e). Replace regex
parsing with string equality checks and balanced depth counting. Single
data-fe="N" attribute replaces three old formats.

BREAKING CHANGE: SSR output format changed - all hydration markers use
new sequential format requiring matched SSR/client versions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…attribute

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace handwritten render functions that hardcoded hydration markers
with entry.html + state.json files rendered by @microsoft/fast-build
WASM at Vite build time. This ensures benchmark SSR output always
matches the actual crate output and eliminates marker drift.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update MIGRATION.md for fast-element and fast-html packages, and the
3.x website migration guide with the new marker format, removed APIs,
and migration impact.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix doc comment: f: prefix → fe: prefix in hydration.ts
- Add input validation to parseAttributeBindingCount (throw on non-numeric)
- Add null guard for factory in content binding path (target-builder.ts)
- Add null guard for first sibling in targetContentBinding (remove non-null assertion)
- Fix duplicate Rust test assertions: use count-based checks instead of contains
- Fix repeat marker test: assert exact count (2) instead of duplicate contains
- Expand migration guide marker table with repeat end and element boundary markers
- Update TEMPLATE-BINDINGS.md error table to reflect count-based semantics
- Add post-loop validation in repeat.ts hydrateViews for item count mismatch

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Widen HydrationTargetElementError.node type from Element to Node
  (content binding path passes Comment, not Element)
- Add strict digit-only regex validation in parseAttributeBindingCount
  (rejects partial numbers like '1abc')
- Fix duplicate assertion in slotted Rust test (use count-based check)
- Fix MIGRATION.md API names to match actual exports
  (isRepeatViewStartMarker, not isRepeatStartMarker, etc.)
- Add repeat overflow detection in hydrateViews
  (throws if DOM has more repeat markers than items.length)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add @microsoft/fast-build dependency to benchmarks package.json
  (was relying on workspace hoisting)
- Clear element boundary marker data (fe:e/fe:/e) during hydration
  (consistent with content/repeat marker cleanup)
- Throw HydrationTargetElementError when fe:/e end marker is not found
  (fail fast on malformed SSR output instead of silent success)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Make XMLSerializer lazy in hydrateViews (only allocated on error path)
- Use FAST.error(Message.invalidHydrationAttributeMarker) instead of
  plain Error for invalid data-fe values (consistent with library error
  reporting conventions)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add debug message for error code 1210 (invalidHydrationAttributeMarker)
  so FAST.error() shows a descriptive message instead of 'Unknown Error'
- Cache pre-stringified templates JSON in ScenarioCache to avoid
  repeated JSON.stringify() on every WASM render call (1000+ per build)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add element boundary error case and repeat overflow to error table
- Add FAST.error(1210) for invalid data-fe values
- Widen 'element node' to 'target node' (type is now Node, not Element)
- Update flowchart: element boundary now clears marker data and throws
  on missing end marker

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a repeat item renders no content between its fe:r/fe:/r markers,
the start/end range passed to template.hydrate() was inverted. Detect
this case by checking if end === startMarker (adjacent markers) and use
the cleared end marker comment as both first and last node.

The second comment (add focused hydration test) is noted for follow-up
but not addressed in this commit as it requires new test fixtures.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Bound repeat hydration scan to this directive's content binding
  boundaries (via bindingViewBoundaries) to prevent scanning into
  sibling repeat blocks
- Fix comment on empty-item handling to match actual implementation
  full diagnostic property bag
- Add Rust tests: single-item repeat, many-item repeat (count-based
  assertions), and multiple attribute bindings on one element

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix MIGRATION.md: correct old API names in Removed column
  (parseRepeatStartMarker not parseRepeatViewStartMarker,
   parseElementBoundaryStartMarker(content) not (node))
- Upgrade targetContentBinding errors from plain Error to
  HydrationTargetElementError with factories and node context
- Fix scanStop boundary: use previousSibling of boundary.first so the
  boundary node itself is still eligible for matching as a start marker
- Remove unused isShadowRoot helper function

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The committed fixture HTML files still had old markers (fe-b$$start$$...)
because the WASM binary was rebuilt locally but the fixtures were not
re-committed. Rebuilt all 21 fixtures with the new WASM.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Initialize views array with Array.from() instead of sparse new Array()
  so .map() in error diagnostics visits all indices
- Clear nested element boundary start/end marker data during
  skipToElementBoundaryEnd (not just the outermost pair)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
All 21 fixture HTML files were still using old marker format
(fe-b$$start$$...) from before the refactor. Rebuilt with
the updated WASM binary that emits fe:b/fe:/b/fe:r/fe:/r markers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Empty repeats (0 items) never enter the main hydration loop, so
'current' still points at this.location.previousSibling — which may
contain fe:/r markers from other repeat blocks. The overflow check
was incorrectly scanning into sibling content and throwing
'found more repeat items than expected 0'.

Skip the overflow check entirely when itemCount is 0 since an empty
repeat cannot overflow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@janechu janechu force-pushed the users/janechu/update-hydration-markers branch from 17642fa to e0d46b9 Compare April 22, 2026 19:29
janechu and others added 2 commits April 22, 2026 13:17
The overflow check (scan for extra fe:/r markers after hydration) and
the scanStop boundary (bindingViewBoundaries-based scan limit) both
caused false positives with nested repeats. Data-free markers make it
impossible to distinguish fe:/r from this repeat vs a nested repeat at
the same DOM depth. The underflow check also misfired when nested
repeat items were counted instead of this repeat's items.

Remove all three mechanisms. The balanced depth counting in the backward
scan correctly handles nesting without them. This matches the original
base branch behavior which also did not have overflow/underflow checks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The backward scan assigned hydrated views from the end of the array
(views[N-1], views[N-2], ...). When SSR had fewer items than the
runtime (e.g., state.json has 1 order but class field has 2), the
wrong indices got hydrated — views[1] instead of views[0].

Fix by collecting all marker ranges in a backward pass, reversing
them to restore forward order, then hydrating from index 0. This
matches the old behavior where marker-embedded indices directly
set the correct array position.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@janechu
Copy link
Copy Markdown
Collaborator Author

janechu commented Apr 22, 2026

I dug through the failing test_js run and this looks like a nested hydration regression, not an observer-map / deepMerge regression.

Only test_js is red; the other checks are green. The 7 failures are clustered in:

  • packages/fast-html/test/fixtures/extensions/observer-map-deep-merge/observer-map-deep-merge.spec.ts (6 failures)
  • packages/fast-html/test/fixtures/scenarios/nested-elements/nested-elements.spec.ts (1 failure)

The common pattern is that initial SSR output is fine, but post-hydration updates inside nested repeat/conditional/custom-element subtrees stop propagating:

  • after Replace Orders, Orders (1) updates but the DOM still shows the old nested order content instead of Order #103 / Monitor
  • toggling details hides the top-level .profile, but nested .metadata inside repeated items stays visible
  • nested-elements renders correctly initially, but repeated <child-element> instances never pick up the updated category attribute

That lines up with the marker rewrite in this PR: top-level bindings still work, but hydrated subviews inside repeats are getting wired incorrectly after the shift to data-free sequential markers. In practice, this looks like the new sequential factory/boundary bookkeeping is correct for outer views but goes stale for nested repeat descendants.

So the pipeline is failing because the new hydration flow is leaving nested repeat/when/custom-element views attached to the wrong boundaries/targets. The first places I’d inspect are:

  1. packages/fast-element/src/templating/repeat.ts (hydrateViews() backward scan / item-to-view reconstruction)
  2. packages/fast-element/src/hydration/target-builder.ts (targetContentBinding() / nested boundary handoff)

The strongest signal is that simple/top-level updates still pass, while updates one level down inside hydrated repeat trees do not.

@janechu
Copy link
Copy Markdown
Collaborator Author

janechu commented Apr 22, 2026

Test Failure Analysis: "should pass parent attribute to child elements"

The data-fe="3" attribute persists on <child-element> after hydration, meaning the repeat item's buildViewBindingTargets never processed it. Since the attribute bindings (text, idx, category) were never established, category stays at its SSR value "General" when the test expects "Updated".

Root cause — the two-pass hydrateViews approach corrupts item boundaries when adjacent markers are cleared:

The new hydrateViews collects ALL item ranges in a backward pass (clearing marker data as it goes), then reverses and hydrates forward. The problem is with adjacent markers like <!--fe:/r--><!--fe:r--> (no whitespace between item N's end and item N+1's start).

During the backward walk:

  1. Find <!--fe:/r--> (item 3 end) → clear data → end = current.previousSibling (whitespace text ✓)
  2. Walk backward to find <!--fe:r--> (item 3 start) → clear data → current = startMarker.previousSibling = <!--fe:/r--> of item 2
  3. Find <!--fe:/r--> (item 2 end) → clear data → end = current.previousSibling

At step 3, current.previousSibling should be the whitespace text before item 2's end marker. But since item 3's start marker (adjacent, just cleared in step 2) has already been visited and the walker has moved past it, the previousSibling chain is correct here.

However, the real issue may be subtler: after ALL markers are cleared in the first pass, the template.hydrate(start, end) call creates a HydrationView whose buildViewBindingTargets creates a Range from start to end. If either boundary node reference was invalidated or if the Range.comparePoint filter excludes the <child-element> due to an edge case with empty comment nodes in between, the TreeWalker skips the child-element entirely.

Suggestion: The old code hydrated each item inline during the backward walk (before clearing adjacent markers). Consider reverting to that pattern — hydrate immediately when each item range is found, rather than collecting ranges first. This avoids any risk of cleared markers affecting boundary node relationships:

// Instead of two passes, hydrate inline (backward) like the old code:
let itemIndex = itemCount - 1;
while (current !== null && itemIndex >= 0) {
    // find end marker, find start marker...
    const view = template.hydrate(itemStart, itemEnd);
    this.views[itemIndex] = view;
    this.bindView(view, this.items, itemIndex, this.controller);
    itemIndex--;
}

This preserves the sequential (no-index) marker design while matching the proven hydration-during-walk pattern from the base branch.

…ments fixture

The ElementController constructor captures @observable default values in
boundObservables during element upgrade. When connect() runs later during
hydration, bindObservables() replays those defaults — overwriting real data
that connectedCallback had already set. This caused the repeat to hydrate
with 0 items, producing duplicate child-elements.

Fix: use @observable with defaults and set data BEFORE super.connectedCallback()
so the constructor captures the real data instead of empty defaults.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@janechu janechu merged commit 46a4314 into releases/fast-element-v3 Apr 22, 2026
11 checks passed
@janechu janechu deleted the users/janechu/update-hydration-markers branch April 22, 2026 23:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fast-element-v3 Pertains to fast-element-v3

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants