Repository navigation
feat: add file-scoped recursive for loops - #547
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
It contains at least one confirmed correctness/UX issue (nondeterministic/incorrect client-template diagnostic locations) plus a FAST validation bypass due to case-sensitive tag matching.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This PR introduces file-scoped named <for> bodies to enable recursive (and mutually recursive) loops by reusing a single compiled loop body within the same entry/component file, while keeping protocol shape stable and avoiding schema/runtime JS additions.
Changes:
- Add
id="..."support for<for>to define/refer to a reusable loop body (including forward/mutual references) with build-time validation and new diagnostic codes. - Enforce unbraced
each="item in collection"parsing across SSR/native compilation paths, and reject legacytemplatenaming with a migration diagnostic. - Add cycle-safe traversal for CSS-token analysis with shared loop records; add extensive Rust + Playwright fixture coverage and documentation updates.
File summaries
| File | Description |
|---|---|
| packages/webui-framework/tests/fixtures/recursive-repeat/state.json | Adds state fixture data for recursive/forward/independent/mutual loop scenarios. |
| packages/webui-framework/tests/fixtures/recursive-repeat/src/test-recursive-tree/test-recursive-tree.html | Defines a recursive named-loop tree rendering template with events and keys. |
| packages/webui-framework/tests/fixtures/recursive-repeat/src/test-recursive-mutual/test-recursive-mutual.html | Adds mutual named-loop reference fixture to validate finite traversal and scoping. |
| packages/webui-framework/tests/fixtures/recursive-repeat/src/test-recursive-independent/test-recursive-independent.html | Adds same-id independence fixture scoped per component/file. |
| packages/webui-framework/tests/fixtures/recursive-repeat/src/test-recursive-forward/test-recursive-forward.html | Adds forward-reference fixture (reference before definition). |
| packages/webui-framework/tests/fixtures/recursive-repeat/src/index.html | Fixture page wiring for Playwright coverage. |
| packages/webui-framework/tests/fixtures/recursive-repeat/recursive-repeat.spec.ts | Playwright coverage for SSR/no-JS render, hydration, keyed updates, events, and client-created recursion. |
| packages/webui-framework/tests/fixtures/recursive-repeat/element.ts | Defines observable element classes used by the recursive fixture tests. |
| docs/guide/concepts/directives/for.md | Documents recursive named loops, id rules, key inheritance, and each formatting constraints. |
| docs/ai.md | Updates AI guidance with named recursion rules and links to the canonical docs section. |
| DESIGN.md | Updates specification for <for> parsing/diagnostics and adds cycle-safe CSS token walk behavior notes. |
| crates/webui/src/lib.rs | Adds an end-to-end Rust build test to ensure recursive named loops don’t break component asset/style discovery. |
| crates/webui-parser/src/plugin/webui/named_repeats.rs | Introduces compile-time named-repeat block reuse + key inheritance + cycle-safe root analysis helper. |
| crates/webui-parser/src/plugin/webui.rs | Integrates named repeat compilation into WebUI template metadata emission; normalizes <if> condition braces. |
| crates/webui-parser/src/plugin/fast/v3.rs | Rejects named <for> references in FAST builds; normalizes <if> condition braces. |
| crates/webui-parser/src/plugin/fast/v2.rs | Same as v3: rejects named references; normalizes <if> condition braces. |
| crates/webui-parser/src/plugin/fast/shared.rs | Adds shared FAST validation to detect unsupported named <for> references. |
| crates/webui-parser/src/plugin/fast/diagnostic.rs | Adds fast-named-for-unsupported diagnostic with actionable guidance. |
| crates/webui-parser/src/lib.rs | Implements named loop scoping, ID collision checks, each parser reuse, brace-stripping for <if>, and cycle-safe token traversal. |
| crates/webui-parser/src/diagnostic.rs | Adds new diagnostic code constants for named-loop authoring errors. |
| crates/webui-handler/src/lib.rs | Adds SSR rendering regression tests covering recursion, scoping restore, missing children termination, and error behavior. |
Review details
- Files reviewed: 21/21 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
ISSUE-001: Deduplicate equivalent shared-block analysis pathsSeverity: 🟡 Performance — P2 Files: The new ancestor-only guard prevents cycles but still visits every acyclic path through shared bodies, retaining a scope record per visit. Equivalent scopes therefore cause exponential build-time CPU and memory growth. Reproduction: 22 named bodies, each referencing the next twice, produce just 2.8 KB of source. A release-mode real build took approximately 884 ms and 328 MiB peak working set, producing only 22 blocks and 2.5 KB of client metadata. Rendering with empty collections produced 25 bytes of HTML. The expensive work is redundant analysis, not required output. Suggested fix: Memoize equivalent block/scope visits or compute free-root sets iteratively. Preserve distinctions between genuinely different scopes; block-only global deduplication would be incorrect. Evidence: 1,451 affected Rust tests, 363 framework unit tests, E2E typechecking, all six recursive browser scenarios, and an additional 250-level client-created tree probe passed. All 35 current-head CI checks are green. Existing recursive runtime calls are pre-existing, not another PR-introduced finding. Full local quality-gate/platform coverage and ordinary-loop performance claims were not independently verified. |
Memoize block visits by their canonical bound-name sets in client root and CSS token analysis, retaining distinct scopes and the ordinary-template fast path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Accepted ISSUE-001 and fixed it in 1b6e2ff by memoizing equivalent block/bound-name-set visits in client-root and CSS-token analysis, preserving distinct lexical scopes, traversal order, cycle guards, and the ordinary-template fast path. Across seven fresh release-build processes, the 22-body reproduction improved from 875.33 ms/328.39 MiB to 0.87 ms/7.53 MiB (medians), with identical 22 blocks, 2,517-byte metadata, canonical protocol content, and 25-byte HTML; the new inline regressions fail without memoization, and 1,458 affected Rust tests, 363 framework unit tests, 37 repeat browser tests, E2E typechecking, and the full |
mddinbox
left a comment
There was a problem hiding this comment.
Reviewed the latest head and the addressed feedback. The FAST case-insensitive validation, deterministic diagnostic locations, and shared-block scope memoization fixes are satisfactory. No remaining code-review blockers found.
Validate scoped overrides separately from unconditional defaults while retaining recursive component asset and style coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
mddinbox
left a comment
There was a problem hiding this comment.
Reviewed the latest merge commit and its conflict resolutions. The recursive CSS-token fixtures now correctly distinguish unconditional :root defaults from selector-scoped overrides, while retaining recursive asset/style and fallback-chain coverage. No new correctness, performance, memory, security, reliability, or dependency-impact blockers found.
## Release Bumps WebUI to `0.0.29`. Previous release tag: `v0.0.28` ## Changes since `v0.0.28` Features: - Ship version-matched AI authoring guidance with `@microsoft/webui`, using a lightweight skill that reads the installed package's reference (feat: bundle versioned AI guidance with WebUI #522 by @mohamedmansour). - Add plugin-aware component discovery, with filename-based HTML components and manifest-driven FAST components (feat: add plugin-aware component discovery #524 by @mohamedmansour). - Add WebUI Press `--show=all|content` for shell-free documentation views that preserve SSR, hydration, authored examples, and accessible theme behavior (feat: add WebUI Press content mode #525 by @mohamedmansour). - Automatically support Trusted Types for compiled templates and generated CSS import maps without promoting arbitrary state HTML to trusted content (feat: use Trusted Types automatically in the framework #536 by @mohamedmansour). - Keep explicit streaming imports lightweight so applications can load streaming support early and application code later (feat: keep explicit streaming imports lightweight #539 by @mohamedmansour). - Render tree-shaped state with file-scoped named `<for id="...">` bodies, including forward references and recursive reuse without extra client runtime code. Use unbraced `each="item in items"`; the legacy `template` spelling and FAST component references produce actionable diagnostics (feat: add file-scoped recursive for loops #547 by @mohamedmansour). Fixes: - Preserve table-rooted hydration ownership and correctly compile whitespace-bearing, nested directives, preventing duplicate controls after reactive updates. Rebuild templates to receive the compiler fix (fix: preserve table-rooted hydration paths #512 by @mohamedmansour; fix: align client directive parsing with the HTML scanner #531 by @mohamedmansour). - Emit reachable stylesheets and templates in deterministic traversal order. External plugin implementations must adapt the affected `HandlerPlugin` and bootstrap component collection interfaces from hash sets to slices (fix: render reachable-component ordering deterministically #519 by @janechu). - Resolve bare CEM module specifiers through their owning packages, respecting export maps, package boundaries, and cache invalidation when nearer dependencies appear (fix: resolve bare CEM module specifiers #528 by @janechu). - Prevent spurious dev-server rebuilds from read-only filesystem activity and recursive traversal of linked workspace dependencies (Improve benchmark telemetry and dev-server watching #521 by @mohamedmansour). - Update rustls to address TLS 1.3 handshake encryption-level validation advisory RUSTSEC-2026-0285 (fix: update rustls to address RUSTSEC-2026-0285 #533 by @mohamedmansour). - Forward bare boolean and ARIA component inputs into SSR state while preserving empty literal attributes and existing conditional boolean bindings (fix: forward HTML component inputs during SSR #540 by @mohamedmansour). - Preserve sibling route declaration order across SSR and client routing so equally specific routes select the correct pending and error boundaries (fix: preserve route order for pending and error boundaries #541 by @mohamedmansour). - Handle skipped or superseded router view transitions without unhandled animation rejections, while preserving route-commit errors and responsive subsequent navigation (fix: handle router view transition rejections #543 by @mohamedmansour). - Wait for active dev-server rebuilds and their bundler subprocesses before graceful shutdown, preventing output writes after the server exits (fix: wait for dev-server rebuilds during shutdown #546 by @mohamedmansour). - Preserve global theme defaults when custom properties are overridden only by scoped selectors or conditional rules, so unmatched component instances retain their intended colors (fix: preserve theme defaults under scoped CSS overrides #548 by @mohamedmansour). Docs: - Add a data-driven Benchmark Explorer, centralize performance guidance, distinguish independent SSR/browser metrics, and publish resource telemetry with accessible methodology details (docs: centralize performance guidance and benchmark data #486 by @mohamedmansour; Improve benchmark telemetry and dev-server watching #521 by @mohamedmansour). - Update AI reference installation and migration guidance while retaining `/ai`, and clarify plugin-specific discovery and FAST converted-template requirements (feat: bundle versioned AI guidance with WebUI #522 by @mohamedmansour; feat: add plugin-aware component discovery #524 by @mohamedmansour). - Document owned Rust partial rendering, streaming state ownership, and watcher hashing benchmarks, including measurement scope and tradeoffs (perf: reduce partial response allocations #510 by @mohamedmansour; perf: move streaming command processing off async workers #529 by @mohamedmansour; perf: bound watcher hashing memory with reusable scratch #530 by @mohamedmansour). - Document automatic Trusted Types support, CSP enforcement, and first-declared precedence for equally specific routes and their pending/error boundaries (feat: use Trusted Types automatically in the framework #536 by @mohamedmansour; fix: preserve route order for pending and error boundaries #541 by @mohamedmansour). - Explain named recursive loops and their compatibility limits, scoped CSS token defaults, and router view-transition error handling (feat: add file-scoped recursive for loops #547 by @mohamedmansour; fix: preserve theme defaults under scoped CSS overrides #548 by @mohamedmansour; fix: handle router view transition rejections #543 by @mohamedmansour). Maintenance: - Reduce partial-response and per-render allocations through owned state projection, borrowed graph traversal, scalar formatting, and borrowed nested route trees. Rust callers use ownership-taking `Protocol::render_partial(Value, ...)`; JSON boundaries retain `render_partial_json` (perf: reduce partial response allocations #510 by @mohamedmansour; perf: cut per-render handler allocations #511 by @mohamedmansour; perf: avoid cloning nested route trees #513 by @mohamedmansour). - Move streaming command deserialization, validation, and default preparation onto the existing blocking renderer, transferring owned records and state rather than cloning retained projections (perf: move streaming command processing off async workers #529 by @mohamedmansour). - Bound watcher hashing content storage to one reusable 8 KiB buffer instead of repeated file-sized allocations while preserving invalidation behavior (perf: bound watcher hashing memory with reusable scratch #530 by @mohamedmansour). - Parallelize release WASM builds and artifact staging, and consolidate dependency scanning before release jobs (chore: parallelize release pipeline work #509 by @mohamedmansour). - Refresh compatible Rust and JavaScript dependencies and migrate repository tooling and CI to pnpm 12.3.4 while preserving existing audit constraints (chore: update dependencies to latest compatible versions #534 by @mohamedmansour). ## Validation - `cargo xtask check` - `pnpm --dir crates/webui-press test` (16 passed). - `pnpm --dir packages/webui-framework exec playwright test recursive-repeat --workers=2 --reporter=line` (6 passed). - `pnpm --dir packages/webui-router exec playwright test --grep "view transition rejection ownership" --workers=2 --reporter=line` (4 passed). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Why
Render tree-shaped state by reusing one loop body instead of duplicating markup or introducing recursive component definitions.
Closes #518
Approach
<id>-<owner index>. References within that file reuse its protocol fragment and native client block index; other components can use the same ID independently.duplicate-for-id; missing definitions and mismatched item names also fail at build time.Compatibility
idreplaces the legacytemplateattribute, which now produces a migration diagnostic.eachaccepts only unbraced syntax such aseach="item in items". FAST component builds reject named references with an actionable diagnostic; SSR and the native WebUI client support them.Validation
cargo xtask checkpassed, including workspace tests, linting, dependency audit, protocol drift, native/WASM builds, examples, benchmark validation, and docs.