@rozie/babel-plugin 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.cjs +1023 -32
  2. package/dist/index.mjs +1023 -32
  3. package/package.json +2 -2
package/dist/index.cjs CHANGED
@@ -42510,6 +42510,66 @@ function subtreeReads(node, accessor, name) {
42510
42510
  return found;
42511
42511
  }
42512
42512
  /**
42513
+ * Returns true if the subtree rooted at `node` contains a BARE Identifier READ of
42514
+ * `name` — the exact shape the Vue `$computed` lowering wraps to `<name>.value`.
42515
+ *
42516
+ * The Vue trigger condition for the `$computed` shadow bug: a `$computed` name is
42517
+ * read as a BARE identifier in source (there is NO `$computed.<name>` accessor
42518
+ * member to gate on), and the downstream Identifier visitor `.value`-wraps every
42519
+ * such bare read. A colliding param/local only mis-captures that wrap when it
42520
+ * actually performs a bare read of `name` within its scope — so a `binding`
42521
+ * trigger (mere existence) would over-apply (renaming an unused same-named param
42522
+ * → corpus drift), and the `accessor` trigger cannot fire (no member access).
42523
+ *
42524
+ * Excludes the SAME non-read positions the Vue Identifier visitor skips, so the
42525
+ * gate matches the rewrite exactly: declaration ids, non-computed member
42526
+ * properties, object keys (shorthand or not), TS type positions, function names,
42527
+ * function params, and import/export specifier ids. A computed member property
42528
+ * (`obj[name]`) IS a bare read and counts.
42529
+ *
42530
+ * Hand-rolled own-child walk (the `subtreeReads` twin): a direct walk with
42531
+ * parent-context tracking is simpler than a Program-rooted Babel traverse and
42532
+ * has no rooting constraint.
42533
+ */
42534
+ function subtreeReadsBareIdentifier(node, name) {
42535
+ if (!node) return false;
42536
+ let found = false;
42537
+ function walk(n, parent) {
42538
+ if (found || !n || typeof n !== "object" || !("type" in n)) return;
42539
+ if (_babel_types.isIdentifier(n) && n.name === name) {
42540
+ if (isBareRead(n, parent)) {
42541
+ found = true;
42542
+ return;
42543
+ }
42544
+ }
42545
+ if (n.type.startsWith("TS") && n.type !== "TSNonNullExpression" && n.type !== "TSAsExpression" && n.type !== "TSSatisfiesExpression" && n.type !== "TSTypeAssertion") return;
42546
+ for (const key of Object.keys(n)) {
42547
+ if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments" || key === "innerComments") continue;
42548
+ const v = n[key];
42549
+ if (Array.isArray(v)) {
42550
+ for (const item of v) if (item && typeof item === "object" && "type" in item) walk(item, n);
42551
+ } else if (v && typeof v === "object" && "type" in v) walk(v, n);
42552
+ }
42553
+ }
42554
+ function isBareRead(id, parent) {
42555
+ if (!parent) return true;
42556
+ if (_babel_types.isVariableDeclarator(parent) && parent.id === id) return false;
42557
+ if ((_babel_types.isMemberExpression(parent) || _babel_types.isOptionalMemberExpression(parent)) && parent.property === id && !parent.computed) return false;
42558
+ if (_babel_types.isObjectProperty(parent) && parent.key === id && !parent.computed) return false;
42559
+ if (_babel_types.isFunctionDeclaration(parent) && parent.id === id) return false;
42560
+ if (_babel_types.isFunctionExpression(parent) && parent.id === id) return false;
42561
+ if (_babel_types.isFunction(parent) && parent.params.includes(id)) return false;
42562
+ if (_babel_types.isImportSpecifier(parent)) return false;
42563
+ if (_babel_types.isImportDefaultSpecifier(parent)) return false;
42564
+ if (_babel_types.isImportNamespaceSpecifier(parent)) return false;
42565
+ if (_babel_types.isExportSpecifier(parent)) return false;
42566
+ if (_babel_types.isLabeledStatement(parent) && parent.label === id) return false;
42567
+ return true;
42568
+ }
42569
+ walk(node, null);
42570
+ return found;
42571
+ }
42572
+ /**
42513
42573
  * THE UNIFIED PASS. Renames any USER binding (function param or
42514
42574
  * `const`/`let`/`var` declarator) that collides with a generated symbol to
42515
42575
  * `<name>$local`, atomically across its binding scope, gated only-on-collision
@@ -42529,6 +42589,7 @@ function deconflictGeneratedSymbols(program, groups, protectedNames = /* @__PURE
42529
42589
  if (!group.names.has(name)) return false;
42530
42590
  if (protectedNames.has(name)) return false;
42531
42591
  if (group.trigger.kind === "binding") return true;
42592
+ if (group.trigger.kind === "bare-read") return subtreeReadsBareIdentifier(scopeBlock, name);
42532
42593
  return subtreeReads(scopeBlock, group.trigger.accessor, name);
42533
42594
  };
42534
42595
  traverse$19(program, {
@@ -42542,6 +42603,7 @@ function deconflictGeneratedSymbols(program, groups, protectedNames = /* @__PURE
42542
42603
  if (!patternIntroducesBinding$3(id, name)) continue;
42543
42604
  const binding = path.scope.getBinding(name);
42544
42605
  const ownerScope = binding ? binding.scope : path.scope;
42606
+ if (group.trigger.kind === "bare-read" && _babel_types.isProgram(ownerScope.block)) continue;
42545
42607
  if (collides(group, name, ownerScope.block)) ownerScope.rename(name, alias(name));
42546
42608
  }
42547
42609
  },
@@ -51167,17 +51229,53 @@ function bindingNames(stmt) {
51167
51229
  function importLocalNames(imp) {
51168
51230
  return imp.specifiers.map((s) => s.local.name);
51169
51231
  }
51232
+ /**
51233
+ * Root identifier of a TS entity name (`Foo` or `Foo.Bar.Baz` → `Foo`). A
51234
+ * `TSImportType` (`import('pkg').Foo`) has no root local identifier — returns
51235
+ * null (its module surface is already self-contained, never a hoist target).
51236
+ */
51237
+ function rootTypeIdentifier(name) {
51238
+ let node = name;
51239
+ while (node && _babel_types.isTSQualifiedName(node)) node = node.left;
51240
+ return node && _babel_types.isIdentifier(node) ? node.name : null;
51241
+ }
51170
51242
  /** Referenced identifier names within a statement (excludes bindings/keys). */
51171
51243
  function referencedNames(stmt) {
51172
51244
  const out = /* @__PURE__ */ new Set();
51173
51245
  try {
51174
- traverse$18(_babel_types.file(_babel_types.program([stmt])), { ReferencedIdentifier(path) {
51175
- out.add(path.node.name);
51176
- } });
51246
+ traverse$18(_babel_types.file(_babel_types.program([stmt])), {
51247
+ ReferencedIdentifier(path) {
51248
+ out.add(path.node.name);
51249
+ },
51250
+ TSTypeReference(path) {
51251
+ const root = rootTypeIdentifier(path.node.typeName);
51252
+ if (root) out.add(root);
51253
+ },
51254
+ TSTypeQuery(path) {
51255
+ const root = rootTypeIdentifier(path.node.exprName);
51256
+ if (root) out.add(root);
51257
+ }
51258
+ });
51177
51259
  } catch {}
51178
51260
  return out;
51179
51261
  }
51180
51262
  /**
51263
+ * Effective import kind of a single specifier. A specifier is type-only when
51264
+ * EITHER the declaration is `import type { … }` (declKind === 'type', in which
51265
+ * case Babel reports the per-specifier `importKind` as `'value'`) OR the
51266
+ * specifier itself is the inline `import { type X }` form (spec.importKind ===
51267
+ * 'type'). The old `spec.importKind ? spec.importKind : declKind` form let the
51268
+ * `'value'` per-specifier kind of a declaration-level `import type` mask the
51269
+ * declaration's type-ness, so a hoisted `import type { T }` lost its type-only
51270
+ * marker and emitted as a runtime `import { T }` (IN-02). Value imports are
51271
+ * unaffected (both kinds are `'value'`).
51272
+ */
51273
+ function effectiveImportKind(declKind, spec) {
51274
+ if (declKind === "type") return "type";
51275
+ if (_babel_types.isImportSpecifier(spec) && spec.importKind === "type") return "type";
51276
+ return "value";
51277
+ }
51278
+ /**
51181
51279
  * Dedup key for a single import specifier per the R4 tuple:
51182
51280
  * (source, importKind, default|namespace|named:imported, local). Including the
51183
51281
  * LOCAL name keeps `import { thing }` and `import { thing as aliased }` distinct
@@ -51185,7 +51283,7 @@ function referencedNames(stmt) {
51185
51283
  * host and partial collapses to ONE statement.
51186
51284
  */
