Skip to content

fix(runtime-tags): serialize sourceless spread bindings in stateful intersections - #3752

Open
rturnq wants to merge 3 commits into
mainfrom
claude/staticattrs-serialization-bug-lwj08l
Open

fix(runtime-tags): serialize sourceless spread bindings in stateful intersections#3752
rturnq wants to merge 3 commits into
mainfrom
claude/staticattrs-serialization-bug-lwj08l

Conversation

@rturnq

@rturnq rturnq commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Description

A binding spread onto a native tag is marked noSerialize since its attribute values are normally baked into the server-rendered HTML. When that binding participates in an intersection with stateful spread attrs, the intersection re-runs client side and reads the value from scope — but a sourceless binding (e.g. the return value of a non-stateful child tag) has no client signal to recompute it, so on the first update its attributes were dropped:

<define/Stat>
  <return={ class: "foo" }>
</define>

<define/Dyn>
  <let/count=0/>
  <return={ "data-count": count, onClick() { count++ } }>
</define>

<Stat/staticAttrs/>
<Dyn/dynamicAttrs/>

<button ...staticAttrs ...dynamicAttrs>Click</button>

Clicking the button removed class="foo", because the intersection pass in references.ts added a serialize reason for staticAttrs but writeSerializedBinding skipped the value due to noSerialize. Referencing the binding elsewhere (e.g. <log=staticAttrs>) masked the bug by making it no longer a pure spread.

The fix registers a separate value-serialize reason (kSerializedValueReason) for sourceless noSerialize bindings in an intersection, keyed to the partner's sources. writeSerializedBinding consults it when it would otherwise skip the value:

  • state-backed partner (the repro above) → the value serializes unconditionally, since the intersection can always re-run client side.
  • input-backed partner (e.g. <const/extra={ id: "x" }/> + <div ...input ...extra /> in native-tag-spread-content-multi) → the value serializes behind the existing runtime serialize guard (_serialize_if(...) && extra), so callers that pass only static input pay nothing. That fixture's snapshot change reflects this — it was a latent instance of the same bug, and its serialized HTML actually shrinks for its static caller.

Checklist:

  • I have read the CONTRIBUTING document and have signed (or will sign) the CLA.
  • I have updated/added documentation affected by my changes.
  • I have added tests to cover my changes.

…ntersections

A binding spread onto a native tag is marked noSerialize since its value
is normally baked into the HTML. When that binding intersects with
stateful spread attrs, the intersection re-runs client side and reads the
value from scope — but a sourceless binding (e.g. a static tag return)
has no client signal to recompute it, so its attrs were dropped on
update. Clear noSerialize for such bindings so their value resumes.
@changeset-bot

changeset-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ff30265

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@marko/runtime-tags Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.35%. Comparing base (69852c9) to head (ff30265).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3752   +/-   ##
=======================================
  Coverage   90.35%   90.35%           
=======================================
  Files         423      423           
  Lines       19785    19793    +8     
  Branches     3635     3638    +3     
=======================================
+ Hits        17876    17884    +8     
  Misses       1367     1367           
  Partials      542      542           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5f9f8e80-a904-49b0-a0be-ef9c01bc5441

📥 Commits

Reviewing files that changed from the base of the PR and between 4371d46 and ff30265.

📒 Files selected for processing (1)
  • agent-feedback/cleanup.md

Walkthrough

The translator preserves sourceless intersection binding values by assigning a serialized-value reason and honoring that reason during binding serialization. New runtime-tag fixtures combine static and dynamic spreads on a native button, verify updates across three clicks, and record output sizes. A changeset documents the hydration fix and patch release.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the fix for sourceless spread bindings in stateful intersections.
Description check ✅ Passed The description directly explains the serialization bug, the fix, affected cases, and test coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/staticattrs-serialization-bug-lwj08l

Comment @coderabbitai help to get the list of available commands.

@rturnq rturnq added this to Roadmap Aug 6, 2026
@github-project-automation github-project-automation Bot moved this to Todo in Roadmap Aug 6, 2026
@rturnq rturnq moved this from Todo to Done in Roadmap Aug 6, 2026
@rturnq rturnq moved this from Done to Todo in Roadmap Aug 6, 2026
rturnq added 2 commits August 6, 2026 02:19
…sources

Instead of unconditionally clearing noSerialize for sourceless bindings
in an intersection, key their value serialization to the partner's
sources. State-backed partners still serialize unconditionally, while
input-backed partners emit a runtime guard so the value only serializes
when the caller actually passes stateful input.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

1 participant