Skip to content

feat: propagate unbound state to all descendant custom elements#7423

Open
janechu wants to merge 3 commits intomainfrom
users/janechu/allow-passing-all-unbound-state-to-child-elements
Open

feat: propagate unbound state to all descendant custom elements#7423
janechu wants to merge 3 commits intomainfrom
users/janechu/allow-passing-all-unbound-state-to-child-elements

Conversation

@janechu
Copy link
Copy Markdown
Collaborator

@janechu janechu commented Apr 10, 2026

Pull Request

📖 Description

This change updates the microsoft-fast-build Rust crate so that all custom elements — whether at the entry level or deeply nested — receive the current root state as a base for their child state. Per-element HTML attributes are overlaid on top, with attribute-derived values taking precedence over any matching state keys.

Previously, only entry-level (root) custom elements received the full root state; nested elements built their state entirely from HTML attributes. This meant that if a child element template referenced a state key like {{text}}, the parent element had to explicitly pass it via an attribute binding (e.g. <my-child text="{{text}}">). With this change, unbound state keys propagate through the entire element tree automatically.

Example

<!-- state.json: {"text": "Hello world"} -->

<!-- my-el template -->
<f-template name="my-el">
    <template>
        {{text}}
        <my-child-el></my-child-el>
    </template>
</f-template>

<!-- my-child-el template — {{text}} resolves automatically -->
<f-template name="my-child-el">
    <template>
        {{text}}
    </template>
</f-template>

Both elements now render "Hello world" without explicit attribute bindings.

👩‍💻 Reviewer Notes

  • The core change is in crates/microsoft-fast-build/src/directive.rs — the render_custom_element function now always uses the current root state as a base for child state (removing the is_entry branching for state building).
  • The is_entry flag still controls opening-tag attribute handling (root elements resolve primitive bindings, strip non-primitives; nested elements render resolved values and inject data-fe-c markers).
  • The test test_nested_custom_element_still_uses_attr_state was updated to test_nested_custom_element_inherits_parent_state to reflect the new expected behavior.
  • DESIGN.md and README.md for the crate have been updated.

📑 Test Plan

  • Updated existing test (test_nested_custom_element_inherits_parent_state) to verify state propagation works.
  • Added 4 new tests:
    • test_unbound_state_propagates_to_child_elements — exact scenario from the spec
    • test_state_propagates_through_multiple_levels — three levels of nesting
    • test_child_attr_overrides_propagated_state — attribute values take precedence
    • test_non_entry_render_also_propagates_state — non-entry mode also propagates
  • All 250 Rust crate tests pass.
  • Full npm build and 10,917 npm tests pass.

✅ 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.

janechu and others added 2 commits April 9, 2026 21:45
Custom elements now always receive the current root state as a base
for their child state, with per-element HTML attributes overlaid on
top. Previously, only entry-level (root) custom elements received the
full root state; nested elements built their state entirely from
attributes. This change ensures unbound state keys propagate through
the entire element tree automatically, so child elements can reference
any ancestor state key without explicit attribute bindings.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@janechu janechu marked this pull request as ready for review April 10, 2026 04:55
@janechu janechu requested a review from Copilot April 10, 2026 04:55
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

Updates the microsoft-fast-build Rust renderer so nested custom elements inherit their parent/root state by default, allowing unbound state keys (e.g. {{text}}) to resolve throughout the custom element tree without requiring explicit attribute bindings.

Changes:

  • Adjusts render_custom_element state construction to always base child state on the current root/parent state, then overlay per-element attributes.
  • Updates and adds Rust tests to validate state propagation through nested custom elements and override precedence.
  • Updates crate docs (README + DESIGN) to reflect the new state propagation model and clarify is_entry behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
crates/microsoft-fast-build/src/directive.rs Changes child state building for custom elements to always inherit current root/parent state before overlaying attrs.
crates/microsoft-fast-build/tests/custom_elements.rs Renames/updates an existing nested-element test and adds new tests for propagation, multi-level nesting, overrides, and non-entry rendering.
crates/microsoft-fast-build/README.md Documents state propagation to nested elements and clarifies entry/root element attribute handling behavior.
crates/microsoft-fast-build/DESIGN.md Updates internal design docs to reflect that is_entry now only affects opening-tag attribute handling, not child state building.

When a custom element has no attributes that modify child state (only
@event, ?bool, or directive attributes), the root state is now passed
through by reference without cloning the HashMap. This avoids O(n)
work per element in deep custom-element trees with large state objects.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants