Skip to content

Commit 6c551d9

Browse files
serpentbladeclaude
andcommitted
fix(16): svelte :style switches to string-form when auto-fallthrough active
ITEM-2-RESIDUAL closure. Fully closes the Svelte arm of PHASE_14_1_FOLLOWUP — matrix VR cell now lands on baseline (was 7 085 px diff post-Item-2); all 12 themed-button × svelte cells pass; 'svelte' removed from PHASE_14_1_FOLLOWUP_TARGETS entirely. Full VR matrix: 253 passed, 1 skipped (down from 13 skipped — the lone remaining skip is the Solid 20px width residual, which has a different root cause). Root cause. Svelte 5's compiled output places per-property `style:<prop>=` directive state under a Symbol-keyed slot processed AFTER any spread inside the generated props object. The original Phase-5 design ("wrapper directives win over spread style") was correct for the wrapper-defaults-survive-no- matter-what use case. But for cross-target auto-fallthrough — where the consumer's spread `style="..."` MUST win over the wrapper's `:style="{...}"` defaults to match Vue/React/Solid/Lit/Angular — the directive precedence was exactly wrong. Fix. Detect bare-`$attrs` spreadBinding on the same element in `emitElement`; thread the flag through `EmitAttrCtx.hasFallthroughSpread`. When set, `tryEmitStyleObjectLiteral` routes the literal-object `:style` emit through a NEW string-form path: `style="prop: value; prop2: value2"` instead of per-property `style:<prop>={value}` directives. String literals splice verbatim; dynamic expressions splice as Svelte template-literal interpolation (`style="--btn-bg: {bgExpr}"`). Consumer's spread `style="..."` then overwrites the wrapper's string-form attribute via `setAttribute`, restoring cross-target consumer-wins precedence. Wrapper's un-overridden defaults survive via the `var(--prop, fallback)` CSS fallback the wrappers idiomatically declare (`color: var(--btn-fg, #ffffff)`). When the element is NOT a fallthrough root, the original `style:<prop>=` directive lowering is preserved — Svelte's per-key reactivity is the better choice for non-fallthrough cases. Files: - packages/targets/svelte/src/emit/emitTemplateAttribute.ts — `EmitAttrCtx.hasFallthroughSpread` new field; new branch in `tryEmitStyleObjectLiteral` that emits string-form when active; handles string-literal, numeric-literal, and dynamic-expression property values - packages/targets/svelte/src/emit/emitTemplateNode.ts — computes `hasFallthroughSpread` from `node.attributes` (bare-`$attrs` Identifier in any spreadBinding) and threads to `emitAttributes` - packages/targets/svelte/src/__tests__/emitTemplateAttribute.test.ts — the 3 existing `:style` literal-object tests now use `inherit-attrs="false"` to opt OUT of auto-fallthrough (isolating the pre-residual `style:` directive lowering); 2 NEW tests cover the Item-2-residual string-form path (string-literal values + dynamic-expression interpolation) - tests/dist-parity/fixtures/ThemedButton*.svelte — rebless the 4 wrapper fixtures whose `:style="{...}"` defaults now emit as `style="--btn-bg: #3b82f6; --btn-fg: #ffffff"` instead of `style:--btn-bg={'#3b82f6'} style:--btn-fg={'#ffffff'}` - matrix.spec.ts PHASE_14_1_FOLLOWUP set — `'ThemedButtonConsumer::svelte'` REMOVED; closure comment captures the two-stage fix path - themed-button.spec.ts PHASE_14_1_FOLLOWUP_TARGETS — set now empty; closure comment notes Item 2 + Item-2-residual closed the Svelte arm Verification: - turbo run typecheck --force --continue: 47/47 green - turbo run test --continue: 46/46 green (target-svelte 216/216, dist-parity 512/512) - svelte-vite test:e2e: 14/14 green - tools/ci-repro/vr.sh -g ThemedButtonConsumer.*svelte|themed-button.*svelte: 12/12 cells pass (was 0/12 pre-fix; 10/12 post-Item-2) - tools/ci-repro/vr.sh (full matrix): 253 passed, 1 skipped, 0 failed — only the Solid 20 px width residual remains gated The Item-2-residual closure leaves Rozie's cross-target attribute- fallthrough story FULLY symmetric across all 5 non-Lit targets: a consumer-passed `style="…"` always wins over the wrapper's `:style="{…}"` defaults regardless of target. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1a07523 commit 6c551d9

