Skip to content

Fix @aware resolution when folding across template boundaries#189

Open
calebporzio wants to merge 2 commits into
mainfrom
fix/flux-select-folding
Open

Fix @aware resolution when folding across template boundaries#189
calebporzio wants to merge 2 commits into
mainfrom
fix/flux-select-folding

Conversation

@calebporzio

Copy link
Copy Markdown
Contributor

Fixes #186

Root cause

Two separate bugs, both surfaced in #186:

1. Folding bakes in wrong @aware values across template boundaries (the actual <flux:select> crash).

When a foldable component that consumes @aware props sits at the root of a template — i.e. it's rendered through an @include partial or from a nested component's view — its real parent only exists at runtime. The folder resolved @aware lexically, found nothing, and baked in the prop defaults.

Concretely: <flux:select.option> declares @aware(['variant', 'indicator']). Rendered through a partial inside <flux:select variant="listbox">, it folded to the default variant's native <option> markup instead of the listbox's <ui-option> markup. flux.js then finds no & > ui-option children in the listbox, treats the list as empty, and hits create._activatable.activate(true) in a state it never expects — the uncaught TypeError kills init for every select on the page. This matches the reporter's repro matrix exactly (inline children fold correctly because the parent is lexically visible; slot/include boundaries crash).

2. The temporary fold cache goes stale across processes (Undefined array key at Unblaze.php:87).

Since #116 the temporary fold cache persists between compiles, but Unblaze::$unblazeReplacements only ever lived in memory in the process that compiled the file. Any other process reusing a cached compiled file (with [STARTCOMPILEDUNBLAZE:…] tokens baked in) missed the lookup, logged the undefined key warning the reporter found, and silently aborted the fold.

Fix

  • The folder no longer folds a component whose @aware props can't be resolved from the template itself (not passed directly, no value from a lexically visible parent, and no component ancestors in the template) — it renders normally and @aware resolves at runtime.
  • hasAwareDescendant() now also detects @aware consumers hiding behind @include/@each and nested component templates (recursive source scan), so folded parents still push their data for that runtime lookup.
  • @unblaze replacement content is now stored from within the compiled file itself (base64-encoded), so any process that renders a cached file repopulates the replacement map. Stale files from older versions purge the temp cache and abort the fold gracefully once.

Tests

  • ComparisonTest: @aware through an include partial and through a nested component (both fail on main, byte-identical to Blade after the fix).
  • FluxProTest: the exact @blaze folding breaks Flux <flux:select> — uncaught _activatable TypeError destroys all selects on the page #186 scenario — <flux:select variant="listbox" searchable> with flux:select.option + flux:select.option.create emitted from an @include and from a nested component.
  • FolderTest: unfoldable at template root with unresolvable @aware; still folds when the prop is passed directly.
  • UnblazeTest: fold succeeds when the temp compiled file was cached by a previous process (fails on main with the reporter's exact Unblaze.php:87 error).

Full suite: 220 passed (including flux + flux-pro comparison suites run locally).

🤖 Generated with Claude Code

calebporzio and others added 2 commits July 5, 2026 07:41
When a foldable component that consumes @AWare props is compiled at the
root of a template (an @include partial or a nested component's view),
its real parent is only knowable at runtime, so folding baked in the
prop defaults. A <flux:select.option> rendered through a partial inside
a <flux:select variant="listbox"> folded to the default variant's
markup instead of the listbox variant's.

Now such components are left unfolded so @AWare resolves at runtime,
and folded parents detect @AWare consumers behind includes and nested
component templates so they push their data for that runtime lookup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Since the temporary fold cache started persisting between compiles, a
process reusing a cached compiled file had no in-memory replacement for
the @unblaze tokens baked into it, causing an undefined array key at
render and silently aborting the fold. Store the replacement content in
the compiled file itself so any process that renders it repopulates the
map, and purge the cache when a stale token from an older format is
encountered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Benchmark Result: Default

Attempt Blade Blaze Improvement
#1 351.25ms 15.89ms 95.5%
#2 350.29ms 15.66ms 95.5%
#3 350.60ms 15.60ms 95.6%
#4 347.62ms 15.52ms 95.5%
#5 349.67ms 15.69ms 95.5%
#6 * 355.23ms 16.22ms 95.4%
#7 * 355.36ms 15.83ms 95.5%
#8 350.30ms 15.74ms 95.5%
#9 350.09ms 15.51ms 95.6%
#10 348.18ms 15.85ms 95.4%
Snapshot 351.19ms 15.83ms 95.5%
Result 350.19ms (~) 15.68ms (~) 95.5% (~)

Median of 10 attempts (* = outlier, excluded from result), 5000 iterations x 10 rounds, 46.16s total

To run a specific benchmark, comment /benchmark <name> where name is one of: attributes, aware, class, default, forwarding, merge, named-slots, no-attributes, slot

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.

@blaze folding breaks Flux <flux:select> — uncaught _activatable TypeError destroys all selects on the page

1 participant