51187
51285
  function specifierKey(source, declKind, spec) {
51188
- const kind = (_babel_types.isImportSpecifier(spec) && spec.importKind ? spec.importKind : declKind) || "value";
51286
+ const kind = effectiveImportKind(declKind, spec);
51189
51287
  if (_babel_types.isImportDefaultSpecifier(spec)) return `${source}\0${kind}\0default\0${spec.local.name}`;
51190
51288
  if (_babel_types.isImportNamespaceSpecifier(spec)) return `${source}\0${kind}\0namespace\0${spec.local.name}`;
51191
51289
  return `${source}\0${kind}\0named:${_babel_types.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value}\0${spec.local.name}`;
@@ -51195,12 +51293,12 @@ function specifierKey(source, declKind, spec) {
51195
51293
  * deduped by {@link specifierKey} and grouped by (source, importKind) so the
51196
51294
  * splice produces idiomatic merged `import { a, b } from 'src'` statements.
51197
51295
  */
51198
- function hoistSpecifier(ctx, sourceNode, declKind, spec) {
51296
+ function hoistSpecifier(ctx, sourceNode, declKind, spec, sourceImport) {
51199
51297
  const source = sourceNode.value;
51200
51298
  const key = specifierKey(source, declKind, spec);
51201
51299
  if (ctx.hoistKeys.has(key)) return;
51202
51300
  ctx.hoistKeys.add(key);
51203
- const groupKind = (_babel_types.isImportSpecifier(spec) && spec.importKind ? spec.importKind : declKind) || "value";
51301
+ const groupKind = effectiveImportKind(declKind, spec);
51204
51302
  const groupKey = `${source}\0${groupKind}`;
51205
51303
  const existing = ctx.hoistGroups.get(groupKey);
51206
51304
  if (existing) {
@@ -51209,9 +51307,427 @@ function hoistSpecifier(ctx, sourceNode, declKind, spec) {
51209
51307
  }
51210
51308
  const decl = _babel_types.importDeclaration([spec], sourceNode);
51211
51309
  if (groupKind === "type") decl.importKind = "type";
51310
+ if (sourceImport) {
51311
+ if (sourceImport.loc) decl.loc = sourceImport.loc;
51312
+ if (sourceImport.trailingComments) decl.trailingComments = sourceImport.trailingComments;
51313
+ if (sourceImport.innerComments) decl.innerComments = sourceImport.innerComments;
51314
+ }
51212
51315
  ctx.hoistGroups.set(groupKey, decl);
51213
51316
  ctx.hoistImports.push(decl);
51214
51317
  }
51318
+ /**
51319
+ * Shift a single Babel `Position` object's `.line` by `offset`, exactly once.
51320
+ *
51321
+ * CRITICAL (Phase 55-04 bug fix): `@babel/parser` SHARES one `Position` object
51322
+ * across the `loc.start`/`loc.end` of nested nodes that begin/end at the same
51323
+ * source position — e.g. a `const f = () => {...}` shares ONE `loc.end` object
51324
+ * between the VariableDeclaration, VariableDeclarator, ArrowFunctionExpression
51325
+ * and BlockStatement (verified: all four `.loc.end` are `===`). The earlier
51326
+ * `seen`-keyed-on-the-`loc`-WRAPPER dedupe therefore shifted such a shared
51327
+ * `Position` once PER wrapping node (4× for the example above), corrupting
51328
+ * `loc.end.line` to `original + 4·offset` (~13985 for a host-line-3502 decl).
51329
+ * That blew the trailing-comment delta hugely negative, collapsing a between-
51330
+ * declaration comment onto the prior closing brace (`}; // comment`). Deduping on
51331
+ * the `Position` object itself shifts each unique position exactly once. Never
51332
+ * throws (D-04).
51333
+ */
51334
+ function shiftPositionLine(pos, offset, shifted) {
51335
+ if (!pos || shifted.has(pos)) return;
51336
+ shifted.add(pos);
51337
+ pos.line += offset;
51338
+ }
51339
+ /**
51340
+ * Stash a node's `.rzts` origin (once, idempotent) then shift its `loc` lines by
51341
+ * `offset`. Byte offsets (`loc.start.index`/`loc.end.index`) and `loc.filename`
51342
+ * are NEVER touched. `shifted` dedupes shared `Position` objects (see
51343
+ * {@link shiftPositionLine}) so a position aliased across nested nodes — or a
51344
+ * comment attached to two adjacent statements — is shifted exactly once
51345
+ * (Pitfall 2). Never throws (D-04).
51346
+ */
51347
+ function stashAndShiftNode(node, offset, shifted) {
51348
+ const loc = node.loc;
51349
+ if (!loc) return;
51350
+ const extra = node.extra ?? {};
51351
+ if (!("__roziePartialOrigin" in extra)) {
51352
+ const startAlreadyShifted = shifted.has(loc.start);
51353
+ node.extra = {
51354
+ ...extra,
51355
+ __roziePartialOrigin: {
51356
+ line: loc.start.line - (startAlreadyShifted ? offset : 0),
51357
+ column: loc.start.column,
51358
+ filename: loc.filename
51359
+ }
51360
+ };
51361
+ }
51362
+ shiftPositionLine(loc.start, offset, shifted);
51363
+ shiftPositionLine(loc.end, offset, shifted);
51364
+ }
51365
+ /** As {@link stashAndShiftNode} but for a comment (origin stashed on the comment). */
51366
+ function stashAndShiftComment(comment, offset, shifted) {
51367
+ const loc = comment.loc;
51368
+ if (!loc) return;
51369
+ const c = comment;
51370
+ if (c.__roziePartialOrigin === void 0) {
51371
+ const startAlreadyShifted = shifted.has(loc.start);
51372
+ c.__roziePartialOrigin = {
51373
+ line: loc.start.line - (startAlreadyShifted ? offset : 0),
51374
+ column: loc.start.column,
51375
+ filename: loc.filename
51376
+ };
51377
+ }
51378
+ shiftPositionLine(loc.start, offset, shifted);
51379
+ shiftPositionLine(loc.end, offset, shifted);
51380
+ }
51381
+ /** Shift every leading/trailing/inner comment attached to `node`. */
51382
+ function shiftAttachedComments(node, offset, shifted) {
51383
+ for (const c of node.leadingComments ?? []) stashAndShiftComment(c, offset, shifted);
51384
+ for (const c of node.trailingComments ?? []) stashAndShiftComment(c, offset, shifted);
51385
+ for (const c of node.innerComments ?? []) stashAndShiftComment(c, offset, shifted);
51386
+ }
51387
+ /** The line a block's first emitted token occupies: its banner comment if it has
51388
+ * leading comments, else the node itself. */
51389
+ function blockFirstEmitLine(firstNode) {
51390
+ const firstLeading = firstNode.leadingComments?.[0]?.loc;
51391
+ return firstLeading ? firstLeading.start.line : firstNode.loc.start.line;
51392
+ }
51393
+ /**
51394
+ * Phase 56-R10 — true when `stmt` is a sigil DIRECTIVE that every target's residual-body
51395
+ * emit STRIPS (it is consumed into a non-residual section: lifecycle / context / computed /
51396
+ * expose / watch). Mirrors the strip lists in each `emitResidualScriptBody`
51397
+ * (`$onMount`/`$onUnmount`/`$onUpdate`/`$watch`/`$expose`/`$provide` ExpressionStatements,
51398
+ * and `$computed`/`$inject` VariableDeclarations).
51399
+ *
51400
+ * USED ONLY to decide whether a spliced LEADING-comment run's IMMEDIATE source predecessor
51401
+ * survives in the residual body. When that predecessor is STRIPPED (e.g. the real DataTable
51402
+ * `exposeStateVerbs` run sits below a `$provide(...)`), the inline-authored form attaches the
51403
+ * boundary comment to the predecessor's `trailingComments` + the spliced decl's
51404
+ * `leadingComments` (shared object), but per-statement generation drops the predecessor's
51405
+ * trailing copy WITH the stripped statement → the comment SINGLE-emits. The vue/svelte splice
51406
+ * mirror would otherwise re-create that prev-trailing copy and DOUBLE it (the R10 bug). When
51407
+ * the predecessor SURVIVES (a plain `let`/`const`, e.g. `let expandedTouched` above
51408
+ * `groupingActiveDefault`), both copies survive → the inline form DOUBLES and the mirror must
51409
+ * keep doing so. Never throws.
51410
+ */
51411
+ function isStrippedSigilDirective(stmt) {
51412
+ if (_babel_types.isExpressionStatement(stmt) && _babel_types.isCallExpression(stmt.expression)) {
51413
+ const callee = stmt.expression.callee;
51414
+ if (_babel_types.isIdentifier(callee)) return callee.name === "$onMount" || callee.name === "$onUnmount" || callee.name === "$onUpdate" || callee.name === "$watch" || callee.name === "$expose" || callee.name === "$provide";
51415
+ return false;
51416
+ }
51417
+ if (_babel_types.isVariableDeclaration(stmt) && stmt.declarations.length > 0) return stmt.declarations.every((d) => d.init !== null && d.init !== void 0 && _babel_types.isCallExpression(d.init) && _babel_types.isIdentifier(d.init.callee) && (d.init.callee.name === "$computed" || d.init.callee.name === "$inject"));
51418
+ return false;
51419
+ }
51420
+ /**
51421
+ * Measure the ORIGINAL source gap above a decl run's first emit token (D-02, R2).
51422
+ *
51423
+ * Returns the `prevEnd + gap` delta the run should flow at: the distance, in source
51424
+ * lines, from the run's first emit token (its banner comment if any, else its first
51425
+ * node) DOWN from the END of the nearest preceding node IN THE SAME SOURCE FILE — a
51426
+ * same-file freshly-hoisted import (`sameFileHoists`) or the prior same-file decl run's
51427
+ * last node (`prevRunLastNode`). `gap = firstEmitLine − precedingEnd` so a zero-blank
51428
+ * adjacency yields `1` (next line) and N blanks yield `N+1` (no clamp). All `loc`s are
51429
+ * still PARTIAL-LOCAL here (the normalize shift runs later), so this reproduces the
51430
+ * partial's own pre-extraction layout — which, by the extraction rule, equals the host
51431
+ * adjacency the inline oracle has (Pitfall 3: measure source-side, NOT the host seam).
51432
+ *
51433
+ * When the run's first decl has NO same-file predecessor (a file-top nested-partial
51434
+ * decl, e.g. HostD's `inner` at the top of its own file), there is no source delta to
51435
+ * measure, so this falls back to the legacy default `2` — preserving the pre-D-02
51436
+ * behavior for that shape (no regression). Never throws.
51437
+ */
51438
+ function measureOriginalGap(firstNode, sameFileHoists, prevRunLastNode) {
51439
+ const loc = firstNode.loc;
51440
+ if (!loc) return 2;
51441
+ const firstEmitLine = blockFirstEmitLine(firstNode);
51442
+ const fname = loc.filename;
51443
+ let precedingEnd = 0;
51444
+ for (const h of sameFileHoists) {
51445
+ const hl = h.loc;
51446
+ if (hl && hl.filename === fname && hl.end.line < firstEmitLine) precedingEnd = Math.max(precedingEnd, hl.end.line);
51447
+ }
51448
+ const pl = prevRunLastNode?.loc;
51449
+ if (pl && pl.filename === fname && pl.end.line < firstEmitLine) precedingEnd = Math.max(precedingEnd, pl.end.line);
51450
+ if (precedingEnd === 0) return 2;
51451
+ return Math.max(1, firstEmitLine - precedingEnd);
51452
+ }
51453
+ /**
51454
+ * Shift every node + attached comment in a block by `offset`, deduping on the
51455
+ * underlying `Position` objects (see {@link shiftPositionLine}). Never throws.
51456
+ *
51457
+ * WR-01: `shifted` is supplied by the CALLER and SHARED across every block in one
51458
+ * normalize pass. A between-statement comment can be aliased across two blocks —
51459
+ * `@babel/parser` attaches the comment between a hoisted import and the first decl
51460
+ * to BOTH the import's `trailingComments` and the decl's `leadingComments` (the
51461
+ * SAME `Position` objects). When the hoist and that decl run land in separate
51462
+ * blocks, a per-block `shifted` set would shift the aliased comment TWICE (once per
51463
+ * block), corrupting its emit line. A shared set shifts each unique `Position`
51464
+ * exactly once. The adjacent hoist/decl offsets are equal by construction (the
51465
+ * sequential decl anchor is derived from the shifted hoist end + the same gap the
51466
+ * partial's import→decl delta encodes), so first-touch-wins is the correct offset.
51467
+ */
51468
+ function shiftBlock(block, offset, shifted) {
51469
+ for (const top of block.nodes) {
51470
+ stashAndShiftNode(top, offset, shifted);
51471
+ shiftAttachedComments(top, offset, shifted);
51472
+ try {
51473
+ traverse$18(_babel_types.file(_babel_types.program([top])), { enter(path) {
51474
+ stashAndShiftNode(path.node, offset, shifted);
51475
+ shiftAttachedComments(path.node, offset, shifted);
51476
+ } });
51477
+ } catch {}
51478
+ }
51479
+ }
51480
+ /** Shift a single comment's loc lines by `offset` WITHOUT stashing a partial origin. */
51481
+ function shiftCommentLinesOnly(comment, offset, shifted) {
51482
+ const loc = comment.loc;
51483
+ if (!loc) return;
51484
+ shiftPositionLine(loc.start, offset, shifted);
51485
+ shiftPositionLine(loc.end, offset, shifted);
51486
+ }
51487
+ /**
51488
+ * Phase 56-R8 — shift a HOST node's loc + attached/nested comments by `offset`,
51489
+ * deduping on the underlying `Position` objects (shared `shifted` set, like
51490
+ * {@link shiftBlock}). Never throws (D-04).
51491
+ *
51492
+ * Unlike {@link shiftBlock} / {@link stashAndShiftNode}, this does NOT stash a
51493
+ * `__roziePartialOrigin`: a host statement belongs to the HOST `.rozie` file, so its
51494
+ * `loc` is the host source-map anchor and must NOT be re-keyed onto a partial origin
51495
+ * (`buildPartialLineOffsets` is per-FILE first-stash-wins and would mis-offset every
51496
+ * other host segment). This is invoked ONLY for an after-side host run that carries a
51497
+ * GENUINE intended blank below a spliced run (`afterGap >= 2`) — the gap-1 trailing
51498
+ * seam. Such runs never appear in any built source-map gate today (dist-parity compiles
51499
+ * with `sourceMap: false`; the R5 smoke fixtures have no `afterGap >= 2` host run), so
51500
+ * the line shift is invisible to the source-map gates while making the @babel/generator
51501
+ * blank-line delta reproduce the source's after-side blank on vue/svelte/solid. The
51502
+ * host node's residual source-map LINE imperfection for such runs is the same class of
51503
+ * `userCodeLineOffset` limitation already documented in deferred-items.md (#1).
51504
+ */
51505
+ function shiftHostNodeLines(node, offset, shifted) {
51506
+ if (offset === 0) return;
51507
+ const apply = (n) => {
51508
+ if (n.loc) {
51509
+ shiftPositionLine(n.loc.start, offset, shifted);
51510
+ shiftPositionLine(n.loc.end, offset, shifted);
51511
+ }
51512
+ for (const c of n.leadingComments ?? []) shiftCommentLinesOnly(c, offset, shifted);
51513
+ for (const c of n.trailingComments ?? []) shiftCommentLinesOnly(c, offset, shifted);
51514
+ for (const c of n.innerComments ?? []) shiftCommentLinesOnly(c, offset, shifted);
51515
+ };
51516
+ apply(node);
51517
+ try {
51518
+ traverse$18(_babel_types.file(_babel_types.program([node])), { enter(path) {
51519
+ apply(path.node);
51520
+ } });
51521
+ } catch {}
51522
+ }
51523
+ /**
51524
+ * FINAL inline-pass step (Phase 55) — decouple the line `@babel/generator` reads
51525
+ * for blank-line/comment placement from the line it reads for the source-map
51526
+ * origin, for every spliced partial node.
51527
+ *
51528
+ * Rationale (RESEARCH Key Finding 1): under `retainLines:false` the generator's
51529
+ * comment-adjacency + blank-line math reads `node.loc.start.line` and
51530
+ * `comment.loc.start.line` only as DELTAS; only the host↔partial (and
51531
+ * partial↔partial) BOUNDARY deltas are wrong (a spliced node carries a small
51532
+ * `.rzts`-local line discontinuous with its host neighbour). A CONSTANT per-block
51533
+ * offset preserves each block's intra deltas; the right boundary delta is restored
51534
+ * by anchoring each block SEQUENTIALLY in residual-body order.
51535
+ *
51536
+ * SEQUENTIAL ANCHOR (Phase 55-04): each block's first emitted line (its banner
51537
+ * comment, else its first node) is anchored ONE blank line below the PRECEDING
51538
+ * statement in the final body. Anchoring every block at its own replaced import
51539
+ * line (Plan 02's approach) collapsed multi-partial hosts: three consecutive
51540
+ * mid-body imports made expand/group/facet pile onto adjacent host lines, so each
51541
+ * 30-45-line block overlapped the next and the partial↔partial comment deltas went
51542
+ * negative (`}; // banner` collapse). Flowing the blocks sequentially reproduces the
51543
+ * inline-authored contiguous layout. The first block (or any block preceded only by
51544
+ * un-located content) falls back to its replaced import's host line.
51545
+ *
51546
+ * WR-01 (whole-program byte-identity): the walk runs over the FULL emit body
51547
+ * (`[...hoistImports, ...residualBody]`), NOT just the residual body, so a
51548
+ * freshly-hoisted import flows in true emit order ahead of the decls. Consecutive
51549
+ * IMPORT blocks anchor CONTIGUOUSLY (gap 1 — no blank between imports); every other
51550
+ * run anchors one blank line below the preceding statement (gap 2). Because each
51551
+ * source-file decl run is now its OWN block (see {@link SplicedEmitBlock}), a
51552
+ * nested-partial decl run flows one blank line below the parent decl run rather than
51553
+ * inheriting the hoist's incommensurate file-top offset.
51554
+ *
51555
+ * TWO PASSES (WR-01): a between-statement comment is aliased across blocks — the
51556
+ * `Position` that is a hoisted import's `trailingComments[i]` is the SAME object as
51557
+ * the next decl's `leadingComments[i]`. If offsets were applied while walking, the
51558
+ * hoist block would shift that comment, and the decl block's `blockFirstEmitLine`
51559
+ * would then read the ALREADY-SHIFTED comment line and derive a wrong offset
51560
+ * (collapsing the comment onto the decl). So PASS 1 MEASURES every block's offset
51561
+ * from ORIGINAL (unmutated) lines, tracking the running emit end arithmetically;
51562
+ * PASS 2 MUTATES, applying every offset with ONE shared dedup set so each aliased
51563
+ * `Position` shifts exactly once (adjacent hoist/decl offsets are equal by
51564
+ * construction, so first-touch-wins is the correct line).
51565
+ *
51566
+ * Runs AFTER all diagnostics are collected (they captured true `.rzts` byte loc via
51567
+ * `nodeLoc`, which reads `node.start`/`node.end`, NOT `loc.{line,column}`), so the
51568
+ * R7 error-frame path is untouched (Pitfall 1). Never throws (D-04).
51569
+ */
51570
+ function normalizeSplicedEmitLines(body, blocks) {
51571
+ const nodeToBlock = /* @__PURE__ */ new Map();
51572
+ for (const b of blocks) for (const n of b.nodes) nodeToBlock.set(n, b);
51573
+ const measured = /* @__PURE__ */ new Set();
51574
+ const plan = [];
51575
+ let prevEnd = 0;
51576
+ let prevWasImport = false;
51577
+ let prevWasBlock = false;
51578
+ let prevBlockAnchorLine = 0;
51579
+ let hostRunOffset = 0;
51580
+ let seamAfterGap;
51581
+ let prevWasHostStmt = false;
51582
+ let prevHostOrigEnd = 0;
51583
+ let prevHostStmt = null;
51584
+ for (const stmt of body) {
51585
+ const block = nodeToBlock.get(stmt);
51586
+ if (block) {
51587
+ if (measured.has(block)) continue;
51588
+ measured.add(block);
51589
+ const firstNode = block.nodes.find((n) => n.loc);
51590
+ if (!firstNode?.loc) continue;
51591
+ const isImportBlock = _babel_types.isImportDeclaration(block.nodes[0]);
51592
+ let gap = isImportBlock && prevWasImport ? 1 : block.originalGap;
51593
+ let stampLeadingSeamStripped = false;
51594
+ if (!isImportBlock && prevWasHostStmt) {
51595
+ const beforeGap = block.anchorLine - prevHostOrigEnd;
51596
+ if (blockFirstEmitLine(firstNode) < firstNode.loc.start.line) {
51597
+ if (beforeGap >= 1 && beforeGap < gap) gap = beforeGap;
51598
+ if (prevHostStmt && isStrippedSigilDirective(prevHostStmt)) stampLeadingSeamStripped = true;
51599
+ }
51600
+ }
51601
+ const offset = (prevEnd > 0 ? prevEnd + gap : block.anchorLine) - blockFirstEmitLine(firstNode);
51602
+ let maxEnd = 0;
51603
+ for (const n of block.nodes) if (n.loc) maxEnd = Math.max(maxEnd, n.loc.end.line);
51604
+ prevEnd = maxEnd + offset;
51605
+ prevWasImport = isImportBlock;
51606
+ prevWasBlock = true;
51607
+ prevWasHostStmt = false;
51608
+ prevHostStmt = null;
51609
+ prevBlockAnchorLine = block.anchorLine;
51610
+ plan.push(stampLeadingSeamStripped ? {
51611
+ block,
51612
+ offset,
51613
+ leadingSeamPrevStripped: true
51614
+ } : {
51615
+ block,
51616
+ offset
51617
+ });
51618
+ continue;
51619
+ }
51620
+ if (stmt.loc) {
51621
+ const firstEmit = blockFirstEmitLine(stmt);
51622
+ let offset;
51623
+ if (prevWasBlock) {
51624
+ const afterGap = firstEmit - prevBlockAnchorLine;
51625
+ if (afterGap >= 2) {
51626
+ offset = prevEnd + afterGap - firstEmit;
51627
+ seamAfterGap = afterGap;
51628
+ } else offset = 0;
51629
+ hostRunOffset = offset;
51630
+ } else offset = hostRunOffset;
51631
+ if (offset !== 0) plan.push(seamAfterGap !== void 0 ? {
51632
+ hostNode: stmt,
51633
+ offset,
51634
+ afterGap: seamAfterGap
51635
+ } : {
51636
+ hostNode: stmt,
51637
+ offset
51638
+ });
51639
+ prevEnd = stmt.loc.end.line + offset;
51640
+ seamAfterGap = void 0;
51641
+ prevWasImport = _babel_types.isImportDeclaration(stmt);
51642
+ prevWasBlock = false;
51643
+ prevWasHostStmt = true;
51644
+ prevHostOrigEnd = stmt.loc.end.line;
51645
+ prevHostStmt = stmt;
51646
+ }
51647
+ }
51648
+ for (const block of blocks) {
51649
+ if (measured.has(block)) continue;
51650
+ measured.add(block);
51651
+ const firstNode = block.nodes.find((n) => n.loc);
51652
+ if (!firstNode?.loc) continue;
51653
+ plan.push({
51654
+ block,
51655
+ offset: block.anchorLine - blockFirstEmitLine(firstNode)
51656
+ });
51657
+ }
51658
+ const shifted = /* @__PURE__ */ new Set();
51659
+ for (const entry of plan) if ("block" in entry) {
51660
+ shiftBlock(entry.block, entry.offset, shifted);
51661
+ if (entry.leadingSeamPrevStripped) {
51662
+ const firstNode = entry.block.nodes.find((n) => n.loc);
51663
+ if (firstNode) firstNode.extra = {
51664
+ ...firstNode.extra ?? {},
51665
+ __rozieLeadingSeamPrevStripped: true
51666
+ };
51667
+ }
51668
+ } else {
51669
+ shiftHostNodeLines(entry.hostNode, entry.offset, shifted);
51670
+ if (entry.afterGap !== void 0) {
51671
+ const extra = entry.hostNode.extra ?? {};
51672
+ entry.hostNode.extra = {
51673
+ ...extra,
51674
+ __rozieAfterGap: entry.afterGap
51675
+ };
51676
+ }
51677
+ }
51678
+ }
51679
+ /**
51680
+ * Phase 56 (Shape-3, R4) — un-FLOAT a hoisted import's between-statement comment that
51681
+ * has separated from its owning declaration.
51682
+ *
51683
+ * `hoistSpecifier` copies a partial import's `trailingComments` onto the HOISTED import
51684
+ * node so a comment authored BETWEEN that import and the next surviving decl rides the
51685
+ * import to the host module-top (Phase 55 byte-identity). @babel/parser attaches such a
51686
+ * between-statement comment to BOTH neighbours: the import's `trailingComments` AND the
51687
+ * following decl's `leadingComments` (the SAME comment object). That copy is CORRECT
51688
+ * only when the import and its decl stay ADJACENT in the final body (e.g. HostC: the
51689
+ * hoisted `clamp` import is immediately followed by the `double` decl it shares the
51690
+ * comment with — the inline oracle ALSO has the comment on both neighbours, so
51691
+ * per-statement targets double it identically).
51692
+ *
51693
+ * When the spliced decl lands NON-adjacent to its hoisted import — a host statement
51694
+ * (e.g. a reassigned module-`let`) sits between them (HostH/the DataTable P15
51695
+ * `editTransition` after-`let` seam) — the import's copy FLOATS the comment to
51696
+ * module-top, away from the decl. The inline oracle keeps the comment ONLY on the decl
51697
+ * (its import is authored far above, never adjacent), so the float is a partial-vs-inline
51698
+ * divergence on ALL six targets (svelte/vue double it at the wrong place, react/angular/
51699
+ * lit drop it, solid dedups). This pass restores the inline placement by STRIPPING the
51700
+ * floated comment from the import's `trailingComments`; the decl keeps it on its
51701
+ * `leadingComments` (object identity preserved), and the per-target emitters then handle
51702
+ * the decl-attached comment exactly as they do for the inline oracle (svelte/vue's
51703
+ * `mirrorSpliceBoundaryComments` restores the doubling at the decl seam).
51704
+ *
51705
+ * Runs BEFORE `normalizeSplicedEmitLines` so the corrected attachment is in place when
51706
+ * the emit-line shift runs. A comment is treated as FLOATED iff it is shared (object
51707
+ * identity) with some body node's `leadingComments` whose owner is NOT the import's
51708
+ * immediate body-successor; a genuine import-only trailing comment (owned by no decl's
51709
+ * leadingComments) and the adjacent-decl case (HostC) are both left untouched. Never
51710
+ * throws (D-04).
51711
+ */
51712
+ function defloatHoistedImportComments(body, hoistImports) {
51713
+ if (hoistImports.length === 0) return;
51714
+ const hoistSet = new Set(hoistImports);
51715
+ const leadOwnerIndex = /* @__PURE__ */ new Map();
51716
+ for (let i = 0; i < body.length; i++) for (const c of body[i].leadingComments ?? []) if (!leadOwnerIndex.has(c)) leadOwnerIndex.set(c, i);
51717
+ for (let hi = 0; hi < body.length; hi++) {
51718
+ const node = body[hi];
51719
+ if (!hoistSet.has(node)) continue;
51720
+ const trailing = node.trailingComments;
51721
+ if (!trailing || trailing.length === 0) continue;
51722
+ const kept = trailing.filter((c) => {
51723
+ const owner = leadOwnerIndex.get(c);
51724
+ if (owner === void 0) return true;
51725
+ if (owner === hi + 1) return true;
51726
+ return false;
51727
+ });
51728
+ if (kept.length !== trailing.length) node.trailingComments = kept.length > 0 ? kept : null;
51729
+ }
51730
+ }
51215
51731
  /** Build the named imported-name list for a (host or nested) partial import. */
51216
51732
  function namedImports(imp) {
51217
51733
  const out = [];
@@ -51274,6 +51790,7 @@ function inlineResolvedPartial(absPath, importedNames, importStmt, importerFile,
51274
51790
  const nestedImports = [];
51275
51791
  /** local name -> nested partial resolved path + the imported name. */
51276
51792
  const nestedLocalMap = /* @__PURE__ */ new Map();
51793
+ const reExports = [];
51277
51794
  let order = 0;
51278
51795
  for (const stmt of partialBody) {
51279
51796
  if (_babel_types.isImportDeclaration(stmt)) {
@@ -51299,9 +51816,47 @@ function inlineResolvedPartial(absPath, importedNames, importStmt, importerFile,
51299
51816
  } else moduleImports.push(stmt);
51300
51817
  continue;
51301
51818
  }
51819
+ if (_babel_types.isExportNamedDeclaration(stmt) && !stmt.declaration && stmt.source) {
51820
+ const src = stmt.source;
51821
+ const declTypeOnly = stmt.exportKind === "type";
51822
+ for (const spec of stmt.specifiers) if (_babel_types.isExportSpecifier(spec)) {
51823
+ const exportedName = _babel_types.isIdentifier(spec.exported) ? spec.exported.name : spec.exported.value;
51824
+ const kind = declTypeOnly || spec.exportKind === "type" ? "type" : "value";
51825
+ const localId = spec.local;
51826
+ reExports.push({
51827
+ exportedName,
51828
+ source: src,
51829
+ kind,
51830
+ build: () => _babel_types.importSpecifier(_babel_types.identifier(exportedName), _babel_types.identifier(localId.name))
51831
+ });
51832
+ } else if (_babel_types.isExportNamespaceSpecifier(spec)) {
51833
+ const nsName = spec.exported.name;
51834
+ reExports.push({
51835
+ exportedName: nsName,
51836
+ source: src,
51837
+ kind: declTypeOnly ? "type" : "value",
51838
+ build: () => _babel_types.importNamespaceSpecifier(_babel_types.identifier(nsName))
51839
+ });
51840
+ }
51841
+ continue;
51842
+ }
51843
+ if (_babel_types.isExportAllDeclaration(stmt)) {
51844
+ ctx.diagnostics.push({
51845
+ code: RozieErrorCode.PARTIAL_UNSUPPORTED_IMPORT_FORM,
51846
+ severity: "error",
51847
+ message: `Script partial '${absPath}' uses \`export * from '${stmt.source.value}'\`. A star re-export has no statically-known named surface to inline — a partial is a compile-time inline, so each re-exported symbol must be named (e.g. \`export { foo } from '${stmt.source.value}'\`).`,
51848
+ loc: nodeLoc(stmt),
51849
+ ...absPath ? { filename: absPath } : {},
51850
+ hint: `Replace the star re-export with explicit named re-exports: export { foo, bar } from '${stmt.source.value}'.`
51851
+ });
51852
+ continue;
51853
+ }
51302
51854
  let bare = null;
51303
- if (_babel_types.isExportNamedDeclaration(stmt) && stmt.declaration) bare = stmt.declaration;
51304
- else if (_babel_types.isVariableDeclaration(stmt) || _babel_types.isFunctionDeclaration(stmt) || _babel_types.isClassDeclaration(stmt) || _babel_types.isTSInterfaceDeclaration(stmt) || _babel_types.isTSTypeAliasDeclaration(stmt) || _babel_types.isTSEnumDeclaration(stmt) || _babel_types.isTSDeclareFunction(stmt)) bare = stmt;
51855
+ if (_babel_types.isExportNamedDeclaration(stmt) && stmt.declaration) {
51856
+ bare = stmt.declaration;
51857
+ const prevStmt = partialBody[partialBody.indexOf(stmt) - 1];
51858
+ if ((prevStmt && _babel_types.isImportDeclaration(prevStmt) && !PARTIAL_EXT.test(prevStmt.source.value) && prevStmt.trailingComments && stmt.leadingComments ? stmt.leadingComments.some((c) => prevStmt.trailingComments.includes(c)) : false) && stmt.leadingComments && stmt.leadingComments.length > 0 && (!bare.leadingComments || bare.leadingComments.length === 0)) bare.leadingComments = stmt.leadingComments;
51859
+ } else if (_babel_types.isVariableDeclaration(stmt) || _babel_types.isFunctionDeclaration(stmt) || _babel_types.isClassDeclaration(stmt) || _babel_types.isTSInterfaceDeclaration(stmt) || _babel_types.isTSTypeAliasDeclaration(stmt) || _babel_types.isTSEnumDeclaration(stmt) || _babel_types.isTSDeclareFunction(stmt)) bare = stmt;
51305
51860
  if (!bare) continue;
51306
51861
  const names = bindingNames(bare);
51307
51862
  if (names.length === 0) continue;
@@ -51354,8 +51909,9 @@ function inlineResolvedPartial(absPath, importedNames, importStmt, importerFile,
51354
51909
  }
51355
51910
  for (const imp of moduleImports) {
51356
51911
  const declKind = imp.importKind ?? "value";
51357
- for (const spec of imp.specifiers) if (referencedAll.has(spec.local.name)) hoistSpecifier(ctx, imp.source, declKind, spec);
51912
+ for (const spec of imp.specifiers) if (referencedAll.has(spec.local.name)) hoistSpecifier(ctx, imp.source, declKind, spec, imp);
51358
51913
  }
51914
+ for (const re of reExports) if (importedNames.includes(re.exportedName) || referencedAll.has(re.exportedName)) hoistSpecifier(ctx, re.source, re.kind, re.build());
51359
51915
  const out = [];
51360
51916
  for (const decl of includedSorted) {
51361
51917
  const collidingNames = [];
@@ -51445,6 +52001,7 @@ function inlineScriptPartials(file, opts = {}) {
51445
52001
  }
51446
52002
  }
51447
52003
  const splicedAbs = /* @__PURE__ */ new Set();
52004
+ const splicedBlocks = [];
51448
52005
  const newBody = [];
51449
52006
  for (const stmt of file.program.body) if (_babel_types.isImportDeclaration(stmt) && PARTIAL_EXT.test(stmt.source.value)) {
51450
52007
  const absPath = hostPartialAbs.get(stmt) ?? null;
@@ -51473,9 +52030,38 @@ function inlineScriptPartials(file, opts = {}) {
51473
52030
  if (splicedAbs.has(absPath)) continue;
51474
52031
  splicedAbs.add(absPath);
51475
52032
  const unionNames = hostPartialNames.get(absPath) ?? namedImports(stmt);
51476
- newBody.push(...inlineResolvedPartial(absPath, unionNames, stmt, fromFile, ctx));
52033
+ const hoistBefore = ctx.hoistImports.length;
52034
+ const spliced = inlineResolvedPartial(absPath, unionNames, stmt, fromFile, ctx);
52035
+ const newHoists = ctx.hoistImports.slice(hoistBefore);
52036
+ if (stmt.loc) {
52037
+ const anchorLine = stmt.loc.start.line;
52038
+ for (const hoist of newHoists) splicedBlocks.push({
52039
+ nodes: [hoist],
52040
+ anchorLine,
52041
+ originalGap: 1
52042
+ });
52043
+ let runStart = 0;
52044
+ let prevRunLastNode = null;
52045
+ while (runStart < spliced.length) {
52046
+ const fname = spliced[runStart].loc?.filename ?? null;
52047
+ let runEnd = runStart + 1;
52048
+ while (runEnd < spliced.length && (spliced[runEnd].loc?.filename ?? null) === fname) runEnd++;
52049
+ const nodes = spliced.slice(runStart, runEnd);
52050
+ const originalGap = measureOriginalGap(nodes.find((n) => n.loc) ?? nodes[0], newHoists, prevRunLastNode);
52051
+ splicedBlocks.push({
52052
+ nodes,
52053
+ anchorLine,
52054
+ originalGap
52055
+ });
52056
+ prevRunLastNode = nodes[nodes.length - 1] ?? prevRunLastNode;
52057
+ runStart = runEnd;
52058
+ }
52059
+ }
52060
+ newBody.push(...spliced);
51477
52061
  } else newBody.push(stmt);
51478
52062
  file.program.body = [...ctx.hoistImports, ...newBody];
52063
+ defloatHoistedImportComments(file.program.body, ctx.hoistImports);
52064
+ normalizeSplicedEmitLines(file.program.body, splicedBlocks);
51479
52065
  return {
51480
52066
  ast: file,
51481
52067
  diagnostics
@@ -53783,6 +54369,27 @@ function rewriteRozieIdentifiers$4(program, ir, diagnostics) {
53783
54369
  loc: ref.sourceLoc
53784
54370
  });
53785
54371
  normalizeModelAccessor$4(program);
54372
+ const vueProtected = new Set((ir.expose ?? []).map((e) => e.name));
54373
+ deconflictGeneratedSymbols(program, [
54374
+ {
54375
+ names: modelProps,
54376
+ trigger: {
54377
+ kind: "accessor",
54378
+ accessor: "$props"
54379
+ }
54380
+ },
54381
+ {
54382
+ names: dataNames,
54383
+ trigger: {
54384
+ kind: "accessor",
54385
+ accessor: "$data"
54386
+ }
54387
+ },
54388
+ {
54389
+ names: computedNames,
54390
+ trigger: { kind: "bare-read" }
54391
+ }
54392
+ ], vueProtected);
53786
54393
  traverse$17(program, {
53787
54394
  MemberExpression(path) {
53788
54395
  /* v8 ignore next -- defensive: MemberExpression nodes do not occur in TS type position */
@@ -54303,6 +54910,80 @@ function arrowBody$4(body) {
54303
54910
  return genCode$9(_babel_types.arrowFunctionExpression([], body));
54304
54911
  }
54305
54912
  /**
54913
+ * Phase 55-04 (literal byte-identity) — reproduce the inline-authored comment
54914
+ * doubling at a script-partial splice boundary.
54915
+ *
54916
+ * In an inline-authored `<script>`, a comment block BETWEEN two statements is
54917
+ * attached by `@babel/parser` to BOTH neighbours (the earlier statement's
54918
+ * `trailingComments` AND the later statement's `leadingComments`). The `.rzts`
54919
+ * script-partial splice attaches the boundary banner ONLY to the spliced node's
54920
+ * `leadingComments` — the preceding statement lives in a different source file and
54921
+ * carries no matching trailing comment. Vue emits the residual body one statement
54922
+ * at a time (`stmts.map((s) => genCode(s)).join('\n')`), so each `genCode` call has
54923
+ * its own comment-dedup set: in the inline form the boundary banner therefore
54924
+ * prints TWICE (once as the previous statement's trailing, once as the next
54925
+ * statement's leading), with a blank line after the previous closing brace.
54926
+ * Re-mirroring the spliced node's leading comments back onto the preceding
54927
+ * statement's trailing comments restores that byte-for-byte.
54928
+ *
54929
+ * Fires at a genuine splice boundary in EITHER direction (Phase 56 R1 broadened
54930
+ * the trigger): the CURRENT statement is spliced (`cur.extra.__roziePartialOrigin`
54931
+ * — the Phase 55 leading seam) OR the PREVIOUS statement is spliced and CUR is an
54932
+ * inline host successor carrying the leading comment (the R1 TRAILING seam). In
54933
+ * both cases CUR's leading comments are mirrored onto PREV's trailing comments
54934
+ * UNLESS already shared (within-partial statement pairs share the same comment
54935
+ * objects; host-only pairs — neither node spliced — are left exactly as authored).
54936
+ * `normalizeSplicedEmitLines` (core) has already anchored the seam spacing, so the
54937
+ * mirrored trailing copy spaces correctly.
54938
+ */
54939
+ function mirrorSpliceBoundaryComments$2(stmts) {
54940
+ for (let i = 1; i < stmts.length; i++) {
54941
+ const cur = stmts[i];
54942
+ const prev = stmts[i - 1];
54943
+ const curExtra = cur.extra;
54944
+ const prevExtra = prev.extra;
54945
+ const curSpliced = curExtra?.__roziePartialOrigin !== void 0;
54946
+ const prevSpliced = prevExtra?.__roziePartialOrigin !== void 0;
54947
+ if (!curSpliced && !prevSpliced) continue;
54948
+ const lead = cur.leadingComments;
54949
+ const prevTrail = prev.trailingComments;
54950
+ if (curSpliced && !prevSpliced && (!lead || lead.length === 0) && prevTrail && prevTrail.length > 0) {
54951
+ cur.leadingComments = [...prevTrail];
54952
+ continue;
54953
+ }
54954
+ if (!lead || lead.length === 0) continue;
54955
+ if (curSpliced && curExtra?.__rozieLeadingSeamPrevStripped === true) continue;
54956
+ const lastLead = lead[lead.length - 1];
54957
+ if (prevTrail && prevTrail.length > 0 && prevTrail[prevTrail.length - 1] === lastLead) continue;
54958
+ let toAppend = lead;
54959
+ if (prevSpliced && !curSpliced) {
54960
+ const afterGap = curExtra?.__rozieAfterGap;
54961
+ const anchorLine = (prev.loc?.end.line ?? 0) + (typeof afterGap === "number" ? afterGap : 1);
54962
+ const baseLine = lead[0]?.loc?.start.line;
54963
+ toAppend = lead.map((c) => {
54964
+ if (!c.loc) return { ...c };
54965
+ const startLine = baseLine === void 0 ? anchorLine : anchorLine + (c.loc.start.line - baseLine);
54966
+ const endLine = baseLine === void 0 ? anchorLine : anchorLine + (c.loc.end.line - baseLine);
54967
+ return {
54968
+ ...c,
54969
+ loc: {
54970
+ ...c.loc,
54971
+ start: {
54972
+ ...c.loc.start,
54973
+ line: startLine
54974
+ },
54975
+ end: {
54976
+ ...c.loc.end,
54977
+ line: endLine
54978
+ }
54979
+ }
54980
+ };
54981
+ });
54982
+ }
54983
+ prev.trailingComments = [...prevTrail ?? [], ...toAppend];
54984
+ }
54985
+ }
54986
+ /**
54306
54987
  * Render a PropTypeAnnotation as a TypeScript type string.
54307
54988
  *
54308
54989
  * Reference examples produce these patterns:
@@ -54757,6 +55438,7 @@ function emitResidualScriptBody$1(clonedProgram, consumedLifecycleIndices) {
54757
55438
  }
54758
55439
  stmts.push(stmt);
54759
55440
  }
55441
+ mirrorSpliceBoundaryComments$2(stmts);
54760
55442
  return {
54761
55443
  code: stmts.map((s) => genCode$9(s)).join("\n"),
54762
55444
  stmts
@@ -58333,7 +59015,8 @@ function remapping$1(input, loader, options) {
58333
59015
  * (the "parent" map) via @ampproject/remapping. Result: a single Source Map
58334
59016
  * v3 that resolves emitted-output positions all the way back to .rozie.
58335
59017
  *
58336
- * Used by all 4 target compose.ts files. Replaces the per-target
59018
+ * Used by all 6 target compose.ts files (react/vue/svelte/solid/lit/angular).
59019
+ * Replaces the per-target
58337
59020
  * single-segment re-projection hack (Phase 3 WR-01 / Phase 4 Plan 04-05
58338
59021
  * Task 1) removed in P2 (D-109).
58339
59022
  *
@@ -58346,7 +59029,114 @@ function remapping$1(input, loader, options) {
58346
59029
  * @experimental — shape may change before v1.0
58347
59030
  */
58348
59031
  const remapping = typeof remapping$1 === "function" ? remapping$1 : remapping$1.default;
59032
+ /** Source-file extension test: a spliced script partial origin (Phase 54/55). */
59033
+ function isPartialSource(src) {
59034
+ return !!src && (src.endsWith(".rzts") || src.endsWith(".rzjs"));
59035
+ }
59036
+ /**
59037
+ * Recover a partial source's constant emit-line offset from the table. The table
59038
+ * is keyed by the absolute `loc.filename` stashed at splice time; a child map's
59039
+ * `sources` entry is the same absolute path, so an EXACT lookup is tried first.
59040
+ *
59041
+ * WR-03: the defensive fallback is restricted to a path-SEGMENT-BOUNDARY match
59042
+ * (`src.endsWith('/' + file) || file.endsWith('/' + src)`) so a partial whose path
59043
+ * is a bare substring of another's (e.g. `xa.rzts` vs `a.rzts`) never collides, and
59044
+ * — critically — if TWO OR MORE table entries match the boundary test (an ambiguous
59045
+ * bare basename shared across nested dirs) the lookup BAILS (returns `undefined`)
59046
+ * rather than mis-attributing one partial's offset to another. Returns `undefined`
59047
+ * when no unambiguous offset is known (mapping left as-is — D-04).
59048
+ */
59049
+ function lookupPartialOffset(src, offsets) {
59050
+ if (!src || !offsets || offsets.size === 0) return void 0;
59051
+ const exact = offsets.get(src);
59052
+ if (exact !== void 0) return exact;
59053
+ let match;
59054
+ let matchCount = 0;
59055
+ for (const [file, off] of offsets) if (src.endsWith(`/${file}`) || file.endsWith(`/${src}`)) {
59056
+ match = off;
59057
+ matchCount++;
59058
+ }
59059
+ return matchCount === 1 ? match : void 0;
59060
+ }
59061
+ /**
59062
+ * Phase 55 Plan 03 (SC-2) — build the per-partial-file constant emit-line offset
59063
+ * table from the lowered script AST.
59064
+ *
59065
+ * Plan 02 shifted each spliced node's `loc.start.line` to a host-contiguous emit
59066
+ * value while stashing the true `.rzts` origin on `extra.__roziePartialOrigin`.
59067
+ * The per-partial offset is therefore the (constant) delta
59068
+ * `loc.start.line − __roziePartialOrigin.line`; subtracting it from a mapping's
59069
+ * original line restores the `.rzts`-local line. The offset is constant per
59070
+ * spliced block (Plan 02 anchored it), so one entry per partial `source` file
59071
+ * suffices — the first stashed node per file wins.
59072
+ *
59073
+ * Walks `scriptAst.program.body` (the spliced statements sit at top level) and
59074
+ * their attached leading/trailing/inner comments (comments carry the stash
59075
+ * directly). Never throws (D-04): a missing/zero stash is skipped, a null AST
59076
+ * yields an empty table.
59077
+ *
59078
+ * IN-02: the `innerComments` loop mirrors `shiftAttachedComments`
59079
+ * (inlineScriptPartials.ts) which walks leading/trailing/inner. In practice the
59080
+ * offset is per-file and first-stash-wins, so a leading/trailing comment or the
59081
+ * decl itself almost always supplies it — the inner loop is additive and harmless,
59082
+ * kept only so the stash-channel scan is symmetric with where stashes are written.
59083
+ */
59084
+ function buildPartialLineOffsets(scriptAst) {
59085
+ const out = /* @__PURE__ */ new Map();
59086
+ const body = scriptAst?.program?.body;
59087
+ if (!body) return out;
59088
+ const record = (carrier, locLine) => {
59089
+ const origin = carrier?.__roziePartialOrigin;
59090
+ if (!origin || locLine === void 0) return;
59091
+ const key = origin.filename;
59092
+ if (!key || out.has(key)) return;
59093
+ out.set(key, locLine - origin.line);
59094
+ };
59095
+ for (const node of body) {
59096
+ const extra = node.extra;
59097
+ record(extra, node.loc?.start.line);
59098
+ for (const c of node.leadingComments ?? []) record(c, c.loc?.start.line);
59099
+ for (const c of node.trailingComments ?? []) record(c, c.loc?.start.line);
59100
+ for (const c of node.innerComments ?? []) record(c, c.loc?.start.line);
59101
+ }
59102
+ return out;
59103
+ }
58349
59104
  /**
59105
+ * Phase 55 Plan 03 — per-target script-map flow survey (Task 1, Assumption A3).
59106
+ *
59107
+ * SURVEY RESULT (confirmed on disk 2026-06-20):
59108
+ *
59109
+ * 1. CONVERGENCE — all SIX per-target `sourcemap/compose.ts` wrappers
59110
+ * (react/vue/svelte/solid/lit/angular) import THIS `composeMaps` and route
59111
+ * their @babel/generator <script> child map through it as `children[0]`. No
59112
+ * target hand-rolls a second map merge; `composeMaps` is the single
59113
+ * convergence point, so the spliced-line restore arithmetic lives here ONCE
59114
+ * (D-03/D-05 uniformity) — never in a per-target wrapper or `emitScript.ts`.
59115
+ *
59116
+ * 2. ACTIVE MAP PATH — the five map-EMITTING targets (react/vue/svelte/solid/
59117
+ * angular) all pass a defined `userCodeLineOffset`, so each takes the
59118
+ * `userCodeLineOffset` branch below (step 3). Empirically, that branch
59119
+ * discarded the child map's per-node `sources` (it hardcoded `[opts.filename]`),
59120
+ * collapsing a spliced node's `.rzts` origin to the host `.rozie` — which is
59121
+ * exactly why the SC-2 line-fidelity smoke test was red/skipped. The restore
59122
+ * therefore lives in step 3. The step-4 remapping path is NOT exercised by
59123
+ * partials (every map-emitting target passes `userCodeLineOffset`), so it is
59124
+ * deliberately left untouched rather than carrying speculative dead code.
59125
+ *
59126
+ * 3. TABLE BUILD SITE — each of those five `emitXxx.ts` holds the lowered `ir`
59127
+ * (whose `ir.setupBody.scriptProgram` script AST carries the spliced nodes'
59128
+ * `extra.__roziePartialOrigin` stashes from Plan 02). Each builds the
59129
+ * `partialLineOffsets` table via {@link buildPartialLineOffsets} and threads
59130
+ * it into the `ComposeOpts` it already constructs. The table derives from the
59131
+ * IR, NOT from generator output — so NO `emitScript.ts` (the @babel/generator
59132
+ * caller) is touched (D-03).
59133
+ *
59134
+ * 4. LIT EXCEPTION — `packages/targets/lit/src/emitLit.ts` returns `map: null`
59135
+ * in v1 and does NOT call `composeSourceMap` (the wrapper is implemented but
59136
+ * dead until Phase 7 wires it). Lit therefore has no build+set site; its
59137
+ * wrapper still receives the `partialLineOffsets` field for forward-compat so
59138
+ * the restore flows automatically once Lit's map path is connected.
59139
+ *
58350
59140
  * Merge the shell map + zero-or-more child maps into a single Source Map v3
58351
59141
  * anchored to .rozie. Defensively re-asserts sources/sourcesContent per
58352
59142
  * Pitfall 2 mitigation.
@@ -58365,6 +59155,27 @@ function composeMaps(opts) {
58365
59155
  }
58366
59156
  if (opts.userCodeLineOffset !== void 0) {
58367
59157
  const childMap = opts.children[0].map;
59158
+ const childSources = childMap.sources ?? [];
59159
+ if (childSources.some((s) => isPartialSource(s)) && !!opts.partialLineOffsets && opts.partialLineOffsets.size > 0) {
59160
+ const decoded = decode(childMap.mappings);
59161
+ for (const line of decoded) for (const seg of line) if (seg.length >= 4) {
59162
+ const full = seg;
59163
+ const src = childSources[full[1]];
59164
+ const off = lookupPartialOffset(src, opts.partialLineOffsets);
59165
+ if (off !== void 0) full[2] = Math.max(0, full[2] - off);
59166
+ }
59167
+ const restoredMappings = encode(decoded);
59168
+ const sources = childSources.slice();
59169
+ const sourcesContent = sources.map((s, i) => s === opts.filename ? opts.source : childMap.sourcesContent?.[i] ?? null);
59170
+ return {
59171
+ version: 3,
59172
+ file: `${opts.filename}${opts.fileExt}`,
59173
+ sources,
59174
+ sourcesContent,
59175
+ names: childMap.names ?? [],
59176
+ mappings: ";".repeat(opts.userCodeLineOffset) + restoredMappings
59177
+ };
59178
+ }
58368
59179
  return {
58369
59180
  version: 3,
58370
59181
  file: `${opts.filename}${opts.fileExt}`,
@@ -58398,7 +59209,8 @@ function composeSourceMap$4(ms, opts) {
58398
59209
  outputOffset: opts.scriptOutputOffset
58399
59210
  }] : [],
58400
59211
  fileExt: ".vue",
58401
- userCodeLineOffset: opts.userCodeLineOffset
59212
+ userCodeLineOffset: opts.userCodeLineOffset,
59213
+ partialLineOffsets: opts.partialLineOffsets
58402
59214
  });
58403
59215
  }
58404
59216
  //#endregion
@@ -58583,7 +59395,8 @@ function emitVue(ir, opts = {}) {
58583
59395
  source: opts.source,
58584
59396
  scriptMap: shellScriptMap,
58585
59397
  scriptOutputOffset,
58586
- userCodeLineOffset
59398
+ userCodeLineOffset,
59399
+ partialLineOffsets: buildPartialLineOffsets(ir.setupBody.scriptProgram)
58587
59400
  }) : null,
58588
59401
  diagnostics: [
58589
59402
  ...scriptDiags,
@@ -62524,7 +63337,8 @@ function composeClassName(attrs, ctx) {
62524
63337
  });
62525
63338
  else segments.push({
62526
63339
  kind: "plainBinding",
62527
- expr: a.expression
63340
+ expr: a.expression,
63341
+ wrapForDisplay: a.wrapForDisplay
62528
63342
  });
62529
63343
  else if (a.kind === "spreadBinding") throw new Error(`React target: spreadBinding not valid in class array context (Phase 14).`);
62530
63344
  else segments.push({
@@ -62546,6 +63360,10 @@ function composeClassName(attrs, ctx) {
62546
63360
  const seg = segments[0];
62547
63361
  const staticSegs = decomposeStaticClassExpr(seg.expr);
62548
63362
  if (staticSegs) return renderInterpolatedClass(staticSegs, ctx);
63363
+ if (seg.wrapForDisplay) {
63364
+ ctx.collectors.runtime.add("clsx");
63365
+ return `clsx(${renderExpr$1(seg.expr, ir)})`;
63366
+ }
62549
63367
  return renderExpr$1(seg.expr, ir);
62550
63368
  }
62551
63369
  if (segments.length === 1 && segments[0].kind === "interpolated") {
@@ -68380,7 +69198,8 @@ function composeSourceMap$3(ms, opts) {
68380
69198
  outputOffset: opts.scriptOutputOffset
68381
69199
  }] : [],
68382
69200
  fileExt: ".tsx",
68383
- userCodeLineOffset: opts.userCodeLineOffset
69201
+ userCodeLineOffset: opts.userCodeLineOffset,
69202
+ partialLineOffsets: opts.partialLineOffsets
68384
69203
  });
68385
69204
  }
68386
69205
  //#endregion
@@ -68483,7 +69302,8 @@ function emitReact(ir, opts = {}) {
68483
69302
  source: opts.source,
68484
69303
  scriptMap: shellScriptMap,
68485
69304
  scriptOutputOffset,
68486
- userCodeLineOffset
69305
+ userCodeLineOffset,
69306
+ partialLineOffsets: buildPartialLineOffsets(ir.setupBody.scriptProgram)
68487
69307
  }) : null,
68488
69308
  diagnostics: [
68489
69309
  ...scriptDiags,
@@ -69477,6 +70297,80 @@ function arrowBody$2(body) {
69477
70297
  return genCode$5(_babel_types.arrowFunctionExpression([], body));
69478
70298
  }
69479
70299
  /**
70300
+ * Phase 55-04 (literal byte-identity) — reproduce the inline-authored comment
70301
+ * doubling at a script-partial splice boundary.
70302
+ *
70303
+ * In an inline-authored `<script>`, a comment block BETWEEN two statements is
70304
+ * attached by `@babel/parser` to BOTH neighbours (the earlier statement's
70305
+ * `trailingComments` AND the later statement's `leadingComments`). The `.rzts`
70306
+ * script-partial splice attaches the boundary banner ONLY to the spliced node's
70307
+ * `leadingComments` — the preceding statement lives in a different source file and
70308
+ * carries no matching trailing comment. Svelte emits the residual body one
70309
+ * statement at a time (`stmts.map((s) => genCode(s)).join('\n')`), so each
70310
+ * `genCode` call has its own comment-dedup set: in the inline form the boundary
70311
+ * banner therefore prints TWICE (once as the previous statement's trailing, once
70312
+ * as the next statement's leading), with a blank line after the previous closing
70313
+ * brace. Re-mirroring the spliced node's leading comments back onto the preceding
70314
+ * statement's trailing comments restores that byte-for-byte.
70315
+ *
70316
+ * Fires at a genuine splice boundary in EITHER direction (Phase 56 R1 broadened
70317
+ * the trigger): the CURRENT statement is spliced (`cur.extra.__roziePartialOrigin`
70318
+ * — the Phase 55 leading seam) OR the PREVIOUS statement is spliced and CUR is an
70319
+ * inline host successor carrying the leading comment (the R1 TRAILING seam). In
70320
+ * both cases CUR's leading comments are mirrored onto PREV's trailing comments
70321
+ * UNLESS already shared (within-partial statement pairs share the same comment
70322
+ * objects; host-only pairs — neither node spliced — are left exactly as authored).
70323
+ * `normalizeSplicedEmitLines` (core) has already anchored the seam spacing, so the
70324
+ * mirrored trailing copy spaces correctly.
70325
+ */
70326
+ function mirrorSpliceBoundaryComments$1(stmts) {
70327
+ for (let i = 1; i < stmts.length; i++) {
70328
+ const cur = stmts[i];
70329
+ const prev = stmts[i - 1];
70330
+ const curExtra = cur.extra;
70331
+ const prevExtra = prev.extra;
70332
+ const curSpliced = curExtra?.__roziePartialOrigin !== void 0;
70333
+ const prevSpliced = prevExtra?.__roziePartialOrigin !== void 0;
70334
+ if (!curSpliced && !prevSpliced) continue;
70335
+ const lead = cur.leadingComments;
70336
+ const prevTrail = prev.trailingComments;
70337
+ if (curSpliced && !prevSpliced && (!lead || lead.length === 0) && prevTrail && prevTrail.length > 0) {
70338
+ cur.leadingComments = [...prevTrail];
70339
+ continue;
70340
+ }
70341
+ if (!lead || lead.length === 0) continue;
70342
+ if (curSpliced && curExtra?.__rozieLeadingSeamPrevStripped === true) continue;
70343
+ const lastLead = lead[lead.length - 1];
70344
+ if (prevTrail && prevTrail.length > 0 && prevTrail[prevTrail.length - 1] === lastLead) continue;
70345
+ let toAppend = lead;
70346
+ if (prevSpliced && !curSpliced) {
70347
+ const afterGap = curExtra?.__rozieAfterGap;
70348
+ const anchorLine = (prev.loc?.end.line ?? 0) + (typeof afterGap === "number" ? afterGap : 1);
70349
+ const baseLine = lead[0]?.loc?.start.line;
70350
+ toAppend = lead.map((c) => {
70351
+ if (!c.loc) return { ...c };
70352
+ const startLine = baseLine === void 0 ? anchorLine : anchorLine + (c.loc.start.line - baseLine);
70353
+ const endLine = baseLine === void 0 ? anchorLine : anchorLine + (c.loc.end.line - baseLine);
70354
+ return {
70355
+ ...c,
70356
+ loc: {
70357
+ ...c.loc,
70358
+ start: {
70359
+ ...c.loc.start,
70360
+ line: startLine
70361
+ },
70362
+ end: {
70363
+ ...c.loc.end,
70364
+ line: endLine
70365
+ }
70366
+ }
70367
+ };
70368
+ });
70369
+ }
70370
+ prev.trailingComments = [...prevTrail ?? [], ...toAppend];
70371
+ }
70372
+ }
70373
+ /**
69480
70374
  * Render a PropTypeAnnotation as a TypeScript type string. Mirrors the Vue
69481
70375
  * target's renderType helper.
69482
70376
  */
@@ -69915,6 +70809,7 @@ function emitResidualScriptBody(clonedProgram, consumedLifecycleIndices, exposeN
69915
70809
  }
69916
70810
  stmts.push(stmt);
69917
70811
  }
70812
+ mirrorSpliceBoundaryComments$1(stmts);
69918
70813
  return {
69919
70814
  code: stmts.map((s) => {
69920
70815
  if (exposeNames.size > 0 && isExposedTopLevelDecl(s, exposeNames)) {
@@ -70578,6 +71473,14 @@ function emitSingleAttr$1(attr, ctx) {
70578
71473
  if (styleObjectLowered !== null) return styleObjectLowered;
70579
71474
  const expr = rewriteTemplateExpression$3(attr.expression, ir);
70580
71475
  const outName = resolveAttrName(attr.name, ctx);
71476
+ if (attr.name === "style") {
71477
+ ctx.runtimeImports?.add("rozieStyle");
71478
+ return `${outName}={rozieStyle(${expr})}`;
71479
+ }
71480
+ if (attr.name === "class" && attr.wrapForDisplay && !_babel_types.isObjectExpression(attr.expression) && !_babel_types.isTemplateLiteral(attr.expression) && shouldWrapSvelteAttrBinding(attr.name, attr.expression, ctx)) {
71481
+ ctx.runtimeImports?.add("rozieClass");
71482
+ return `${outName}={rozieClass(${expr})}`;
71483
+ }
70581
71484
  if (attr.wrapForDisplay && shouldWrapSvelteAttrBinding(attr.name, attr.expression, ctx)) {
70582
71485
  ctx.runtimeImports?.add("rozieAttr");
70583
71486
  return `${outName}={rozieAttr(${expr})}`;
@@ -70605,12 +71508,16 @@ function emitSingleAttr$1(attr, ctx) {
70605
71508
  * Convert a single AttributeBinding into a JS expression string suitable for
70606
71509
  * inclusion in an array (used by class/style merge below).
70607
71510
  */
70608
- function attrToArraySegment$1(attr, ir, runtimeImports) {
71511
+ function attrToArraySegment$1(attr, ir, runtimeImports, isClass = false) {
70609
71512
  if (attr.kind === "twoWayBinding") throw new Error(`Svelte target: twoWayBinding not valid in class/style array context (Phase 07.3 Wave 3 Plan 07.3-04).`);
70610
71513
  if (attr.kind === "static") return JSON.stringify(attr.value);
70611
71514
  if (attr.kind === "binding") {
70612
71515
  const code = rewriteTemplateExpression$3(attr.expression, ir);
70613
71516
  if (attr.wrapForDisplay && !_babel_types.isObjectExpression(attr.expression)) {
71517
+ if (isClass && !_babel_types.isTemplateLiteral(attr.expression)) {
71518
+ runtimeImports?.add("rozieClass");
71519
+ return `rozieClass(${code})`;
71520
+ }
70614
71521
  runtimeImports?.add("rozieDisplay");
70615
71522
  return `rozieDisplay(${code})`;
70616
71523
  }
@@ -70701,7 +71608,7 @@ function emitAttributes$2(attrs, ctx) {
70701
71608
  if (synthetic) merged.push(synthetic);
70702
71609
  } else if (src.name === a.name && !isLiteralStyleObjectBinding(src)) merged.push(src);
70703
71610
  for (const x of merged) consumed.add(x);
70704
- const segments = merged.map((x) => attrToArraySegment$1(x, ctx.ir, ctx.runtimeImports));
71611
+ const segments = merged.map((x) => attrToArraySegment$1(x, ctx.ir, ctx.runtimeImports, a.name === "class"));
70705
71612
  if (hasOpaqueMerge) for (const readExpr of opaqueSpreadClassReads) segments.push(readExpr);
70706
71613
  if (hasOpaqueMerge) deferredMergedClassStyle.push(`${a.name}={[${segments.join(", ")}]}`);
70707
71614
  else out.push(`${a.name}={[${segments.join(", ")}]}`);
@@ -72193,7 +73100,8 @@ function composeSourceMap$2(ms, opts) {
72193
73100
  outputOffset: opts.scriptOutputOffset
72194
73101
  }] : [],
72195
73102
  fileExt: ".svelte",
72196
- userCodeLineOffset: opts.userCodeLineOffset
73103
+ userCodeLineOffset: opts.userCodeLineOffset,
73104
+ partialLineOffsets: opts.partialLineOffsets
72197
73105
  });
72198
73106
  }
72199
73107
  //#endregion
@@ -72297,7 +73205,8 @@ function emitSvelte(ir, opts = {}) {
72297
73205
  source: opts.source,
72298
73206
  scriptMap: shellScriptMap,
72299
73207
  scriptOutputOffset,
72300
- userCodeLineOffset
73208
+ userCodeLineOffset,
73209
+ partialLineOffsets: buildPartialLineOffsets(ir.setupBody.scriptProgram)
72301
73210
  }) : null,
72302
73211
  diagnostics: [
72303
73212
  ...scriptDiags,
@@ -73568,7 +74477,15 @@ function buildSlotCtx(slot) {
73568
74477
  */
73569
74478
  function buildNgTemplateContextGuard(componentName, slots) {
73570
74479
  if (slots.length === 0) return null;
73571
- const unionType = slots.map((s) => slotCtxName(s.name)).join(" | ");
74480
+ const seenCtxNames = /* @__PURE__ */ new Set();
74481
+ const ctxNames = [];
74482
+ for (const s of slots) {
74483
+ const ctxName = slotCtxName(s.name);
74484
+ if (seenCtxNames.has(ctxName)) continue;
74485
+ seenCtxNames.add(ctxName);
74486
+ ctxNames.push(ctxName);
74487
+ }
74488
+ const unionType = ctxNames.join(" | ");
73572
74489
  return [
73573
74490
  `static ngTemplateContextGuard(`,
73574
74491
  ` _dir: ${componentName},`,
@@ -73617,7 +74534,7 @@ function buildSlotMethod$2(slot, scopeHash) {
73617
74534
  const slotName = portalKey(slot);
73618
74535
  const tplField = angularTplField(slot);
73619
74536
  const paramNames = slot.portalParamNames ?? [];
73620
- return ` ${slotName}: (container: HTMLElement, scope: ${paramNames.length > 0 ? `{ ${paramNames.map((n) => `${n}: unknown`).join("; ")} }` : "unknown"}): (() => void) => {\n const tpl = this.${tplField}();\n const vcr = this._portalAnchor();\n if (!tpl || !vcr) return () => {};\n` + setAttrLine$2(slotName, scopeHash) + " const view = vcr.createEmbeddedView(tpl, scope as unknown as Record<string, unknown>);\n view.detectChanges();\n for (const node of view.rootNodes as Node[]) container.appendChild(node);\n this._portalViews.add(view as EmbeddedViewRef<unknown>);\n return () => {\n view.destroy();\n this._portalViews.delete(view as EmbeddedViewRef<unknown>);\n };\n },";
74537
+ return ` ${slotName}: (container: HTMLElement, scope: ${paramNames.length > 0 ? `{ ${paramNames.map((n) => `${n}: unknown`).join("; ")} }` : "unknown"}): (() => void) => {\n const tpl = this.${tplField}();\n const vcr = this._portalAnchor();\n if (!tpl || !vcr) return () => {};\n` + setAttrLine$2(slotName, scopeHash) + " const view = vcr.createEmbeddedView(tpl, scope as unknown as Record<string, unknown>);\n view.detectChanges();\n for (const node of view.rootNodes as globalThis.Node[]) container.appendChild(node);\n this._portalViews.add(view as EmbeddedViewRef<unknown>);\n return () => {\n view.destroy();\n this._portalViews.delete(view as EmbeddedViewRef<unknown>);\n };\n },";
73621
74538
  }
73622
74539
  /**
73623
74540
  * Phase 33 / REQ-21 — reactive portal-slot method body.
@@ -73634,7 +74551,7 @@ function buildReactiveSlotMethod$2(slot, scopeHash) {
73634
74551
  const slotName = portalKey(slot);
73635
74552
  const tplField = angularTplField(slot);
73636
74553
  const paramNames = slot.portalParamNames ?? [];
73637
- return ` ${slotName}: (container: HTMLElement, scope: ${paramNames.length > 0 ? `{ ${paramNames.map((n) => `${n}: unknown`).join("; ")} }` : "unknown"}): ReactivePortalHandle => {\n const tpl = this.${tplField}();\n const vcr = this._portalAnchor();\n if (!tpl || !vcr) return { update() {}, dispose() {} };\n` + setAttrLine$2(slotName, scopeHash) + " const view = vcr.createEmbeddedView(tpl, scope as unknown as Record<string, unknown>);\n view.detectChanges();\n for (const node of view.rootNodes as Node[]) container.appendChild(node);\n this._portalViews.add(view as EmbeddedViewRef<unknown>);\n return {\n update: (s: unknown): void => {\n Object.assign(view.context as object, s as object);\n view.detectChanges();\n },\n dispose: (): void => {\n view.destroy();\n this._portalViews.delete(view as EmbeddedViewRef<unknown>);\n },\n };\n },";
74554
+ return ` ${slotName}: (container: HTMLElement, scope: ${paramNames.length > 0 ? `{ ${paramNames.map((n) => `${n}: unknown`).join("; ")} }` : "unknown"}): ReactivePortalHandle => {\n const tpl = this.${tplField}();\n const vcr = this._portalAnchor();\n if (!tpl || !vcr) return { update() {}, dispose() {} };\n` + setAttrLine$2(slotName, scopeHash) + " const view = vcr.createEmbeddedView(tpl, scope as unknown as Record<string, unknown>);\n view.detectChanges();\n for (const node of view.rootNodes as globalThis.Node[]) container.appendChild(node);\n this._portalViews.add(view as EmbeddedViewRef<unknown>);\n return {\n update: (s: unknown): void => {\n Object.assign(view.context as object, s as object);\n view.detectChanges();\n },\n dispose: (): void => {\n view.destroy();\n this._portalViews.delete(view as EmbeddedViewRef<unknown>);\n },\n };\n },";
73638
74555
  }
73639
74556
  function emitPortals$2(ir, scopeHash = "") {
73640
74557
  const portals = ir.slots.filter((s) => s.isPortal === true);
@@ -74097,7 +75014,7 @@ function buildCvaClassShape(prop) {
74097
75014
  return [
74098
75015
  `private __rozieCvaOnChange: (v: ${tsType}) => void = () => {};`,
74099
75016
  `private __rozieCvaOnTouchedFn: () => void = () => {};`,
74100
- `private __rozieCvaDisabled = signal(false);`,
75017
+ `protected __rozieCvaDisabled = signal(false);`,
74101
75018
  ``,
74102
75019
  `writeValue(v: ${tsType} | null): void {`,
74103
75020
  writeValueBody,
@@ -74360,7 +75277,10 @@ function emitScript$2(ir, opts = {}) {
74360
75277
  const interfaceDecls = [];
74361
75278
  for (const typeDecl of hoistedTypeDecls) interfaceDecls.push(genCode$3(typeDecl));
74362
75279
  const slotFieldDecls = [];
75280
+ const seenSlotNames = /* @__PURE__ */ new Set();
74363
75281
  for (const slot of ir.slots) {
75282
+ if (seenSlotNames.has(slot.name)) continue;
75283
+ seenSlotNames.add(slot.name);
74364
75284
  const ctx = buildSlotCtx(slot);
74365
75285
  interfaceDecls.push(ctx.interfaceDecl);
74366
75286
  slotFieldDecls.push(ctx.fieldDecl);
@@ -75323,6 +76243,7 @@ function shouldWrapAttrBinding$1(name, expr, ctx, elementTagName) {
75323
76243
  if (BOOLEAN_HTML_ATTRS$1.has(name.toLowerCase())) return false;
75324
76244
  if ((name === "value" || name === "checked") && FORM_INPUT_TAGS$2.has(elementTagName.toLowerCase())) return false;
75325
76245
  if (name === "style") return false;
76246
+ if (name === "class") return false;
75326
76247
  if (_babel_types.isObjectExpression(expr)) return false;
75327
76248
  return true;
75328
76249
  }
@@ -78091,7 +79012,8 @@ function composeSourceMap$1(ms, opts) {
78091
79012
  outputOffset: opts.scriptOutputOffset
78092
79013
  }] : [],
78093
79014
  fileExt: ".ts",
78094
- userCodeLineOffset: opts.userCodeLineOffset
79015
+ userCodeLineOffset: opts.userCodeLineOffset,
79016
+ partialLineOffsets: opts.partialLineOffsets
78095
79017
  });
78096
79018
  }
78097
79019
  //#endregion
@@ -78477,7 +79399,8 @@ function emitAngular(ir, opts = {}) {
78477
79399
  source: opts.source,
78478
79400
  scriptMap: shellScriptMap,
78479
79401
  scriptOutputOffset,
78480
- userCodeLineOffset
79402
+ userCodeLineOffset,
79403
+ partialLineOffsets: buildPartialLineOffsets(ir.setupBody.scriptProgram)
78481
79404
  }) : null,
78482
79405
  diagnostics: [
78483
79406
  ...scriptResult.diagnostics,
@@ -79421,6 +80344,56 @@ function tryHoistArrowToFunction(stmt) {
79421
80344
  return _babel_types.inherits(fn, stmt);
79422
80345
  }
79423
80346
  /**
80347
+ * Phase 55-04 (literal byte-identity) — reproduce the inline-authored comment
80348
+ * doubling at a script-partial splice boundary.
80349
+ *
80350
+ * In an inline-authored `<script>`, a comment block BETWEEN two statements is
80351
+ * attached by `@babel/parser` to BOTH neighbours (the earlier statement's
80352
+ * `trailingComments` AND the later statement's `leadingComments`). The `.rzts`
80353
+ * script-partial splice instead attaches the boundary banner ONLY to the spliced
80354
+ * node's `leadingComments` — the preceding statement lives in a different source
80355
+ * file and so carries no matching trailing comment. Re-mirroring the spliced
80356
+ * node's leading comments back onto the preceding statement's trailing comments
80357
+ * restores the inline form byte-for-byte:
80358
+ * - whole-program generation (Solid here) prints the (deduped) banner once AND
80359
+ * gets the boundary blank line from `@babel/generator`'s `printJoin`
80360
+ * `_lastCommentLine` path — a trailing comment on the previous statement is
80361
+ * exactly what triggers the loc-delta blank-line insertion; and
80362
+ * - per-statement generation (Vue/Svelte) doubles the banner (one copy as the
80363
+ * previous statement's trailing, one as the next statement's leading).
80364
+ *
80365
+ * Fires ONLY at a genuine splice boundary: the current statement carries
80366
+ * `extra.__roziePartialOrigin` AND its leading comments are not ALREADY shared as
80367
+ * the previous statement's trailing comments (within-partial statement pairs
80368
+ * already share the same comment objects; host-only pairs are left exactly as
80369
+ * authored). MUST run before the arrow→function hoist — `t.inherits` does not
80370
+ * copy `extra`, so the spliced marker is only visible on the original nodes.
80371
+ */
80372
+ function mirrorSpliceBoundaryComments(stmts) {
80373
+ for (let i = 1; i < stmts.length; i++) {
80374
+ const cur = stmts[i];
80375
+ const prev = stmts[i - 1];
80376
+ const lead = cur.leadingComments;
80377
+ if (!lead || lead.length === 0) continue;
80378
+ const extra = cur.extra;
80379
+ const curSpliced = extra?.__roziePartialOrigin !== void 0;
80380
+ const prevSpliced = prev.extra?.__roziePartialOrigin !== void 0;
80381
+ if (!curSpliced) {
80382
+ if (prevSpliced && extra?.__rozieAfterGap !== void 0) {
80383
+ const prevTrail = prev.trailingComments;
80384
+ const lastLead = lead[lead.length - 1];
80385
+ if (prevTrail && prevTrail.length > 0 && prevTrail[prevTrail.length - 1] === lastLead) continue;
80386
+ prev.trailingComments = [...prevTrail ?? [], ...lead];
80387
+ }
80388
+ continue;
80389
+ }
80390
+ const prevTrail = prev.trailingComments;
80391
+ const lastLead = lead[lead.length - 1];
80392
+ if (prevTrail && prevTrail.length > 0 && prevTrail[prevTrail.length - 1] === lastLead) continue;
80393
+ prev.trailingComments = [...prevTrail ?? [], ...lead];
80394
+ }
80395
+ }
80396
+ /**
79424
80397
  * WR-01 ROOT CAUSE 2 — re-project an author function-type annotation written
79425
80398
  * on a `VariableDeclarator` `id` (`const f: (e: E) => R = …`) onto a rebuilt
79426
80399
  * `FunctionDeclaration` (which has no annotatable `id`). Each declarator-type
@@ -79546,7 +80519,7 @@ function emitScript$1(ir, collectors, _registry) {
79546
80519
  }
79547
80520
  }
79548
80521
  for (const ref of ir.refs) hookLines.push(`let ${ref.name}Ref: HTMLElement | null = null;`);
79549
- const filteredStmts = [];
80522
+ const residualStmts = [];
79550
80523
  for (const stmt of rewriteResult.rewrittenProgram.program.body) {
79551
80524
  if (_babel_types.isVariableDeclaration(stmt)) {
79552
80525
  if (stmt.declarations.every((d) => d.init && _babel_types.isCallExpression(d.init) && _babel_types.isIdentifier(d.init.callee) && d.init.callee.name === "$computed")) continue;
@@ -79556,8 +80529,10 @@ function emitScript$1(ir, collectors, _registry) {
79556
80529
  const callee = stmt.expression.callee;
79557
80530
  if (_babel_types.isIdentifier(callee) && (callee.name === "$onMount" || callee.name === "$onUnmount" || callee.name === "$onUpdate" || callee.name === "$watch" || callee.name === "$expose" || callee.name === "$provide")) continue;
79558
80531
  }
79559
- filteredStmts.push(tryHoistArrowToFunction(stmt) ?? stmt);
80532
+ residualStmts.push(stmt);
79560
80533
  }
80534
+ mirrorSpliceBoundaryComments(residualStmts);
80535
+ const filteredStmts = residualStmts.map((stmt) => tryHoistArrowToFunction(stmt) ?? stmt);
79561
80536
  let userArrowsSection = "";
79562
80537
  let scriptMap = null;
79563
80538
  if (filteredStmts.length > 0) {
@@ -80258,6 +81233,10 @@ function composeClassValue(attrs, ir, exprOpts, runtime) {
80258
81233
  if (attrs.length === 1 && attrs[0].kind === "binding") {
80259
81234
  const a = attrs[0];
80260
81235
  if (_babel_types.isObjectExpression(a.expression)) return renderExpr(a.expression, ir, exprOpts);
81236
+ if (a.wrapForDisplay && !_babel_types.isTemplateLiteral(a.expression)) {
81237
+ runtime?.add("rozieClass");
81238
+ return `rozieClass(${renderExpr(a.expression, ir, exprOpts)})`;
81239
+ }
80261
81240
  return renderExpr(a.expression, ir, exprOpts);
80262
81241
  }
80263
81242
  if (attrs.length === 1 && attrs[0].kind === "interpolated") {
@@ -80276,7 +81255,10 @@ function composeClassValue(attrs, ir, exprOpts, runtime) {
80276
81255
  const parts = [];
80277
81256
  for (const a of attrs) if (a.kind === "twoWayBinding") throw new Error(`Solid target: twoWayBinding not valid in class array context (Phase 07.3 Wave 3 Plan 07.3-07).`);
80278
81257
  else if (a.kind === "static") parts.push(renderStaticClassValue(a.value));
80279
- else if (a.kind === "binding") parts.push(`(${renderExpr(a.expression, ir, exprOpts)})`);
81258
+ else if (a.kind === "binding") if (a.wrapForDisplay && !_babel_types.isTemplateLiteral(a.expression)) {
81259
+ runtime?.add("rozieClass");
81260
+ parts.push(`rozieClass(${renderExpr(a.expression, ir, exprOpts)})`);
81261
+ } else parts.push(`(${renderExpr(a.expression, ir, exprOpts)})`);
80280
81262
  else if (a.kind === "spreadBinding") throw new Error(`Solid target: spreadBinding not valid in class array context (Phase 14).`);
80281
81263
  else {
80282
81264
  let lit = "";
@@ -82927,7 +83909,8 @@ function composeSourceMap(ms, opts) {
82927
83909
  outputOffset: opts.scriptOutputOffset
82928
83910
  }] : [],
82929
83911
  fileExt: ".tsx",
82930
- userCodeLineOffset: opts.userCodeLineOffset
83912
+ userCodeLineOffset: opts.userCodeLineOffset,
83913
+ partialLineOffsets: opts.partialLineOffsets
82931
83914
  });
82932
83915
  }
82933
83916
  //#endregion
@@ -83033,7 +84016,8 @@ function emitSolid(ir, opts = {}) {
83033
84016
  source: opts.source,
83034
84017
  scriptMap: shell.scriptMap,
83035
84018
  scriptOutputOffset: shell.scriptOutputOffset,
83036
- userCodeLineOffset: shell.userCodeLineOffset
84019
+ userCodeLineOffset: shell.userCodeLineOffset,
84020
+ partialLineOffsets: buildPartialLineOffsets(ir.setupBody.scriptProgram)
83037
84021
  }) : null;
83038
84022
  const diagnostics = [
83039
84023
  ...scriptResult.diagnostics,
@@ -85905,6 +86889,10 @@ function emitAttribute(attr, ir, tagName, tagKind = "html", opts) {
85905
86889
  return `style=\${styleMap(${expr})}`;
85906
86890
  }
85907
86891
  const expr = rewriteTemplateExpression(attr.expression, ir);
86892
+ if (attr.name === "style") {
86893
+ opts?.runtime.add("rozieStyle");
86894
+ return `style=\${rozieStyle(${expr})}`;
86895
+ }
85908
86896
  if (tagKind === "component" || tagKind === "self") {
85909
86897
  const propName = attr.name.includes("-") ? attr.name.replace(/-([a-z])/g, (_, ch) => ch.toUpperCase()) : attr.name;
85910
86898
  if (opts?._state && (_babel_types.isArrayExpression(attr.expression) || _babel_types.isObjectExpression(attr.expression)) && isPureLiteral(attr.expression)) {
@@ -86324,9 +87312,12 @@ function emitElementOpenTag(node, ir, irName, opts) {
86324
87312
  const expr = rewriteTemplateExpression(bindingClass.expression, ir);
86325
87313
  const staticPart = staticClassValues.length > 0 ? `${staticClassValues.join(" ")} ` : "";
86326
87314
  let classExpr = expr;
86327
- if (bindingClass.wrapForDisplay) {
87315
+ if (bindingClass.wrapForDisplay) if (_babel_types.isTemplateLiteral(bindingClass.expression)) {
86328
87316
  opts.runtime.add("rozieDisplay");
86329
87317
  classExpr = `rozieDisplay(${expr})`;
87318
+ } else {
87319
+ opts.runtime.add("rozieClass");
87320
+ classExpr = `rozieClass(${expr})`;
86330
87321
  }
86331
87322
  parts.push(`class="${staticPart}\${(${classExpr})}"`);
86332
87323
  } else if (bindingClass.kind === "interpolated") {