9 files changed

Lines changed: 148 additions & 66 deletions

File tree

packages/targets/svelte/src/__tests__/emitTemplateAttribute.test.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,16 @@ function lowerInline(src: string, filename = 'Test.rozie'): IRComponent {
2828
}
2929

3030
describe('emitTemplateAttribute — `:style` literal-object lowering (Svelte / Spike 004 subset)', () => {
31+
// `inherit-attrs="false"` on each of these literal-object `:style` tests
32+
// opts the single-root element OUT of `synthesizeAttrsFallthrough`. Without
33+
// the opt-out, pre-Phase-16 cleanup Item-2-residual routes `:style="{...}"`
34+
// through the new string-form path so the consumer's spread `style="..."`
35+
// value can overwrite the wrapper's defaults (cross-target consumer-wins
36+
// precedence). These three tests isolate the pre-residual `style:` directive
37+
// lowering; the dedicated `…interacts with auto-fallthrough` test below
38+
// covers the new string-form path.
3139
it('single-key literal object: `:style="{ background: \'#f00\' }"` → `style:background={\'#f00\'}`', () => {
32-
const ir = lowerInline(`<rozie name="Test">
40+
const ir = lowerInline(`<rozie name="Test" inherit-attrs="false">
3341
<template>
3442
<span :style="{ background: '#f00' }"></span>
3543
</template>
@@ -44,7 +52,7 @@ describe('emitTemplateAttribute — `:style` literal-object lowering (Svelte / S
4452
});
4553

4654
it('multi-key literal object: `:style="{ background: x, color: y }"` → two style: directives', () => {
47-
const ir = lowerInline(`<rozie name="Test">
55+
const ir = lowerInline(`<rozie name="Test" inherit-attrs="false">
4856
<data>{ x: '#f00', y: '#0f0' }</data>
4957
<template>
5058
<span :style="{ background: $data.x, color: $data.y }"></span>
@@ -58,7 +66,7 @@ describe('emitTemplateAttribute — `:style` literal-object lowering (Svelte / S
5866
});
5967

6068
it('camelCase key kebabizes: `:style="{ backgroundColor: x }"` → `style:background-color={x}`', () => {
61-
const ir = lowerInline(`<rozie name="Test">
69+
const ir = lowerInline(`<rozie name="Test" inherit-attrs="false">
6270
<data>{ x: '#f00' }</data>
6371
<template>
6472
<span :style="{ backgroundColor: $data.x }"></span>
@@ -69,6 +77,51 @@ describe('emitTemplateAttribute — `:style` literal-object lowering (Svelte / S
6977
expect(template).toContain('style:background-color={x}');
7078
});
7179

80+
it('Item-2-residual: literal-object `:style` switches to string-form `style="..."` when auto-fallthrough is active on the same element', () => {
81+
// Pre-Phase-16 cleanup Item-2-residual — when a single-root html element
82+
// is `synthesizeAttrsFallthrough`-eligible (the default — `inherit-attrs`
83+
// is true), the lowering swaps `style:<prop>={value}` directives for a
84+
// string-form `style="prop: value"` attribute. Background: Svelte's
85+
// compiled output places per-property `style:` directive state under a
86+
// Symbol-keyed slot processed AFTER any spread inside the generated props
87+
// object, so directives win over spread `style` — the OPPOSITE precedence
88+
// the other 5 targets implement. String-form lets the consumer's spread
89+
// `style` value overwrite the wrapper's defaults via `setAttribute`,
90+
// restoring cross-target parity. The wrapper's un-overridden defaults
91+
// survive via the wrapper's `var(--prop, fallback)` CSS fallback.
92+
const ir = lowerInline(`<rozie name="Test">
93+
<template>
94+
<button :style="{ '--btn-bg': '#3b82f6', '--btn-fg': '#ffffff' }"></button>
95+
</template>
96+
</rozie>`);
97+
const { template, diagnostics } = emitTemplate(ir, REGISTRY);
98+
expect(diagnostics).toEqual([]);
99+
// String-form style attribute with both custom properties spliced.
100+
expect(template).toContain('style="--btn-bg: #3b82f6; --btn-fg: #ffffff"');
101+
// No `style:` directives (the Item-2-residual switch suppresses them).
102+
expect(template).not.toMatch(/\bstyle:--btn-bg=/);
103+
expect(template).not.toMatch(/\bstyle:--btn-fg=/);
104+
// Auto-fallthrough fired (sanity check).
105+
expect(template).toContain('{...__rozieAttrs}');
106+
});
107+
108+
it('Item-2-residual: dynamic-value literal-object `:style` interpolates expressions into the string', () => {
109+
// Same fallthrough condition as above, but the object values are
110+
// identifiers/expressions rather than string literals. The rewriter
111+
// splices each value via Svelte template-literal interpolation in the
112+
// attribute string: `style="prop: {expr}; prop2: {expr2}"`.
113+
const ir = lowerInline(`<rozie name="Test">
114+
<data>{ bg: '#3b82f6', fg: '#ffffff' }</data>
115+
<template>
116+
<button :style="{ '--btn-bg': $data.bg, '--btn-fg': $data.fg }"></button>
117+
</template>
118+
</rozie>`);
119+
const { template, diagnostics } = emitTemplate(ir, REGISTRY);
120+
expect(diagnostics).toEqual([]);
121+
expect(template).toContain('style="--btn-bg: {bg}; --btn-fg: {fg}"');
122+
expect(template).not.toMatch(/\bstyle:--btn-bg=/);
123+
});
124+
72125
it('non-object binding falls through to existing passthrough: `:style="$data.s"` → `style={s}`', () => {
73126
const ir = lowerInline(`<rozie name="Test">
74127
<data>{ s: 'background: red' }</data>

packages/targets/svelte/src/emit/emitTemplateAttribute.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,23 @@ export interface EmitAttrCtx {
5252
* `<input>` to text — `bind:value` is correct there).
5353
*/
5454
inputType?: string;
55+
/**
56+
* Pre-Phase-16 cleanup Item-2-residual — true when the host element ALSO
57+
* carries a bare-`$attrs` `spreadBinding` (attribute auto-fallthrough is
58+
* active). When this is set, `:style="{...}"` object-literal lowering
59+
* switches from `style:<prop>={value}` directives to a string-form
60+
* `style="prop: value; ..."` attribute. Background: Svelte's compiled
61+
* output places per-property `style:` directive state under a Symbol-keyed
62+
* slot processed AFTER any spread inside the generated props object, so
63+
* `style:` directives intentionally win over spread `style` — the OPPOSITE
64+
* precedence the other 5 targets implement for the auto-fallthrough case.
65+
* Re-emitting the wrapper's `:style` defaults as a string attribute lets
66+
* the consumer's spread `style="..."` value overwrite them via
67+
* `setAttribute('style', ...)`, restoring cross-target parity. Wrapper's
68+
* un-overridden defaults survive via the `var(--prop, fallback)` CSS
69+
* fallback the wrappers idiomatically already declare.
70+
*/
71+
hasFallthroughSpread?: boolean;
5572
}
5673

5774
/**
@@ -260,11 +277,52 @@ function objectPropertyKeyAsString(
260277
function tryEmitStyleObjectLiteral(
261278
attr: AttributeBinding,
262279
ir: IRComponent,
280+
ctx?: EmitAttrCtx,
263281
): string | null {
264282
if (attr.kind !== 'binding') return null;
265283
if (attr.name !== 'style') return null;
266284
if (!t.isObjectExpression(attr.expression)) return null;
267285

286+
// Pre-Phase-16 Item-2-residual — when auto-fallthrough is active on this
287+
// element, the consumer's spread `style="..."` value would otherwise lose
288+
// to the wrapper's `style:<prop>=` directives (Svelte's STYLES_KEY runs
289+
// after spread by design). Emit the wrapper's defaults as a string-form
290+
// `style="prop: value; ..."` attribute instead — the spread's
291+
// setAttribute then overwrites it cleanly, restoring cross-target
292+
// consumer-wins precedence. Bail to null (caller falls through to the
293+
// generic `style={<expr>}` passthrough) if any property value is too
294+
// complex to safely serialise to a CSS declaration string.
295+
if (ctx?.hasFallthroughSpread) {
296+
const parts: string[] = [];
297+
for (const prop of attr.expression.properties) {
298+
if (!t.isObjectProperty(prop)) return null;
299+
const keyName = objectPropertyKeyAsString(prop);
300+
if (keyName === null) return null;
301+
if (!t.isExpression(prop.value)) return null;
302+
const cssProp = kebabizeStyleKey(keyName);
303+
// String-literal value → splice the value text verbatim (no quoting in
304+
// CSS — declarations are not JS).
305+
if (t.isStringLiteral(prop.value)) {
306+
parts.push(`${cssProp}: ${prop.value.value}`);
307+
continue;
308+
}
309+
// Numeric-literal value → splice as bare number (matches Svelte's
310+
// implicit `${n}px`-less serialization; for CSS custom properties the
311+
// value is a number-literal which is valid).
312+
if (t.isNumericLiteral(prop.value)) {
313+
parts.push(`${cssProp}: ${prop.value.value}`);
314+
continue;
315+
}
316+
// Dynamic value → Svelte template-literal interpolation in the
317+
// attribute string: `style="--btn-bg: {bgExpr}"`. Reuse the standard
318+
// template-expression rewriter (handles $props.X, $data.X, etc.).
319+
const exprCode = rewriteTemplateExpression(prop.value, ir);
320+
parts.push(`${cssProp}: {${exprCode}}`);
321+
}
322+
if (parts.length === 0) return 'style=""';
323+
return `style="${parts.join('; ')}"`;
324+
}
325+
268326
const directives: string[] = [];
269327
for (const prop of attr.expression.properties) {
270328
// Bail on spreads / methods / computed keys — caller falls through to
@@ -585,7 +643,7 @@ export function emitSingleAttr(
585643
// serialize the object via toString() to `[object Object]`. Falls
586644
// through to the default attribute emit for non-literal-object exprs
587645
// (string form is handled natively by Svelte).
588-
const styleObjectLowered = tryEmitStyleObjectLiteral(attr, ir);
646+
const styleObjectLowered = tryEmitStyleObjectLiteral(attr, ir, ctx);
589647
if (styleObjectLowered !== null) return styleObjectLowered;
590648
const expr = rewriteTemplateExpression(attr.expression, ir);
591649
const outName = resolveAttrName(attr.name, ctx);

packages/targets/svelte/src/emit/emitTemplateNode.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,12 +325,29 @@ function emitElement(node: TemplateElementIR, ctx: EmitNodeCtx): string {
325325
}
326326
}
327327
}
328+
// Pre-Phase-16 Item-2-residual — detect a bare-`$attrs` spreadBinding on
329+
// the same element. When present, `:style="{...}"` object-literal lowering
330+
// routes through the string-form `style="..."` path so the consumer's
331+
// spread `style` value can overwrite the wrapper's defaults (the
332+
// cross-target consumer-wins precedence). See EmitAttrCtx.hasFallthroughSpread.
333+
let hasFallthroughSpread = false;
334+
for (const a of node.attributes) {
335+
if (
336+
a.kind === 'spreadBinding' &&
337+
a.expression.type === 'Identifier' &&
338+
a.expression.name === '$attrs'
339+
) {
340+
hasFallthroughSpread = true;
341+
break;
342+
}
343+
}
328344
const attrText = emitAttributes(node.attributes, {
329345
ir: ctx.ir,
330346
elementTagKind: node.tagKind,
331347
// Spread only when defined — `exactOptionalPropertyTypes` rejects an
332348
// explicit `inputType: undefined` against the optional `inputType?` field.
333349
...(inputType !== undefined ? { inputType } : {}),
350+
...(hasFallthroughSpread ? { hasFallthroughSpread: true } : {}),
334351
});
335352

336353
// Phase 15 R6 — assemble the per-element listener emit. Literal-key

tests/dist-parity/fixtures/ThemedButton.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ let {
1515
</script>
1616

1717

18-
<button style:--btn-bg={'#3b82f6'} style:--btn-fg={'#ffffff'} {...__rozieAttrs} class={["btn", variant, (__rozieAttrs)?.class]} use:applyListeners={__rozieAttrs} data-rozie-s-7914ecaa>
18+
<button style="--btn-bg: #3b82f6; --btn-fg: #ffffff" {...__rozieAttrs} class={["btn", variant, (__rozieAttrs)?.class]} use:applyListeners={__rozieAttrs} data-rozie-s-7914ecaa>
1919
{label}
2020
</button>
2121

tests/dist-parity/fixtures/ThemedButtonAllManual.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ let {
1515
</script>
1616

1717

18-
<button style:--btn-bg={'#3b82f6'} style:--btn-fg={'#ffffff'} {...__rozieAttrs} class={["btn", variant, (__rozieAttrs)?.class]} use:applyListeners={__rozieAttrs} data-rozie-s-de172510>
18+
<button style="--btn-bg: #3b82f6; --btn-fg: #ffffff" {...__rozieAttrs} class={["btn", variant, (__rozieAttrs)?.class]} use:applyListeners={__rozieAttrs} data-rozie-s-de172510>
1919
{label}
2020
</button>
2121

tests/dist-parity/fixtures/ThemedButtonListenersManual.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ let {
1515
</script>
1616

1717

18-
<button style:--btn-bg={'#3b82f6'} style:--btn-fg={'#ffffff'} {...__rozieAttrs} class={["btn", variant, (__rozieAttrs)?.class]} use:applyListeners={__rozieAttrs} data-rozie-s-97e125bc>
18+
<button style="--btn-bg: #3b82f6; --btn-fg: #ffffff" {...__rozieAttrs} class={["btn", variant, (__rozieAttrs)?.class]} use:applyListeners={__rozieAttrs} data-rozie-s-97e125bc>
1919
{label}
2020
</button>
2121

tests/dist-parity/fixtures/ThemedButtonManual.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ let {
1515
</script>
1616

1717

18-
<button style:--btn-bg={'#3b82f6'} style:--btn-fg={'#ffffff'} {...__rozieAttrs} class={["btn", variant, (__rozieAttrs)?.class]} use:applyListeners={__rozieAttrs} data-rozie-s-671f0616>
18+
<button style="--btn-bg: #3b82f6; --btn-fg: #ffffff" {...__rozieAttrs} class={["btn", variant, (__rozieAttrs)?.class]} use:applyListeners={__rozieAttrs} data-rozie-s-671f0616>
1919
{label}
2020
</button>
2121

tests/visual-regression/specs/matrix.spec.ts

Lines changed: 7 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -401,47 +401,13 @@ const PHASE_14_1_FOLLOWUP = new Set<string>([
401401
// kerns identically, but a single specific case may not).
402402
// None are blocking; carried into a deeper post-Phase-16 investigation.
403403
'ThemedButtonConsumer::solid',
404-
// Pre-Phase-16 cleanup Item 2 PARTIAL closure (2026-05-23). The original
405-
// gate hypothesis was correct: Svelte uses native class-hash CSS scoping
406-
// (`.foo.svelte-XXX`) which only stamps on elements lexically inside the
407-
// same SFC compile unit, so the consumer's `.extra-variant.svelte-CONSUMER`
408-
// rule never matched the wrapper's inner button (which carried
409-
// `.svelte-WRAPPER`). Item 2 implemented fix-path (b) — switched Rozie's
410-
// Svelte CSS pipeline off Svelte's native scoper onto `data-rozie-s-*`,
411-
// mirroring react/solid/lit:
412-
// - `packages/targets/svelte/src/emit/scopeCss.ts` (new) — selector
413-
// rewriter, copy of the react sibling
414-
// - `emitStyle.ts` — wraps scoped rules in `:global { ... }` block to
415-
// opt out of Svelte's native scoper; `PORTAL_SCOPE_REPEAT` drops 1→0
416-
// - `emitTemplate.ts` + `emitTemplateNode.ts` — threads `scopeAttr` and
417-
// stamps `data-rozie-s-<hash>` on every element AND every component-
418-
// tag invocation (cross-SFC propagation path)
419-
// - `emitSvelte.ts` — derives `scopeAttr` from the existing
420-
// `portalScopeHash` (no new hash computation)
421-
//
422-
// Post-Item-2 the cell improves 9 457 → 7 085 px diff (25% reduction);
423-
// `.extra-variant` font-weight (bold) renders correctly, `.btn` background
424-
// applies, all four buttons present in the right layout. 10 of 11
425-
// themed-button × svelte cells now pass (DOM-class assertions, listener
426-
// attachments, etc.).
427-
//
428-
// RESIDUAL — the per-button color overrides (consumer-passed
429-
// `style="--btn-bg: #ef4444"` etc.) do not win over the wrapper's
430-
// `style:--btn-bg={'#3b82f6'}` directive. Root cause is an established
431-
// Svelte 5 semantic (see `emitTemplateAttribute.ts:401-406`): Svelte's
432-
// compiled output places per-property `style:` directive state under a
433-
// Symbol-keyed slot processed AFTER any spread inside the generated props
434-
// object, so wrapper directives intentionally win over spread style. The
435-
// other 5 targets (Vue/React/Solid/Lit/Angular) all run the OPPOSITE
436-
// precedence (consumer wins), which is the cross-target intent for
437-
// attribute fallthrough. Closing this requires switching Svelte's `:style`
438-
// object-literal lowering OFF the `style:<prop>=` directive form (when
439-
// auto-fallthrough is active on the same element) ONTO either a
440-
// string-attribute emit or a runtime style-merge helper. Tracked as
441-
// Item-2-residual into a post-Phase-16 sweep — see also
442-
// `themed-button.spec.ts` PHASE_14_1_FOLLOWUP_TARGETS for the
443-
// `consumer style="--btn-bg" overrides...` test that still fails.
444-
'ThemedButtonConsumer::svelte',
404+
// Svelte arm FULLY CLOSED (2026-05-24) — Item 2 (cross-SFC CSS-scoping
405+
// switch to `data-rozie-s-*`) + Item-2-residual (auto-fallthrough-aware
406+
// `:style` string-form lowering, when active, instead of `style:<prop>=`
407+
// directives that would otherwise win over spread `style`). All 12
408+
// themed-button × svelte cells pass and the matrix VR cell lands on
409+
// baseline. See commit history for the full path through the two-stage
410+
// fix. No remaining svelte entries in this set.
445411
]);
446412

447413
for (const example of EXAMPLES) {

tests/visual-regression/specs/themed-button.spec.ts

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -72,23 +72,11 @@ const FOUR_CORNERS = [
7272

7373
// Phase-N.M follow-up gate — companion to matrix.spec.ts's PHASE_14_1_FOLLOWUP
7474
// set. Phase 14.1 closed 3 of 5 ThemedButtonConsumer entries (react / angular
75-
// / lit). Pre-Phase-16 cleanup closed the Solid arm here (DOM-assertion cells
76-
// pass after the emit-redundancy fix — see commit history). Svelte remains.
77-
const PHASE_14_1_FOLLOWUP_TARGETS = new Set<string>([
78-
// Svelte: pre-Phase-16 Item 2 closed the cross-SFC CSS-scoping gap (the
79-
// 10 class/listener/attribute assertion cells below now pass), but the
80-
// `consumer style="--btn-bg" overrides the wrapper's default` test still
81-
// fails on a residual issue: Svelte's `style:<prop>=` directive (used to
82-
// emit the wrapper's `:style="{...}"` defaults) wins over consumer-
83-
// passed `style="..."` strings inside the auto-fallthrough spread. This
84-
// is the OPPOSITE precedence the other 5 targets implement. Closing it
85-
// requires switching Svelte's :style lowering off the `style:` directive
86-
// form when auto-fallthrough is active. Tracked as Item-2-residual; see
87-
// matrix.spec.ts PHASE_14_1_FOLLOWUP gate for the full rationale. Whole
88-
// svelte arm stays gated because the test-list here is a binary
89-
// per-target set; switching to per-test gating is a separate refactor.
90-
'svelte',
91-
]);
75+
// / lit). Pre-Phase-16 cleanup closed the remaining two arms — Solid via the
76+
// dual-spread emit fix (Item 1) and Svelte via the cross-SFC CSS-scoping
77+
// switch (Item 2) + auto-fallthrough-aware `:style` lowering (Item-2-residual).
78+
// Set is empty; preserved for future per-target gate additions.
79+
const PHASE_14_1_FOLLOWUP_TARGETS = new Set<string>([]);
9280

9381
for (const target of TARGETS) {
9482
const built = existsSync(

0 commit comments

Comments
 (0)