@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.mjs CHANGED
@@ -42436,6 +42436,66 @@ function subtreeReads(node, accessor, name) {
42436
42436
  return found;
42437
42437
  }
42438
42438
  /**
42439
+ * Returns true if the subtree rooted at `node` contains a BARE Identifier READ of
42440
+ * `name` — the exact shape the Vue `$computed` lowering wraps to `<name>.value`.
42441
+ *
42442
+ * The Vue trigger condition for the `$computed` shadow bug: a `$computed` name is
42443
+ * read as a BARE identifier in source (there is NO `$computed.<name>` accessor
42444
+ * member to gate on), and the downstream Identifier visitor `.value`-wraps every
42445
+ * such bare read. A colliding param/local only mis-captures that wrap when it
42446
+ * actually performs a bare read of `name` within its scope — so a `binding`
42447
+ * trigger (mere existence) would over-apply (renaming an unused same-named param
42448
+ * → corpus drift), and the `accessor` trigger cannot fire (no member access).
42449
+ *
42450
+ * Excludes the SAME non-read positions the Vue Identifier visitor skips, so the
42451
+ * gate matches the rewrite exactly: declaration ids, non-computed member
42452
+ * properties, object keys (shorthand or not), TS type positions, function names,
42453
+ * function params, and import/export specifier ids. A computed member property
42454
+ * (`obj[name]`) IS a bare read and counts.
42455
+ *
42456
+ * Hand-rolled own-child walk (the `subtreeReads` twin): a direct walk with
42457
+ * parent-context tracking is simpler than a Program-rooted Babel traverse and
42458
+ * has no rooting constraint.
42459
+ */
42460
+ function subtreeReadsBareIdentifier(node, name) {
42461
+ if (!node) return false;
42462
+ let found = false;
42463
+ function walk(n, parent) {
42464
+ if (found || !n || typeof n !== "object" || !("type" in n)) return;
42465
+ if (t$3.isIdentifier(n) && n.name === name) {
42466
+ if (isBareRead(n, parent)) {
42467
+ found = true;
42468
+ return;
42469
+ }
42470
+ }
42471
+ if (n.type.startsWith("TS") && n.type !== "TSNonNullExpression" && n.type !== "TSAsExpression" && n.type !== "TSSatisfiesExpression" && n.type !== "TSTypeAssertion") return;
42472
+ for (const key of Object.keys(n)) {
42473
+ if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments" || key === "innerComments") continue;
42474
+ const v = n[key];
42475
+ if (Array.isArray(v)) {
42476
+ for (const item of v) if (item && typeof item === "object" && "type" in item) walk(item, n);
42477
+ } else if (v && typeof v === "object" && "type" in v) walk(v, n);
42478
+ }
42479
+ }
42480
+ function isBareRead(id, parent) {
42481
+ if (!parent) return true;
42482
+ if (t$3.isVariableDeclarator(parent) && parent.id === id) return false;
42483
+ if ((t$3.isMemberExpression(parent) || t$3.isOptionalMemberExpression(parent)) && parent.property === id && !parent.computed) return false;
42484
+ if (t$3.isObjectProperty(parent) && parent.key === id && !parent.computed) return false;
42485
+ if (t$3.isFunctionDeclaration(parent) && parent.id === id) return false;
42486
+ if (t$3.isFunctionExpression(parent) && parent.id === id) return false;
42487
+ if (t$3.isFunction(parent) && parent.params.includes(id)) return false;
42488
+ if (t$3.isImportSpecifier(parent)) return false;
42489
+ if (t$3.isImportDefaultSpecifier(parent)) return false;
42490
+ if (t$3.isImportNamespaceSpecifier(parent)) return false;
42491
+ if (t$3.isExportSpecifier(parent)) return false;
42492
+ if (t$3.isLabeledStatement(parent) && parent.label === id) return false;
42493
+ return true;
42494
+ }
42495
+ walk(node, null);
42496
+ return found;
42497
+ }
42498
+ /**
42439
42499
  * THE UNIFIED PASS. Renames any USER binding (function param or
42440
42500
  * `const`/`let`/`var` declarator) that collides with a generated symbol to
42441
42501
  * `<name>$local`, atomically across its binding scope, gated only-on-collision
@@ -42455,6 +42515,7 @@ function deconflictGeneratedSymbols(program, groups, protectedNames = /* @__PURE
42455
42515
  if (!group.names.has(name)) return false;
42456
42516
  if (protectedNames.has(name)) return false;
42457
42517
  if (group.trigger.kind === "binding") return true;
42518
+ if (group.trigger.kind === "bare-read") return subtreeReadsBareIdentifier(scopeBlock, name);
42458
42519
  return subtreeReads(scopeBlock, group.trigger.accessor, name);
42459
42520
  };
42460
42521
  traverse$19(program, {
@@ -42468,6 +42529,7 @@ function deconflictGeneratedSymbols(program, groups, protectedNames = /* @__PURE
42468
42529
  if (!patternIntroducesBinding$3(id, name)) continue;
42469
42530
  const binding = path.scope.getBinding(name);
42470
42531
  const ownerScope = binding ? binding.scope : path.scope;
42532
+ if (group.trigger.kind === "bare-read" && t$3.isProgram(ownerScope.block)) continue;
42471
42533
  if (collides(group, name, ownerScope.block)) ownerScope.rename(name, alias(name));
42472
42534
  }
42473
42535
  },
@@ -51093,17 +51155,53 @@ function bindingNames(stmt) {
51093
51155
  function importLocalNames(imp) {
51094
51156
  return imp.specifiers.map((s) => s.local.name);
51095
51157
  }
51158
+ /**
51159
+ * Root identifier of a TS entity name (`Foo` or `Foo.Bar.Baz` → `Foo`). A
51160
+ * `TSImportType` (`import('pkg').Foo`) has no root local identifier — returns
51161
+ * null (its module surface is already self-contained, never a hoist target).
51162
+ */
51163
+ function rootTypeIdentifier(name) {
51164
+ let node = name;
51165
+ while (node && t$3.isTSQualifiedName(node)) node = node.left;
51166
+ return node && t$3.isIdentifier(node) ? node.name : null;
51167
+ }
51096
51168
  /** Referenced identifier names within a statement (excludes bindings/keys). */
51097
51169
  function referencedNames(stmt) {
51098
51170
  const out = /* @__PURE__ */ new Set();
51099
51171
  try {
51100
- traverse$18(t$3.file(t$3.program([stmt])), { ReferencedIdentifier(path) {
51101
- out.add(path.node.name);
51102
- } });
51172
+ traverse$18(t$3.file(t$3.program([stmt])), {
51173
+ ReferencedIdentifier(path) {
51174
+ out.add(path.node.name);
51175
+ },
51176
+ TSTypeReference(path) {
51177
+ const root = rootTypeIdentifier(path.node.typeName);
51178
+ if (root) out.add(root);
51179
+ },
51180
+ TSTypeQuery(path) {
51181
+ const root = rootTypeIdentifier(path.node.exprName);
51182
+ if (root) out.add(root);
51183
+ }
51184
+ });
51103
51185
  } catch {}
51104
51186
  return out;
51105
51187
  }
51106
51188
  /**
51189
+ * Effective import kind of a single specifier. A specifier is type-only when
51190
+ * EITHER the declaration is `import type { … }` (declKind === 'type', in which
51191
+ * case Babel reports the per-specifier `importKind` as `'value'`) OR the
51192
+ * specifier itself is the inline `import { type X }` form (spec.importKind ===
51193
+ * 'type'). The old `spec.importKind ? spec.importKind : declKind` form let the
51194
+ * `'value'` per-specifier kind of a declaration-level `import type` mask the
51195
+ * declaration's type-ness, so a hoisted `import type { T }` lost its type-only
51196
+ * marker and emitted as a runtime `import { T }` (IN-02). Value imports are
51197
+ * unaffected (both kinds are `'value'`).
51198
+ */
51199
+ function effectiveImportKind(declKind, spec) {
51200
+ if (declKind === "type") return "type";
51201
+ if (t$3.isImportSpecifier(spec) && spec.importKind === "type") return "type";
51202
+ return "value";
51203
+ }
51204
+ /**
51107
51205
  * Dedup key for a single import specifier per the R4 tuple:
51108
51206
  * (source, importKind, default|namespace|named:imported, local). Including the
51109
51207
  * LOCAL name keeps `import { thing }` and `import { thing as aliased }` distinct
@@ -51111,7 +51209,7 @@ function referencedNames(stmt) {
51111
51209
  * host and partial collapses to ONE statement.
51112
51210
  */
51113
51211
  function specifierKey(source, declKind, spec) {
51114
- const kind = (t$3.isImportSpecifier(spec) && spec.importKind ? spec.importKind : declKind) || "value";
51212
+ const kind = effectiveImportKind(declKind, spec);
51115
51213
  if (t$3.isImportDefaultSpecifier(spec)) return `${source}\0${kind}\0default\0${spec.local.name}`;
51116
51214
  if (t$3.isImportNamespaceSpecifier(spec)) return `${source}\0${kind}\0namespace\0${spec.local.name}`;
51117
51215
  return `${source}\0${kind}\0named:${t$3.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value}\0${spec.local.name}`;
@@ -51121,12 +51219,12 @@ function specifierKey(source, declKind, spec) {
51121
51219
  * deduped by {@link specifierKey} and grouped by (source, importKind) so the
51122
51220
  * splice produces idiomatic merged `import { a, b } from 'src'` statements.
51123
51221
  */
51124
- function hoistSpecifier(ctx, sourceNode, declKind, spec) {
51222
+ function hoistSpecifier(ctx, sourceNode, declKind, spec, sourceImport) {
51125
51223
  const source = sourceNode.value;
51126
51224
  const key = specifierKey(source, declKind, spec);
51127
51225
  if (ctx.hoistKeys.has(key)) return;
51128
51226
  ctx.hoistKeys.add(key);
51129
- const groupKind = (t$3.isImportSpecifier(spec) && spec.importKind ? spec.importKind : declKind) || "value";
51227
+ const groupKind = effectiveImportKind(declKind, spec);
51130
51228
  const groupKey = `${source}\0${groupKind}`;
51131
51229
  const existing = ctx.hoistGroups.get(groupKey);
51132
51230
  if (existing) {
@@ -51135,9 +51233,427 @@ function hoistSpecifier(ctx, sourceNode, declKind, spec) {
51135
51233
  }
51136
51234
  const decl = t$3.importDeclaration([spec], sourceNode);
51137
51235
  if (groupKind === "type") decl.importKind = "type";
51236
+ if (sourceImport) {
51237
+ if (sourceImport.loc) decl.loc = sourceImport.loc;
51238
+ if (sourceImport.trailingComments) decl.trailingComments = sourceImport.trailingComments;
51239
+ if (sourceImport.innerComments) decl.innerComments = sourceImport.innerComments;
51240
+ }
51138
51241
  ctx.hoistGroups.set(groupKey, decl);
51139
51242
  ctx.hoistImports.push(decl);
51140
51243
  }
51244
+ /**
51245
+ * Shift a single Babel `Position` object's `.line` by `offset`, exactly once.
51246
+ *
51247
+ * CRITICAL (Phase 55-04 bug fix): `@babel/parser` SHARES one `Position` object
51248
+ * across the `loc.start`/`loc.end` of nested nodes that begin/end at the same
51249
+ * source position — e.g. a `const f = () => {...}` shares ONE `loc.end` object
51250
+ * between the VariableDeclaration, VariableDeclarator, ArrowFunctionExpression
51251
+ * and BlockStatement (verified: all four `.loc.end` are `===`). The earlier
51252
+ * `seen`-keyed-on-the-`loc`-WRAPPER dedupe therefore shifted such a shared
51253
+ * `Position` once PER wrapping node (4× for the example above), corrupting
51254
+ * `loc.end.line` to `original + 4·offset` (~13985 for a host-line-3502 decl).
51255
+ * That blew the trailing-comment delta hugely negative, collapsing a between-
51256
+ * declaration comment onto the prior closing brace (`}; // comment`). Deduping on
51257
+ * the `Position` object itself shifts each unique position exactly once. Never
51258
+ * throws (D-04).
51259
+ */
51260
+ function shiftPositionLine(pos, offset, shifted) {
51261
+ if (!pos || shifted.has(pos)) return;
51262
+ shifted.add(pos);
51263
+ pos.line += offset;
51264
+ }
51265
+ /**
51266
+ * Stash a node's `.rzts` origin (once, idempotent) then shift its `loc` lines by
51267
+ * `offset`. Byte offsets (`loc.start.index`/`loc.end.index`) and `loc.filename`
51268
+ * are NEVER touched. `shifted` dedupes shared `Position` objects (see
51269
+ * {@link shiftPositionLine}) so a position aliased across nested nodes — or a
51270
+ * comment attached to two adjacent statements — is shifted exactly once
51271
+ * (Pitfall 2). Never throws (D-04).
51272
+ */
51273
+ function stashAndShiftNode(node, offset, shifted) {
51274
+ const loc = node.loc;
51275
+ if (!loc) return;
51276
+ const extra = node.extra ?? {};
51277
+ if (!("__roziePartialOrigin" in extra)) {
51278
+ const startAlreadyShifted = shifted.has(loc.start);
51279
+ node.extra = {
51280
+ ...extra,
51281
+ __roziePartialOrigin: {
51282
+ line: loc.start.line - (startAlreadyShifted ? offset : 0),
51283
+ column: loc.start.column,
51284
+ filename: loc.filename
51285
+ }
51286
+ };
51287
+ }
51288
+ shiftPositionLine(loc.start, offset, shifted);
51289
+ shiftPositionLine(loc.end, offset, shifted);
51290
+ }
51291
+ /** As {@link stashAndShiftNode} but for a comment (origin stashed on the comment). */
51292
+ function stashAndShiftComment(comment, offset, shifted) {
51293
+ const loc = comment.loc;
51294
+ if (!loc) return;
51295
+ const c = comment;
51296
+ if (c.__roziePartialOrigin === void 0) {
51297
+ const startAlreadyShifted = shifted.has(loc.start);
51298
+ c.__roziePartialOrigin = {
51299
+ line: loc.start.line - (startAlreadyShifted ? offset : 0),
51300
+ column: loc.start.column,
51301
+ filename: loc.filename
51302
+ };
51303
+ }
51304
+ shiftPositionLine(loc.start, offset, shifted);
51305
+ shiftPositionLine(loc.end, offset, shifted);
51306
+ }
51307
+ /** Shift every leading/trailing/inner comment attached to `node`. */
51308
+ function shiftAttachedComments(node, offset, shifted) {
51309
+ for (const c of node.leadingComments ?? []) stashAndShiftComment(c, offset, shifted);
51310
+ for (const c of node.trailingComments ?? []) stashAndShiftComment(c, offset, shifted);
51311
+ for (const c of node.innerComments ?? []) stashAndShiftComment(c, offset, shifted);
51312
+ }
51313
+ /** The line a block's first emitted token occupies: its banner comment if it has
51314
+ * leading comments, else the node itself. */
51315
+ function blockFirstEmitLine(firstNode) {
51316
+ const firstLeading = firstNode.leadingComments?.[0]?.loc;
51317
+ return firstLeading ? firstLeading.start.line : firstNode.loc.start.line;
51318
+ }
51319
+ /**
51320
+ * Phase 56-R10 — true when `stmt` is a sigil DIRECTIVE that every target's residual-body
51321
+ * emit STRIPS (it is consumed into a non-residual section: lifecycle / context / computed /
51322
+ * expose / watch). Mirrors the strip lists in each `emitResidualScriptBody`
51323
+ * (`$onMount`/`$onUnmount`/`$onUpdate`/`$watch`/`$expose`/`$provide` ExpressionStatements,
51324
+ * and `$computed`/`$inject` VariableDeclarations).
51325
+ *
51326
+ * USED ONLY to decide whether a spliced LEADING-comment run's IMMEDIATE source predecessor
51327
+ * survives in the residual body. When that predecessor is STRIPPED (e.g. the real DataTable
51328
+ * `exposeStateVerbs` run sits below a `$provide(...)`), the inline-authored form attaches the
51329
+ * boundary comment to the predecessor's `trailingComments` + the spliced decl's
51330
+ * `leadingComments` (shared object), but per-statement generation drops the predecessor's
51331
+ * trailing copy WITH the stripped statement → the comment SINGLE-emits. The vue/svelte splice
51332
+ * mirror would otherwise re-create that prev-trailing copy and DOUBLE it (the R10 bug). When
51333
+ * the predecessor SURVIVES (a plain `let`/`const`, e.g. `let expandedTouched` above
51334
+ * `groupingActiveDefault`), both copies survive → the inline form DOUBLES and the mirror must
51335
+ * keep doing so. Never throws.
51336
+ */
51337
+ function isStrippedSigilDirective(stmt) {
51338
+ if (t$3.isExpressionStatement(stmt) && t$3.isCallExpression(stmt.expression)) {
51339
+ const callee = stmt.expression.callee;
51340
+ if (t$3.isIdentifier(callee)) return callee.name === "$onMount" || callee.name === "$onUnmount" || callee.name === "$onUpdate" || callee.name === "$watch" || callee.name === "$expose" || callee.name === "$provide";
51341
+ return false;
51342
+ }
51343
+ if (t$3.isVariableDeclaration(stmt) && stmt.declarations.length > 0) return stmt.declarations.every((d) => d.init !== null && d.init !== void 0 && t$3.isCallExpression(d.init) && t$3.isIdentifier(d.init.callee) && (d.init.callee.name === "$computed" || d.init.callee.name === "$inject"));
51344
+ return false;
51345
+ }
51346
+ /**
51347
+ * Measure the ORIGINAL source gap above a decl run's first emit token (D-02, R2).
51348
+ *
51349
+ * Returns the `prevEnd + gap` delta the run should flow at: the distance, in source
51350
+ * lines, from the run's first emit token (its banner comment if any, else its first
51351
+ * node) DOWN from the END of the nearest preceding node IN THE SAME SOURCE FILE — a
51352
+ * same-file freshly-hoisted import (`sameFileHoists`) or the prior same-file decl run's
51353
+ * last node (`prevRunLastNode`). `gap = firstEmitLine − precedingEnd` so a zero-blank
51354
+ * adjacency yields `1` (next line) and N blanks yield `N+1` (no clamp). All `loc`s are
51355
+ * still PARTIAL-LOCAL here (the normalize shift runs later), so this reproduces the
51356
+ * partial's own pre-extraction layout — which, by the extraction rule, equals the host
51357
+ * adjacency the inline oracle has (Pitfall 3: measure source-side, NOT the host seam).
51358
+ *
51359
+ * When the run's first decl has NO same-file predecessor (a file-top nested-partial
51360
+ * decl, e.g. HostD's `inner` at the top of its own file), there is no source delta to
51361
+ * measure, so this falls back to the legacy default `2` — preserving the pre-D-02
51362
+ * behavior for that shape (no regression). Never throws.
51363
+ */
51364
+ function measureOriginalGap(firstNode, sameFileHoists, prevRunLastNode) {
51365
+ const loc = firstNode.loc;
51366
+ if (!loc) return 2;
51367
+ const firstEmitLine = blockFirstEmitLine(firstNode);
51368
+ const fname = loc.filename;
51369
+ let precedingEnd = 0;
51370
+ for (const h of sameFileHoists) {
51371
+ const hl = h.loc;
51372
+ if (hl && hl.filename === fname && hl.end.line < firstEmitLine) precedingEnd = Math.max(precedingEnd, hl.end.line);
51373
+ }
51374
+ const pl = prevRunLastNode?.loc;
51375
+ if (pl && pl.filename === fname && pl.end.line < firstEmitLine) precedingEnd = Math.max(precedingEnd, pl.end.line);
51376
+ if (precedingEnd === 0) return 2;
51377
+ return Math.max(1, firstEmitLine - precedingEnd);
51378
+ }
51379
+ /**
51380
+ * Shift every node + attached comment in a block by `offset`, deduping on the
51381
+ * underlying `Position` objects (see {@link shiftPositionLine}). Never throws.
51382
+ *
51383
+ * WR-01: `shifted` is supplied by the CALLER and SHARED across every block in one
51384
+ * normalize pass. A between-statement comment can be aliased across two blocks —
51385
+ * `@babel/parser` attaches the comment between a hoisted import and the first decl
51386
+ * to BOTH the import's `trailingComments` and the decl's `leadingComments` (the
51387
+ * SAME `Position` objects). When the hoist and that decl run land in separate
51388
+ * blocks, a per-block `shifted` set would shift the aliased comment TWICE (once per
51389
+ * block), corrupting its emit line. A shared set shifts each unique `Position`
51390
+ * exactly once. The adjacent hoist/decl offsets are equal by construction (the
51391
+ * sequential decl anchor is derived from the shifted hoist end + the same gap the
51392
+ * partial's import→decl delta encodes), so first-touch-wins is the correct offset.
51393
+ */
51394
+ function shiftBlock(block, offset, shifted) {
51395
+ for (const top of block.nodes) {
51396
+ stashAndShiftNode(top, offset, shifted);
51397
+ shiftAttachedComments(top, offset, shifted);
51398
+ try {
51399
+ traverse$18(t$3.file(t$3.program([top])), { enter(path) {
51400
+ stashAndShiftNode(path.node, offset, shifted);
51401
+ shiftAttachedComments(path.node, offset, shifted);
51402
+ } });
51403
+ } catch {}
51404
+ }
51405
+ }
51406
+ /** Shift a single comment's loc lines by `offset` WITHOUT stashing a partial origin. */
51407
+ function shiftCommentLinesOnly(comment, offset, shifted) {
51408
+ const loc = comment.loc;
51409
+ if (!loc) return;
51410
+ shiftPositionLine(loc.start, offset, shifted);
51411
+ shiftPositionLine(loc.end, offset, shifted);
51412
+ }
51413
+ /**
51414
+ * Phase 56-R8 — shift a HOST node's loc + attached/nested comments by `offset`,
51415
+ * deduping on the underlying `Position` objects (shared `shifted` set, like
51416
+ * {@link shiftBlock}). Never throws (D-04).
51417
+ *
51418
+ * Unlike {@link shiftBlock} / {@link stashAndShiftNode}, this does NOT stash a
51419
+ * `__roziePartialOrigin`: a host statement belongs to the HOST `.rozie` file, so its
51420
+ * `loc` is the host source-map anchor and must NOT be re-keyed onto a partial origin
51421
+ * (`buildPartialLineOffsets` is per-FILE first-stash-wins and would mis-offset every
51422
+ * other host segment). This is invoked ONLY for an after-side host run that carries a
51423
+ * GENUINE intended blank below a spliced run (`afterGap >= 2`) — the gap-1 trailing
51424
+ * seam. Such runs never appear in any built source-map gate today (dist-parity compiles
51425
+ * with `sourceMap: false`; the R5 smoke fixtures have no `afterGap >= 2` host run), so
51426
+ * the line shift is invisible to the source-map gates while making the @babel/generator
51427
+ * blank-line delta reproduce the source's after-side blank on vue/svelte/solid. The
51428
+ * host node's residual source-map LINE imperfection for such runs is the same class of
51429
+ * `userCodeLineOffset` limitation already documented in deferred-items.md (#1).
51430
+ */
51431
+ function shiftHostNodeLines(node, offset, shifted) {
51432
+ if (offset === 0) return;
51433
+ const apply = (n) => {
51434
+ if (n.loc) {
51435
+ shiftPositionLine(n.loc.start, offset, shifted);
51436
+ shiftPositionLine(n.loc.end, offset, shifted);
51437
+ }
51438
+ for (const c of n.leadingComments ?? []) shiftCommentLinesOnly(c, offset, shifted);
51439
+ for (const c of n.trailingComments ?? []) shiftCommentLinesOnly(c, offset, shifted);
51440
+ for (const c of n.innerComments ?? []) shiftCommentLinesOnly(c, offset, shifted);
51441
+ };
51442
+ apply(node);
51443
+ try {
51444
+ traverse$18(t$3.file(t$3.program([node])), { enter(path) {
51445
+ apply(path.node);
51446
+ } });
51447
+ } catch {}
51448
+ }
51449
+ /**
51450
+ * FINAL inline-pass step (Phase 55) — decouple the line `@babel/generator` reads
51451
+ * for blank-line/comment placement from the line it reads for the source-map
51452
+ * origin, for every spliced partial node.
51453
+ *
51454
+ * Rationale (RESEARCH Key Finding 1): under `retainLines:false` the generator's
51455
+ * comment-adjacency + blank-line math reads `node.loc.start.line` and
51456
+ * `comment.loc.start.line` only as DELTAS; only the host↔partial (and
51457
+ * partial↔partial) BOUNDARY deltas are wrong (a spliced node carries a small
51458
+ * `.rzts`-local line discontinuous with its host neighbour). A CONSTANT per-block
51459
+ * offset preserves each block's intra deltas; the right boundary delta is restored
51460
+ * by anchoring each block SEQUENTIALLY in residual-body order.
51461
+ *
51462
+ * SEQUENTIAL ANCHOR (Phase 55-04): each block's first emitted line (its banner
51463
+ * comment, else its first node) is anchored ONE blank line below the PRECEDING
51464
+ * statement in the final body. Anchoring every block at its own replaced import
51465
+ * line (Plan 02's approach) collapsed multi-partial hosts: three consecutive
51466
+ * mid-body imports made expand/group/facet pile onto adjacent host lines, so each
51467
+ * 30-45-line block overlapped the next and the partial↔partial comment deltas went
51468
+ * negative (`}; // banner` collapse). Flowing the blocks sequentially reproduces the
51469
+ * inline-authored contiguous layout. The first block (or any block preceded only by
51470
+ * un-located content) falls back to its replaced import's host line.
51471
+ *
51472
+ * WR-01 (whole-program byte-identity): the walk runs over the FULL emit body
51473
+ * (`[...hoistImports, ...residualBody]`), NOT just the residual body, so a
51474
+ * freshly-hoisted import flows in true emit order ahead of the decls. Consecutive
51475
+ * IMPORT blocks anchor CONTIGUOUSLY (gap 1 — no blank between imports); every other
51476
+ * run anchors one blank line below the preceding statement (gap 2). Because each
51477
+ * source-file decl run is now its OWN block (see {@link SplicedEmitBlock}), a
51478
+ * nested-partial decl run flows one blank line below the parent decl run rather than
51479
+ * inheriting the hoist's incommensurate file-top offset.
51480
+ *
51481
+ * TWO PASSES (WR-01): a between-statement comment is aliased across blocks — the
51482
+ * `Position` that is a hoisted import's `trailingComments[i]` is the SAME object as
51483
+ * the next decl's `leadingComments[i]`. If offsets were applied while walking, the
51484
+ * hoist block would shift that comment, and the decl block's `blockFirstEmitLine`
51485
+ * would then read the ALREADY-SHIFTED comment line and derive a wrong offset
51486
+ * (collapsing the comment onto the decl). So PASS 1 MEASURES every block's offset
51487
+ * from ORIGINAL (unmutated) lines, tracking the running emit end arithmetically;
51488
+ * PASS 2 MUTATES, applying every offset with ONE shared dedup set so each aliased
51489
+ * `Position` shifts exactly once (adjacent hoist/decl offsets are equal by
51490
+ * construction, so first-touch-wins is the correct line).
51491
+ *
51492
+ * Runs AFTER all diagnostics are collected (they captured true `.rzts` byte loc via
51493
+ * `nodeLoc`, which reads `node.start`/`node.end`, NOT `loc.{line,column}`), so the
51494
+ * R7 error-frame path is untouched (Pitfall 1). Never throws (D-04).
51495
+ */
51496
+ function normalizeSplicedEmitLines(body, blocks) {
51497
+ const nodeToBlock = /* @__PURE__ */ new Map();
51498
+ for (const b of blocks) for (const n of b.nodes) nodeToBlock.set(n, b);
51499
+ const measured = /* @__PURE__ */ new Set();
51500
+ const plan = [];
51501
+ let prevEnd = 0;
51502
+ let prevWasImport = false;
51503
+ let prevWasBlock = false;
51504
+ let prevBlockAnchorLine = 0;
51505
+ let hostRunOffset = 0;
51506
+ let seamAfterGap;
51507
+ let prevWasHostStmt = false;
51508
+ let prevHostOrigEnd = 0;
51509
+ let prevHostStmt = null;
51510
+ for (const stmt of body) {
51511
+ const block = nodeToBlock.get(stmt);
51512
+ if (block) {
51513
+ if (measured.has(block)) continue;
51514
+ measured.add(block);
51515
+ const firstNode = block.nodes.find((n) => n.loc);
51516
+ if (!firstNode?.loc) continue;
51517
+ const isImportBlock = t$3.isImportDeclaration(block.nodes[0]);
51518
+ let gap = isImportBlock && prevWasImport ? 1 : block.originalGap;
51519
+ let stampLeadingSeamStripped = false;
51520
+ if (!isImportBlock && prevWasHostStmt) {
51521
+ const beforeGap = block.anchorLine - prevHostOrigEnd;
51522
+ if (blockFirstEmitLine(firstNode) < firstNode.loc.start.line) {
51523
+ if (beforeGap >= 1 && beforeGap < gap) gap = beforeGap;
51524
+ if (prevHostStmt && isStrippedSigilDirective(prevHostStmt)) stampLeadingSeamStripped = true;
51525
+ }
51526
+ }
51527
+ const offset = (prevEnd > 0 ? prevEnd + gap : block.anchorLine) - blockFirstEmitLine(firstNode);
51528
+ let maxEnd = 0;
51529
+ for (const n of block.nodes) if (n.loc) maxEnd = Math.max(maxEnd, n.loc.end.line);
51530
+ prevEnd = maxEnd + offset;
51531
+ prevWasImport = isImportBlock;
51532
+ prevWasBlock = true;
51533
+ prevWasHostStmt = false;
51534
+ prevHostStmt = null;
51535
+ prevBlockAnchorLine = block.anchorLine;
51536
+ plan.push(stampLeadingSeamStripped ? {
51537
+ block,
51538
+ offset,
51539
+ leadingSeamPrevStripped: true
51540
+ } : {
51541
+ block,
51542
+ offset
51543
+ });
51544
+ continue;
51545
+ }
51546
+ if (stmt.loc) {
51547
+ const firstEmit = blockFirstEmitLine(stmt);
51548
+ let offset;
51549
+ if (prevWasBlock) {
51550
+ const afterGap = firstEmit - prevBlockAnchorLine;
51551
+ if (afterGap >= 2) {
51552
+ offset = prevEnd + afterGap - firstEmit;
51553
+ seamAfterGap = afterGap;
51554
+ } else offset = 0;
51555
+ hostRunOffset = offset;
51556
+ } else offset = hostRunOffset;
51557
+ if (offset !== 0) plan.push(seamAfterGap !== void 0 ? {
51558
+ hostNode: stmt,
51559
+ offset,
51560
+ afterGap: seamAfterGap
51561
+ } : {
51562
+ hostNode: stmt,
51563
+ offset
51564
+ });
51565
+ prevEnd = stmt.loc.end.line + offset;
51566
+ seamAfterGap = void 0;
51567
+ prevWasImport = t$3.isImportDeclaration(stmt);
51568
+ prevWasBlock = false;
51569
+ prevWasHostStmt = true;
51570
+ prevHostOrigEnd = stmt.loc.end.line;
51571
+ prevHostStmt = stmt;
51572
+ }
51573
+ }
51574
+ for (const block of blocks) {
51575
+ if (measured.has(block)) continue;
51576
+ measured.add(block);
51577
+ const firstNode = block.nodes.find((n) => n.loc);
51578
+ if (!firstNode?.loc) continue;
51579
+ plan.push({
51580
+ block,
51581
+ offset: block.anchorLine - blockFirstEmitLine(firstNode)
51582
+ });
51583
+ }
51584
+ const shifted = /* @__PURE__ */ new Set();
51585
+ for (const entry of plan) if ("block" in entry) {
51586
+ shiftBlock(entry.block, entry.offset, shifted);
51587
+ if (entry.leadingSeamPrevStripped) {
51588
+ const firstNode = entry.block.nodes.find((n) => n.loc);
51589
+ if (firstNode) firstNode.extra = {
51590
+ ...firstNode.extra ?? {},
51591
+ __rozieLeadingSeamPrevStripped: true
51592
+ };
51593
+ }
51594
+ } else {
51595
+ shiftHostNodeLines(entry.hostNode, entry.offset, shifted);
51596
+ if (entry.afterGap !== void 0) {
51597
+ const extra = entry.hostNode.extra ?? {};
51598
+ entry.hostNode.extra = {
51599
+ ...extra,
51600
+ __rozieAfterGap: entry.afterGap
51601
+ };
51602
+ }
51603
+ }
51604
+ }
51605
+ /**
51606
+ * Phase 56 (Shape-3, R4) — un-FLOAT a hoisted import's between-statement comment that
51607
+ * has separated from its owning declaration.
51608
+ *
51609
+ * `hoistSpecifier` copies a partial import's `trailingComments` onto the HOISTED import
51610
+ * node so a comment authored BETWEEN that import and the next surviving decl rides the
51611
+ * import to the host module-top (Phase 55 byte-identity). @babel/parser attaches such a
51612
+ * between-statement comment to BOTH neighbours: the import's `trailingComments` AND the
51613
+ * following decl's `leadingComments` (the SAME comment object). That copy is CORRECT
51614
+ * only when the import and its decl stay ADJACENT in the final body (e.g. HostC: the
51615
+ * hoisted `clamp` import is immediately followed by the `double` decl it shares the
51616
+ * comment with — the inline oracle ALSO has the comment on both neighbours, so
51617
+ * per-statement targets double it identically).
51618
+ *
51619
+ * When the spliced decl lands NON-adjacent to its hoisted import — a host statement
51620
+ * (e.g. a reassigned module-`let`) sits between them (HostH/the DataTable P15
51621
+ * `editTransition` after-`let` seam) — the import's copy FLOATS the comment to
51622
+ * module-top, away from the decl. The inline oracle keeps the comment ONLY on the decl
51623
+ * (its import is authored far above, never adjacent), so the float is a partial-vs-inline
51624
+ * divergence on ALL six targets (svelte/vue double it at the wrong place, react/angular/
51625
+ * lit drop it, solid dedups). This pass restores the inline placement by STRIPPING the
51626
+ * floated comment from the import's `trailingComments`; the decl keeps it on its
51627
+ * `leadingComments` (object identity preserved), and the per-target emitters then handle
51628
+ * the decl-attached comment exactly as they do for the inline oracle (svelte/vue's
51629
+ * `mirrorSpliceBoundaryComments` restores the doubling at the decl seam).
51630
+ *
51631
+ * Runs BEFORE `normalizeSplicedEmitLines` so the corrected attachment is in place when
51632
+ * the emit-line shift runs. A comment is treated as FLOATED iff it is shared (object
51633
+ * identity) with some body node's `leadingComments` whose owner is NOT the import's
51634
+ * immediate body-successor; a genuine import-only trailing comment (owned by no decl's
51635
+ * leadingComments) and the adjacent-decl case (HostC) are both left untouched. Never
51636
+ * throws (D-04).
51637
+ */
51638
+ function defloatHoistedImportComments(body, hoistImports) {
51639
+ if (hoistImports.length === 0) return;
51640
+ const hoistSet = new Set(hoistImports);
51641
+ const leadOwnerIndex = /* @__PURE__ */ new Map();
51642
+ for (let i = 0; i < body.length; i++) for (const c of body[i].leadingComments ?? []) if (!leadOwnerIndex.has(c)) leadOwnerIndex.set(c, i);
51643
+ for (let hi = 0; hi < body.length; hi++) {
51644
+ const node = body[hi];
51645
+ if (!hoistSet.has(node)) continue;
51646
+ const trailing = node.trailingComments;
51647
+ if (!trailing || trailing.length === 0) continue;
51648
+ const kept = trailing.filter((c) => {
51649
+ const owner = leadOwnerIndex.get(c);
51650
+ if (owner === void 0) return true;
51651
+ if (owner === hi + 1) return true;
51652
+ return false;
51653
+ });
51654
+ if (kept.length !== trailing.length) node.trailingComments = kept.length > 0 ? kept : null;
51655
+ }
51656
+ }
51141
51657
  /** Build the named imported-name list for a (host or nested) partial import. */
51142
51658
  function namedImports(imp) {
51143
51659
  const out = [];
@@ -51200,6 +51716,7 @@ function inlineResolvedPartial(absPath, importedNames, importStmt, importerFile,
51200
51716
  const nestedImports = [];
51201
51717
  /** local name -> nested partial resolved path + the imported name. */
51202
51718
  const nestedLocalMap = /* @__PURE__ */ new Map();
51719
+ const reExports = [];
51203
51720
  let order = 0;
51204
51721
  for (const stmt of partialBody) {
51205
51722
  if (t$3.isImportDeclaration(stmt)) {
@@ -51225,9 +51742,47 @@ function inlineResolvedPartial(absPath, importedNames, importStmt, importerFile,
51225
51742
  } else moduleImports.push(stmt);
51226
51743
  continue;
51227
51744
  }
51745
+ if (t$3.isExportNamedDeclaration(stmt) && !stmt.declaration && stmt.source) {
51746
+ const src = stmt.source;
51747
+ const declTypeOnly = stmt.exportKind === "type";
51748
+ for (const spec of stmt.specifiers) if (t$3.isExportSpecifier(spec)) {
51749
+ const exportedName = t$3.isIdentifier(spec.exported) ? spec.exported.name : spec.exported.value;
51750
+ const kind = declTypeOnly || spec.exportKind === "type" ? "type" : "value";
51751
+ const localId = spec.local;
51752
+ reExports.push({
51753
+ exportedName,
51754
+ source: src,
51755
+ kind,
51756
+ build: () => t$3.importSpecifier(t$3.identifier(exportedName), t$3.identifier(localId.name))
51757
+ });
51758
+ } else if (t$3.isExportNamespaceSpecifier(spec)) {
51759
+ const nsName = spec.exported.name;
51760
+ reExports.push({
51761
+ exportedName: nsName,
51762
+ source: src,
51763
+ kind: declTypeOnly ? "type" : "value",
51764
+ build: () => t$3.importNamespaceSpecifier(t$3.identifier(nsName))
51765
+ });
51766
+ }
51767
+ continue;
51768
+ }
51769
+ if (t$3.isExportAllDeclaration(stmt)) {
51770
+ ctx.diagnostics.push({
51771
+ code: RozieErrorCode.PARTIAL_UNSUPPORTED_IMPORT_FORM,
51772
+ severity: "error",
51773
+ 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}'\`).`,
51774
+ loc: nodeLoc(stmt),
51775
+ ...absPath ? { filename: absPath } : {},
51776
+ hint: `Replace the star re-export with explicit named re-exports: export { foo, bar } from '${stmt.source.value}'.`
51777
+ });
51778
+ continue;
51779
+ }
51228
51780
  let bare = null;
51229
- if (t$3.isExportNamedDeclaration(stmt) && stmt.declaration) bare = stmt.declaration;
51230
- else if (t$3.isVariableDeclaration(stmt) || t$3.isFunctionDeclaration(stmt) || t$3.isClassDeclaration(stmt) || t$3.isTSInterfaceDeclaration(stmt) || t$3.isTSTypeAliasDeclaration(stmt) || t$3.isTSEnumDeclaration(stmt) || t$3.isTSDeclareFunction(stmt)) bare = stmt;
51781
+ if (t$3.isExportNamedDeclaration(stmt) && stmt.declaration) {
51782
+ bare = stmt.declaration;
51783
+ const prevStmt = partialBody[partialBody.indexOf(stmt) - 1];
51784
+ if ((prevStmt && t$3.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;
51785
+ } else if (t$3.isVariableDeclaration(stmt) || t$3.isFunctionDeclaration(stmt) || t$3.isClassDeclaration(stmt) || t$3.isTSInterfaceDeclaration(stmt) || t$3.isTSTypeAliasDeclaration(stmt) || t$3.isTSEnumDeclaration(stmt) || t$3.isTSDeclareFunction(stmt)) bare = stmt;
51231
51786
  if (!bare) continue;
51232
51787
  const names = bindingNames(bare);
51233
51788
  if (names.length === 0) continue;
@@ -51280,8 +51835,9 @@ function inlineResolvedPartial(absPath, importedNames, importStmt, importerFile,
51280
51835
  }
51281
51836
  for (const imp of moduleImports) {
51282
51837
  const declKind = imp.importKind ?? "value";
51283
- for (const spec of imp.specifiers) if (referencedAll.has(spec.local.name)) hoistSpecifier(ctx, imp.source, declKind, spec);
51838
+ for (const spec of imp.specifiers) if (referencedAll.has(spec.local.name)) hoistSpecifier(ctx, imp.source, declKind, spec, imp);
51284
51839
  }
51840
+ for (const re of reExports) if (importedNames.includes(re.exportedName) || referencedAll.has(re.exportedName)) hoistSpecifier(ctx, re.source, re.kind, re.build());
51285
51841
  const out = [];
51286
51842
  for (const decl of includedSorted) {
51287
51843
  const collidingNames = [];
@@ -51371,6 +51927,7 @@ function inlineScriptPartials(file, opts = {}) {
51371
51927
  }
51372
51928
  }
51373
51929
  const splicedAbs = /* @__PURE__ */ new Set();
51930
+ const splicedBlocks = [];
51374
51931
  const newBody = [];
51375
51932
  for (const stmt of file.program.body) if (t$3.isImportDeclaration(stmt) && PARTIAL_EXT.test(stmt.source.value)) {
51376
51933
  const absPath = hostPartialAbs.get(stmt) ?? null;
@@ -51399,9 +51956,38 @@ function inlineScriptPartials(file, opts = {}) {
51399
51956
  if (splicedAbs.has(absPath)) continue;
51400
51957
  splicedAbs.add(absPath);
51401
51958
  const unionNames = hostPartialNames.get(absPath) ?? namedImports(stmt);
51402
- newBody.push(...inlineResolvedPartial(absPath, unionNames, stmt, fromFile, ctx));
51959
+ const hoistBefore = ctx.hoistImports.length;
51960
+ const spliced = inlineResolvedPartial(absPath, unionNames, stmt, fromFile, ctx);
51961
+ const newHoists = ctx.hoistImports.slice(hoistBefore);
51962
+ if (stmt.loc) {
51963
+ const anchorLine = stmt.loc.start.line;
51964
+ for (const hoist of newHoists) splicedBlocks.push({
51965
+ nodes: [hoist],
51966
+ anchorLine,
51967
+ originalGap: 1
51968
+ });
51969
+ let runStart = 0;
51970
+ let prevRunLastNode = null;
51971
+ while (runStart < spliced.length) {
51972
+ const fname = spliced[runStart].loc?.filename ?? null;
51973
+ let runEnd = runStart + 1;
51974
+ while (runEnd < spliced.length && (spliced[runEnd].loc?.filename ?? null) === fname) runEnd++;
51975
+ const nodes = spliced.slice(runStart, runEnd);
51976
+ const originalGap = measureOriginalGap(nodes.find((n) => n.loc) ?? nodes[0], newHoists, prevRunLastNode);
51977
+ splicedBlocks.push({
51978
+ nodes,
51979
+ anchorLine,
51980
+ originalGap
51981
+ });
51982
+ prevRunLastNode = nodes[nodes.length - 1] ?? prevRunLastNode;
51983
+ runStart = runEnd;
51984
+ }
51985
+ }
51986
+ newBody.push(...spliced);
51403
51987
  } else newBody.push(stmt);
51404
51988
  file.program.body = [...ctx.hoistImports, ...newBody];
51989
+ defloatHoistedImportComments(file.program.body, ctx.hoistImports);
51990
+ normalizeSplicedEmitLines(file.program.body, splicedBlocks);
51405
51991
  return {
51406
51992
  ast: file,
51407
51993
  diagnostics
@@ -53709,6 +54295,27 @@ function rewriteRozieIdentifiers$4(program, ir, diagnostics) {
53709
54295
  loc: ref.sourceLoc
53710
54296
  });
53711
54297
  normalizeModelAccessor$4(program);
54298
+ const vueProtected = new Set((ir.expose ?? []).map((e) => e.name));
54299
+ deconflictGeneratedSymbols(program, [
54300
+ {
54301
+ names: modelProps,
54302
+ trigger: {
54303
+ kind: "accessor",
54304
+ accessor: "$props"
54305
+ }
54306
+ },
54307
+ {
54308
+ names: dataNames,
54309
+ trigger: {
54310
+ kind: "accessor",
54311
+ accessor: "$data"
54312
+ }
54313
+ },
54314
+ {
54315
+ names: computedNames,
54316
+ trigger: { kind: "bare-read" }
54317
+ }
54318
+ ], vueProtected);
53712
54319
  traverse$17(program, {
53713
54320
  MemberExpression(path) {
53714
54321
  /* v8 ignore next -- defensive: MemberExpression nodes do not occur in TS type position */
@@ -54229,6 +54836,80 @@ function arrowBody$4(body) {
54229
54836
  return genCode$9(t$3.arrowFunctionExpression([], body));
54230
54837
  }
54231
54838
  /**
54839
+ * Phase 55-04 (literal byte-identity) — reproduce the inline-authored comment
54840
+ * doubling at a script-partial splice boundary.
54841
+ *
54842
+ * In an inline-authored `<script>`, a comment block BETWEEN two statements is
54843
+ * attached by `@babel/parser` to BOTH neighbours (the earlier statement's
54844
+ * `trailingComments` AND the later statement's `leadingComments`). The `.rzts`
54845
+ * script-partial splice attaches the boundary banner ONLY to the spliced node's
54846
+ * `leadingComments` — the preceding statement lives in a different source file and
54847
+ * carries no matching trailing comment. Vue emits the residual body one statement
54848
+ * at a time (`stmts.map((s) => genCode(s)).join('\n')`), so each `genCode` call has
54849
+ * its own comment-dedup set: in the inline form the boundary banner therefore
54850
+ * prints TWICE (once as the previous statement's trailing, once as the next
54851
+ * statement's leading), with a blank line after the previous closing brace.
54852
+ * Re-mirroring the spliced node's leading comments back onto the preceding
54853
+ * statement's trailing comments restores that byte-for-byte.
54854
+ *
54855
+ * Fires at a genuine splice boundary in EITHER direction (Phase 56 R1 broadened
54856
+ * the trigger): the CURRENT statement is spliced (`cur.extra.__roziePartialOrigin`
54857
+ * — the Phase 55 leading seam) OR the PREVIOUS statement is spliced and CUR is an
54858
+ * inline host successor carrying the leading comment (the R1 TRAILING seam). In
54859
+ * both cases CUR's leading comments are mirrored onto PREV's trailing comments
54860
+ * UNLESS already shared (within-partial statement pairs share the same comment
54861
+ * objects; host-only pairs — neither node spliced — are left exactly as authored).
54862
+ * `normalizeSplicedEmitLines` (core) has already anchored the seam spacing, so the
54863
+ * mirrored trailing copy spaces correctly.
54864
+ */
54865
+ function mirrorSpliceBoundaryComments$2(stmts) {
54866
+ for (let i = 1; i < stmts.length; i++) {
54867
+ const cur = stmts[i];
54868
+ const prev = stmts[i - 1];
54869
+ const curExtra = cur.extra;
54870
+ const prevExtra = prev.extra;
54871
+ const curSpliced = curExtra?.__roziePartialOrigin !== void 0;
54872
+ const prevSpliced = prevExtra?.__roziePartialOrigin !== void 0;
54873
+ if (!curSpliced && !prevSpliced) continue;
54874
+ const lead = cur.leadingComments;
54875
+ const prevTrail = prev.trailingComments;
54876
+ if (curSpliced && !prevSpliced && (!lead || lead.length === 0) && prevTrail && prevTrail.length > 0) {
54877
+ cur.leadingComments = [...prevTrail];
54878
+ continue;
54879
+ }
54880
+ if (!lead || lead.length === 0) continue;
54881
+ if (curSpliced && curExtra?.__rozieLeadingSeamPrevStripped === true) continue;
54882
+ const lastLead = lead[lead.length - 1];
54883
+ if (prevTrail && prevTrail.length > 0 && prevTrail[prevTrail.length - 1] === lastLead) continue;
54884
+ let toAppend = lead;
54885
+ if (prevSpliced && !curSpliced) {
54886
+ const afterGap = curExtra?.__rozieAfterGap;
54887
+ const anchorLine = (prev.loc?.end.line ?? 0) + (typeof afterGap === "number" ? afterGap : 1);
54888
+ const baseLine = lead[0]?.loc?.start.line;
54889
+ toAppend = lead.map((c) => {
54890
+ if (!c.loc) return { ...c };
54891
+ const startLine = baseLine === void 0 ? anchorLine : anchorLine + (c.loc.start.line - baseLine);
54892
+ const endLine = baseLine === void 0 ? anchorLine : anchorLine + (c.loc.end.line - baseLine);
54893
+ return {
54894
+ ...c,
54895
+ loc: {
54896
+ ...c.loc,
54897
+ start: {
54898
+ ...c.loc.start,
54899
+ line: startLine
54900
+ },
54901
+ end: {
54902
+ ...c.loc.end,
54903
+ line: endLine
54904
+ }
54905
+ }
54906
+ };
54907
+ });
54908
+ }
54909
+ prev.trailingComments = [...prevTrail ?? [], ...toAppend];
54910
+ }
54911
+ }
54912
+ /**
54232
54913
  * Render a PropTypeAnnotation as a TypeScript type string.
54233
54914
  *
54234
54915
  * Reference examples produce these patterns:
@@ -54683,6 +55364,7 @@ function emitResidualScriptBody$1(clonedProgram, consumedLifecycleIndices) {
54683
55364
  }
54684
55365
  stmts.push(stmt);
54685
55366
  }
55367
+ mirrorSpliceBoundaryComments$2(stmts);
54686
55368
  return {
54687
55369
  code: stmts.map((s) => genCode$9(s)).join("\n"),
54688
55370
  stmts
@@ -58259,7 +58941,8 @@ function remapping$1(input, loader, options) {
58259
58941
  * (the "parent" map) via @ampproject/remapping. Result: a single Source Map
58260
58942
  * v3 that resolves emitted-output positions all the way back to .rozie.
58261
58943
  *
58262
- * Used by all 4 target compose.ts files. Replaces the per-target
58944
+ * Used by all 6 target compose.ts files (react/vue/svelte/solid/lit/angular).
58945
+ * Replaces the per-target
58263
58946
  * single-segment re-projection hack (Phase 3 WR-01 / Phase 4 Plan 04-05
58264
58947
  * Task 1) removed in P2 (D-109).
58265
58948
  *
@@ -58272,7 +58955,114 @@ function remapping$1(input, loader, options) {
58272
58955
  * @experimental — shape may change before v1.0
58273
58956
  */
58274
58957
  const remapping = typeof remapping$1 === "function" ? remapping$1 : remapping$1.default;
58958
+ /** Source-file extension test: a spliced script partial origin (Phase 54/55). */
58959
+ function isPartialSource(src) {
58960
+ return !!src && (src.endsWith(".rzts") || src.endsWith(".rzjs"));
58961
+ }
58962
+ /**
58963
+ * Recover a partial source's constant emit-line offset from the table. The table
58964
+ * is keyed by the absolute `loc.filename` stashed at splice time; a child map's
58965
+ * `sources` entry is the same absolute path, so an EXACT lookup is tried first.
58966
+ *
58967
+ * WR-03: the defensive fallback is restricted to a path-SEGMENT-BOUNDARY match
58968
+ * (`src.endsWith('/' + file) || file.endsWith('/' + src)`) so a partial whose path
58969
+ * is a bare substring of another's (e.g. `xa.rzts` vs `a.rzts`) never collides, and
58970
+ * — critically — if TWO OR MORE table entries match the boundary test (an ambiguous
58971
+ * bare basename shared across nested dirs) the lookup BAILS (returns `undefined`)
58972
+ * rather than mis-attributing one partial's offset to another. Returns `undefined`
58973
+ * when no unambiguous offset is known (mapping left as-is — D-04).
58974
+ */
58975
+ function lookupPartialOffset(src, offsets) {
58976
+ if (!src || !offsets || offsets.size === 0) return void 0;
58977
+ const exact = offsets.get(src);
58978
+ if (exact !== void 0) return exact;
58979
+ let match;
58980
+ let matchCount = 0;
58981
+ for (const [file, off] of offsets) if (src.endsWith(`/${file}`) || file.endsWith(`/${src}`)) {
58982
+ match = off;
58983
+ matchCount++;
58984
+ }
58985
+ return matchCount === 1 ? match : void 0;
58986
+ }
58987
+ /**
58988
+ * Phase 55 Plan 03 (SC-2) — build the per-partial-file constant emit-line offset
58989
+ * table from the lowered script AST.
58990
+ *
58991
+ * Plan 02 shifted each spliced node's `loc.start.line` to a host-contiguous emit
58992
+ * value while stashing the true `.rzts` origin on `extra.__roziePartialOrigin`.
58993
+ * The per-partial offset is therefore the (constant) delta
58994
+ * `loc.start.line − __roziePartialOrigin.line`; subtracting it from a mapping's
58995
+ * original line restores the `.rzts`-local line. The offset is constant per
58996
+ * spliced block (Plan 02 anchored it), so one entry per partial `source` file
58997
+ * suffices — the first stashed node per file wins.
58998
+ *
58999
+ * Walks `scriptAst.program.body` (the spliced statements sit at top level) and
59000
+ * their attached leading/trailing/inner comments (comments carry the stash
59001
+ * directly). Never throws (D-04): a missing/zero stash is skipped, a null AST
59002
+ * yields an empty table.
59003
+ *
59004
+ * IN-02: the `innerComments` loop mirrors `shiftAttachedComments`
59005
+ * (inlineScriptPartials.ts) which walks leading/trailing/inner. In practice the
59006
+ * offset is per-file and first-stash-wins, so a leading/trailing comment or the
59007
+ * decl itself almost always supplies it — the inner loop is additive and harmless,
59008
+ * kept only so the stash-channel scan is symmetric with where stashes are written.
59009
+ */
59010
+ function buildPartialLineOffsets(scriptAst) {
59011
+ const out = /* @__PURE__ */ new Map();
59012
+ const body = scriptAst?.program?.body;
59013
+ if (!body) return out;
59014
+ const record = (carrier, locLine) => {
59015
+ const origin = carrier?.__roziePartialOrigin;
59016
+ if (!origin || locLine === void 0) return;
59017
+ const key = origin.filename;
59018
+ if (!key || out.has(key)) return;
59019
+ out.set(key, locLine - origin.line);
59020
+ };
59021
+ for (const node of body) {
59022
+ const extra = node.extra;
59023
+ record(extra, node.loc?.start.line);
59024
+ for (const c of node.leadingComments ?? []) record(c, c.loc?.start.line);
59025
+ for (const c of node.trailingComments ?? []) record(c, c.loc?.start.line);
59026
+ for (const c of node.innerComments ?? []) record(c, c.loc?.start.line);
59027
+ }
59028
+ return out;
59029
+ }
58275
59030
  /**
59031
+ * Phase 55 Plan 03 — per-target script-map flow survey (Task 1, Assumption A3).
59032
+ *
59033
+ * SURVEY RESULT (confirmed on disk 2026-06-20):
59034
+ *
59035
+ * 1. CONVERGENCE — all SIX per-target `sourcemap/compose.ts` wrappers
59036
+ * (react/vue/svelte/solid/lit/angular) import THIS `composeMaps` and route
59037
+ * their @babel/generator <script> child map through it as `children[0]`. No
59038
+ * target hand-rolls a second map merge; `composeMaps` is the single
59039
+ * convergence point, so the spliced-line restore arithmetic lives here ONCE
59040
+ * (D-03/D-05 uniformity) — never in a per-target wrapper or `emitScript.ts`.
59041
+ *
59042
+ * 2. ACTIVE MAP PATH — the five map-EMITTING targets (react/vue/svelte/solid/
59043
+ * angular) all pass a defined `userCodeLineOffset`, so each takes the
59044
+ * `userCodeLineOffset` branch below (step 3). Empirically, that branch
59045
+ * discarded the child map's per-node `sources` (it hardcoded `[opts.filename]`),
59046
+ * collapsing a spliced node's `.rzts` origin to the host `.rozie` — which is
59047
+ * exactly why the SC-2 line-fidelity smoke test was red/skipped. The restore
59048
+ * therefore lives in step 3. The step-4 remapping path is NOT exercised by
59049
+ * partials (every map-emitting target passes `userCodeLineOffset`), so it is
59050
+ * deliberately left untouched rather than carrying speculative dead code.
59051
+ *
59052
+ * 3. TABLE BUILD SITE — each of those five `emitXxx.ts` holds the lowered `ir`
59053
+ * (whose `ir.setupBody.scriptProgram` script AST carries the spliced nodes'
59054
+ * `extra.__roziePartialOrigin` stashes from Plan 02). Each builds the
59055
+ * `partialLineOffsets` table via {@link buildPartialLineOffsets} and threads
59056
+ * it into the `ComposeOpts` it already constructs. The table derives from the
59057
+ * IR, NOT from generator output — so NO `emitScript.ts` (the @babel/generator
59058
+ * caller) is touched (D-03).
59059
+ *
59060
+ * 4. LIT EXCEPTION — `packages/targets/lit/src/emitLit.ts` returns `map: null`
59061
+ * in v1 and does NOT call `composeSourceMap` (the wrapper is implemented but
59062
+ * dead until Phase 7 wires it). Lit therefore has no build+set site; its
59063
+ * wrapper still receives the `partialLineOffsets` field for forward-compat so
59064
+ * the restore flows automatically once Lit's map path is connected.
59065
+ *
58276
59066
  * Merge the shell map + zero-or-more child maps into a single Source Map v3
58277
59067
  * anchored to .rozie. Defensively re-asserts sources/sourcesContent per
58278
59068
  * Pitfall 2 mitigation.
@@ -58291,6 +59081,27 @@ function composeMaps(opts) {
58291
59081
  }
58292
59082
  if (opts.userCodeLineOffset !== void 0) {
58293
59083
  const childMap = opts.children[0].map;
59084
+ const childSources = childMap.sources ?? [];
59085
+ if (childSources.some((s) => isPartialSource(s)) && !!opts.partialLineOffsets && opts.partialLineOffsets.size > 0) {
59086
+ const decoded = decode(childMap.mappings);
59087
+ for (const line of decoded) for (const seg of line) if (seg.length >= 4) {
59088
+ const full = seg;
59089
+ const src = childSources[full[1]];
59090
+ const off = lookupPartialOffset(src, opts.partialLineOffsets);
59091
+ if (off !== void 0) full[2] = Math.max(0, full[2] - off);
59092
+ }
59093
+ const restoredMappings = encode(decoded);
59094
+ const sources = childSources.slice();
59095
+ const sourcesContent = sources.map((s, i) => s === opts.filename ? opts.source : childMap.sourcesContent?.[i] ?? null);
59096
+ return {
59097
+ version: 3,
59098
+ file: `${opts.filename}${opts.fileExt}`,
59099
+ sources,
59100
+ sourcesContent,
59101
+ names: childMap.names ?? [],
59102
+ mappings: ";".repeat(opts.userCodeLineOffset) + restoredMappings
59103
+ };
59104
+ }
58294
59105
  return {
58295
59106
  version: 3,
58296
59107
  file: `${opts.filename}${opts.fileExt}`,
@@ -58324,7 +59135,8 @@ function composeSourceMap$4(ms, opts) {
58324
59135
  outputOffset: opts.scriptOutputOffset
58325
59136
  }] : [],
58326
59137
  fileExt: ".vue",
58327
- userCodeLineOffset: opts.userCodeLineOffset
59138
+ userCodeLineOffset: opts.userCodeLineOffset,
59139
+ partialLineOffsets: opts.partialLineOffsets
58328
59140
  });
58329
59141
  }
58330
59142
  //#endregion
@@ -58509,7 +59321,8 @@ function emitVue(ir, opts = {}) {
58509
59321
  source: opts.source,
58510
59322
  scriptMap: shellScriptMap,
58511
59323
  scriptOutputOffset,
58512
- userCodeLineOffset
59324
+ userCodeLineOffset,
59325
+ partialLineOffsets: buildPartialLineOffsets(ir.setupBody.scriptProgram)
58513
59326
  }) : null,
58514
59327
  diagnostics: [
58515
59328
  ...scriptDiags,
@@ -62450,7 +63263,8 @@ function composeClassName(attrs, ctx) {
62450
63263
  });
62451
63264
  else segments.push({
62452
63265
  kind: "plainBinding",
62453
- expr: a.expression
63266
+ expr: a.expression,
63267
+ wrapForDisplay: a.wrapForDisplay
62454
63268
  });
62455
63269
  else if (a.kind === "spreadBinding") throw new Error(`React target: spreadBinding not valid in class array context (Phase 14).`);
62456
63270
  else segments.push({
@@ -62472,6 +63286,10 @@ function composeClassName(attrs, ctx) {
62472
63286
  const seg = segments[0];
62473
63287
  const staticSegs = decomposeStaticClassExpr(seg.expr);
62474
63288
  if (staticSegs) return renderInterpolatedClass(staticSegs, ctx);
63289
+ if (seg.wrapForDisplay) {
63290
+ ctx.collectors.runtime.add("clsx");
63291
+ return `clsx(${renderExpr$1(seg.expr, ir)})`;
63292
+ }
62475
63293
  return renderExpr$1(seg.expr, ir);
62476
63294
  }
62477
63295
  if (segments.length === 1 && segments[0].kind === "interpolated") {
@@ -68306,7 +69124,8 @@ function composeSourceMap$3(ms, opts) {
68306
69124
  outputOffset: opts.scriptOutputOffset
68307
69125
  }] : [],
68308
69126
  fileExt: ".tsx",
68309
- userCodeLineOffset: opts.userCodeLineOffset
69127
+ userCodeLineOffset: opts.userCodeLineOffset,
69128
+ partialLineOffsets: opts.partialLineOffsets
68310
69129
  });
68311
69130
  }
68312
69131
  //#endregion
@@ -68409,7 +69228,8 @@ function emitReact(ir, opts = {}) {
68409
69228
  source: opts.source,
68410
69229
  scriptMap: shellScriptMap,
68411
69230
  scriptOutputOffset,
68412
- userCodeLineOffset
69231
+ userCodeLineOffset,
69232
+ partialLineOffsets: buildPartialLineOffsets(ir.setupBody.scriptProgram)
68413
69233
  }) : null,
68414
69234
  diagnostics: [
68415
69235
  ...scriptDiags,
@@ -69403,6 +70223,80 @@ function arrowBody$2(body) {
69403
70223
  return genCode$5(t$3.arrowFunctionExpression([], body));
69404
70224
  }
69405
70225
  /**
70226
+ * Phase 55-04 (literal byte-identity) — reproduce the inline-authored comment
70227
+ * doubling at a script-partial splice boundary.
70228
+ *
70229
+ * In an inline-authored `<script>`, a comment block BETWEEN two statements is
70230
+ * attached by `@babel/parser` to BOTH neighbours (the earlier statement's
70231
+ * `trailingComments` AND the later statement's `leadingComments`). The `.rzts`
70232
+ * script-partial splice attaches the boundary banner ONLY to the spliced node's
70233
+ * `leadingComments` — the preceding statement lives in a different source file and
70234
+ * carries no matching trailing comment. Svelte emits the residual body one
70235
+ * statement at a time (`stmts.map((s) => genCode(s)).join('\n')`), so each
70236
+ * `genCode` call has its own comment-dedup set: in the inline form the boundary
70237
+ * banner therefore prints TWICE (once as the previous statement's trailing, once
70238
+ * as the next statement's leading), with a blank line after the previous closing
70239
+ * brace. Re-mirroring the spliced node's leading comments back onto the preceding
70240
+ * statement's trailing comments restores that byte-for-byte.
70241
+ *
70242
+ * Fires at a genuine splice boundary in EITHER direction (Phase 56 R1 broadened
70243
+ * the trigger): the CURRENT statement is spliced (`cur.extra.__roziePartialOrigin`
70244
+ * — the Phase 55 leading seam) OR the PREVIOUS statement is spliced and CUR is an
70245
+ * inline host successor carrying the leading comment (the R1 TRAILING seam). In
70246
+ * both cases CUR's leading comments are mirrored onto PREV's trailing comments
70247
+ * UNLESS already shared (within-partial statement pairs share the same comment
70248
+ * objects; host-only pairs — neither node spliced — are left exactly as authored).
70249
+ * `normalizeSplicedEmitLines` (core) has already anchored the seam spacing, so the
70250
+ * mirrored trailing copy spaces correctly.
70251
+ */
70252
+ function mirrorSpliceBoundaryComments$1(stmts) {
70253
+ for (let i = 1; i < stmts.length; i++) {
70254
+ const cur = stmts[i];
70255
+ const prev = stmts[i - 1];
70256
+ const curExtra = cur.extra;
70257
+ const prevExtra = prev.extra;
70258
+ const curSpliced = curExtra?.__roziePartialOrigin !== void 0;
70259
+ const prevSpliced = prevExtra?.__roziePartialOrigin !== void 0;
70260
+ if (!curSpliced && !prevSpliced) continue;
70261
+ const lead = cur.leadingComments;
70262
+ const prevTrail = prev.trailingComments;
70263
+ if (curSpliced && !prevSpliced && (!lead || lead.length === 0) && prevTrail && prevTrail.length > 0) {
70264
+ cur.leadingComments = [...prevTrail];
70265
+ continue;
70266
+ }
70267
+ if (!lead || lead.length === 0) continue;
70268
+ if (curSpliced && curExtra?.__rozieLeadingSeamPrevStripped === true) continue;
70269
+ const lastLead = lead[lead.length - 1];
70270
+ if (prevTrail && prevTrail.length > 0 && prevTrail[prevTrail.length - 1] === lastLead) continue;
70271
+ let toAppend = lead;
70272
+ if (prevSpliced && !curSpliced) {
70273
+ const afterGap = curExtra?.__rozieAfterGap;
70274
+ const anchorLine = (prev.loc?.end.line ?? 0) + (typeof afterGap === "number" ? afterGap : 1);
70275
+ const baseLine = lead[0]?.loc?.start.line;
70276
+ toAppend = lead.map((c) => {
70277
+ if (!c.loc) return { ...c };
70278
+ const startLine = baseLine === void 0 ? anchorLine : anchorLine + (c.loc.start.line - baseLine);
70279
+ const endLine = baseLine === void 0 ? anchorLine : anchorLine + (c.loc.end.line - baseLine);
70280
+ return {
70281
+ ...c,
70282
+ loc: {
70283
+ ...c.loc,
70284
+ start: {
70285
+ ...c.loc.start,
70286
+ line: startLine
70287
+ },
70288
+ end: {
70289
+ ...c.loc.end,
70290
+ line: endLine
70291
+ }
70292
+ }
70293
+ };
70294
+ });
70295
+ }
70296
+ prev.trailingComments = [...prevTrail ?? [], ...toAppend];
70297
+ }
70298
+ }
70299
+ /**
69406
70300
  * Render a PropTypeAnnotation as a TypeScript type string. Mirrors the Vue
69407
70301
  * target's renderType helper.
69408
70302
  */
@@ -69841,6 +70735,7 @@ function emitResidualScriptBody(clonedProgram, consumedLifecycleIndices, exposeN
69841
70735
  }
69842
70736
  stmts.push(stmt);
69843
70737
  }
70738
+ mirrorSpliceBoundaryComments$1(stmts);
69844
70739
  return {
69845
70740
  code: stmts.map((s) => {
69846
70741
  if (exposeNames.size > 0 && isExposedTopLevelDecl(s, exposeNames)) {
@@ -70504,6 +71399,14 @@ function emitSingleAttr$1(attr, ctx) {
70504
71399
  if (styleObjectLowered !== null) return styleObjectLowered;
70505
71400
  const expr = rewriteTemplateExpression$3(attr.expression, ir);
70506
71401
  const outName = resolveAttrName(attr.name, ctx);
71402
+ if (attr.name === "style") {
71403
+ ctx.runtimeImports?.add("rozieStyle");
71404
+ return `${outName}={rozieStyle(${expr})}`;
71405
+ }
71406
+ if (attr.name === "class" && attr.wrapForDisplay && !t$3.isObjectExpression(attr.expression) && !t$3.isTemplateLiteral(attr.expression) && shouldWrapSvelteAttrBinding(attr.name, attr.expression, ctx)) {
71407
+ ctx.runtimeImports?.add("rozieClass");
71408
+ return `${outName}={rozieClass(${expr})}`;
71409
+ }
70507
71410
  if (attr.wrapForDisplay && shouldWrapSvelteAttrBinding(attr.name, attr.expression, ctx)) {
70508
71411
  ctx.runtimeImports?.add("rozieAttr");
70509
71412
  return `${outName}={rozieAttr(${expr})}`;
@@ -70531,12 +71434,16 @@ function emitSingleAttr$1(attr, ctx) {
70531
71434
  * Convert a single AttributeBinding into a JS expression string suitable for
70532
71435
  * inclusion in an array (used by class/style merge below).
70533
71436
  */
70534
- function attrToArraySegment$1(attr, ir, runtimeImports) {
71437
+ function attrToArraySegment$1(attr, ir, runtimeImports, isClass = false) {
70535
71438
  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).`);
70536
71439
  if (attr.kind === "static") return JSON.stringify(attr.value);
70537
71440
  if (attr.kind === "binding") {
70538
71441
  const code = rewriteTemplateExpression$3(attr.expression, ir);
70539
71442
  if (attr.wrapForDisplay && !t$3.isObjectExpression(attr.expression)) {
71443
+ if (isClass && !t$3.isTemplateLiteral(attr.expression)) {
71444
+ runtimeImports?.add("rozieClass");
71445
+ return `rozieClass(${code})`;
71446
+ }
70540
71447
  runtimeImports?.add("rozieDisplay");
70541
71448
  return `rozieDisplay(${code})`;
70542
71449
  }
@@ -70627,7 +71534,7 @@ function emitAttributes$2(attrs, ctx) {
70627
71534
  if (synthetic) merged.push(synthetic);
70628
71535
  } else if (src.name === a.name && !isLiteralStyleObjectBinding(src)) merged.push(src);
70629
71536
  for (const x of merged) consumed.add(x);
70630
- const segments = merged.map((x) => attrToArraySegment$1(x, ctx.ir, ctx.runtimeImports));
71537
+ const segments = merged.map((x) => attrToArraySegment$1(x, ctx.ir, ctx.runtimeImports, a.name === "class"));
70631
71538
  if (hasOpaqueMerge) for (const readExpr of opaqueSpreadClassReads) segments.push(readExpr);
70632
71539
  if (hasOpaqueMerge) deferredMergedClassStyle.push(`${a.name}={[${segments.join(", ")}]}`);
70633
71540
  else out.push(`${a.name}={[${segments.join(", ")}]}`);
@@ -72119,7 +73026,8 @@ function composeSourceMap$2(ms, opts) {
72119
73026
  outputOffset: opts.scriptOutputOffset
72120
73027
  }] : [],
72121
73028
  fileExt: ".svelte",
72122
- userCodeLineOffset: opts.userCodeLineOffset
73029
+ userCodeLineOffset: opts.userCodeLineOffset,
73030
+ partialLineOffsets: opts.partialLineOffsets
72123
73031
  });
72124
73032
  }
72125
73033
  //#endregion
@@ -72223,7 +73131,8 @@ function emitSvelte(ir, opts = {}) {
72223
73131
  source: opts.source,
72224
73132
  scriptMap: shellScriptMap,
72225
73133
  scriptOutputOffset,
72226
- userCodeLineOffset
73134
+ userCodeLineOffset,
73135
+ partialLineOffsets: buildPartialLineOffsets(ir.setupBody.scriptProgram)
72227
73136
  }) : null,
72228
73137
  diagnostics: [
72229
73138
  ...scriptDiags,
@@ -73494,7 +74403,15 @@ function buildSlotCtx(slot) {
73494
74403
  */
73495
74404
  function buildNgTemplateContextGuard(componentName, slots) {
73496
74405
  if (slots.length === 0) return null;
73497
- const unionType = slots.map((s) => slotCtxName(s.name)).join(" | ");
74406
+ const seenCtxNames = /* @__PURE__ */ new Set();
74407
+ const ctxNames = [];
74408
+ for (const s of slots) {
74409
+ const ctxName = slotCtxName(s.name);
74410
+ if (seenCtxNames.has(ctxName)) continue;
74411
+ seenCtxNames.add(ctxName);
74412
+ ctxNames.push(ctxName);
74413
+ }
74414
+ const unionType = ctxNames.join(" | ");
73498
74415
  return [
73499
74416
  `static ngTemplateContextGuard(`,
73500
74417
  ` _dir: ${componentName},`,
@@ -73543,7 +74460,7 @@ function buildSlotMethod$2(slot, scopeHash) {
73543
74460
  const slotName = portalKey(slot);
73544
74461
  const tplField = angularTplField(slot);
73545
74462
  const paramNames = slot.portalParamNames ?? [];
73546
- 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 },";
74463
+ 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 },";
73547
74464
  }
73548
74465
  /**
73549
74466
  * Phase 33 / REQ-21 — reactive portal-slot method body.
@@ -73560,7 +74477,7 @@ function buildReactiveSlotMethod$2(slot, scopeHash) {
73560
74477
  const slotName = portalKey(slot);
73561
74478
  const tplField = angularTplField(slot);
73562
74479
  const paramNames = slot.portalParamNames ?? [];
73563
- 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 },";
74480
+ 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 },";
73564
74481
  }
73565
74482
  function emitPortals$2(ir, scopeHash = "") {
73566
74483
  const portals = ir.slots.filter((s) => s.isPortal === true);
@@ -74023,7 +74940,7 @@ function buildCvaClassShape(prop) {
74023
74940
  return [
74024
74941
  `private __rozieCvaOnChange: (v: ${tsType}) => void = () => {};`,
74025
74942
  `private __rozieCvaOnTouchedFn: () => void = () => {};`,
74026
- `private __rozieCvaDisabled = signal(false);`,
74943
+ `protected __rozieCvaDisabled = signal(false);`,
74027
74944
  ``,
74028
74945
  `writeValue(v: ${tsType} | null): void {`,
74029
74946
  writeValueBody,
@@ -74286,7 +75203,10 @@ function emitScript$2(ir, opts = {}) {
74286
75203
  const interfaceDecls = [];
74287
75204
  for (const typeDecl of hoistedTypeDecls) interfaceDecls.push(genCode$3(typeDecl));
74288
75205
  const slotFieldDecls = [];
75206
+ const seenSlotNames = /* @__PURE__ */ new Set();
74289
75207
  for (const slot of ir.slots) {
75208
+ if (seenSlotNames.has(slot.name)) continue;
75209
+ seenSlotNames.add(slot.name);
74290
75210
  const ctx = buildSlotCtx(slot);
74291
75211
  interfaceDecls.push(ctx.interfaceDecl);
74292
75212
  slotFieldDecls.push(ctx.fieldDecl);
@@ -75249,6 +76169,7 @@ function shouldWrapAttrBinding$1(name, expr, ctx, elementTagName) {
75249
76169
  if (BOOLEAN_HTML_ATTRS$1.has(name.toLowerCase())) return false;
75250
76170
  if ((name === "value" || name === "checked") && FORM_INPUT_TAGS$2.has(elementTagName.toLowerCase())) return false;
75251
76171
  if (name === "style") return false;
76172
+ if (name === "class") return false;
75252
76173
  if (t$3.isObjectExpression(expr)) return false;
75253
76174
  return true;
75254
76175
  }
@@ -78017,7 +78938,8 @@ function composeSourceMap$1(ms, opts) {
78017
78938
  outputOffset: opts.scriptOutputOffset
78018
78939
  }] : [],
78019
78940
  fileExt: ".ts",
78020
- userCodeLineOffset: opts.userCodeLineOffset
78941
+ userCodeLineOffset: opts.userCodeLineOffset,
78942
+ partialLineOffsets: opts.partialLineOffsets
78021
78943
  });
78022
78944
  }
78023
78945
  //#endregion
@@ -78403,7 +79325,8 @@ function emitAngular(ir, opts = {}) {
78403
79325
  source: opts.source,
78404
79326
  scriptMap: shellScriptMap,
78405
79327
  scriptOutputOffset,
78406
- userCodeLineOffset
79328
+ userCodeLineOffset,
79329
+ partialLineOffsets: buildPartialLineOffsets(ir.setupBody.scriptProgram)
78407
79330
  }) : null,
78408
79331
  diagnostics: [
78409
79332
  ...scriptResult.diagnostics,
@@ -79347,6 +80270,56 @@ function tryHoistArrowToFunction(stmt) {
79347
80270
  return t$3.inherits(fn, stmt);
79348
80271
  }
79349
80272
  /**
80273
+ * Phase 55-04 (literal byte-identity) — reproduce the inline-authored comment
80274
+ * doubling at a script-partial splice boundary.
80275
+ *
80276
+ * In an inline-authored `<script>`, a comment block BETWEEN two statements is
80277
+ * attached by `@babel/parser` to BOTH neighbours (the earlier statement's
80278
+ * `trailingComments` AND the later statement's `leadingComments`). The `.rzts`
80279
+ * script-partial splice instead attaches the boundary banner ONLY to the spliced
80280
+ * node's `leadingComments` — the preceding statement lives in a different source
80281
+ * file and so carries no matching trailing comment. Re-mirroring the spliced
80282
+ * node's leading comments back onto the preceding statement's trailing comments
80283
+ * restores the inline form byte-for-byte:
80284
+ * - whole-program generation (Solid here) prints the (deduped) banner once AND
80285
+ * gets the boundary blank line from `@babel/generator`'s `printJoin`
80286
+ * `_lastCommentLine` path — a trailing comment on the previous statement is
80287
+ * exactly what triggers the loc-delta blank-line insertion; and
80288
+ * - per-statement generation (Vue/Svelte) doubles the banner (one copy as the
80289
+ * previous statement's trailing, one as the next statement's leading).
80290
+ *
80291
+ * Fires ONLY at a genuine splice boundary: the current statement carries
80292
+ * `extra.__roziePartialOrigin` AND its leading comments are not ALREADY shared as
80293
+ * the previous statement's trailing comments (within-partial statement pairs
80294
+ * already share the same comment objects; host-only pairs are left exactly as
80295
+ * authored). MUST run before the arrow→function hoist — `t.inherits` does not
80296
+ * copy `extra`, so the spliced marker is only visible on the original nodes.
80297
+ */
80298
+ function mirrorSpliceBoundaryComments(stmts) {
80299
+ for (let i = 1; i < stmts.length; i++) {
80300
+ const cur = stmts[i];
80301
+ const prev = stmts[i - 1];
80302
+ const lead = cur.leadingComments;
80303
+ if (!lead || lead.length === 0) continue;
80304
+ const extra = cur.extra;
80305
+ const curSpliced = extra?.__roziePartialOrigin !== void 0;
80306
+ const prevSpliced = prev.extra?.__roziePartialOrigin !== void 0;
80307
+ if (!curSpliced) {
80308
+ if (prevSpliced && extra?.__rozieAfterGap !== void 0) {
80309
+ const prevTrail = prev.trailingComments;
80310
+ const lastLead = lead[lead.length - 1];
80311
+ if (prevTrail && prevTrail.length > 0 && prevTrail[prevTrail.length - 1] === lastLead) continue;
80312
+ prev.trailingComments = [...prevTrail ?? [], ...lead];
80313
+ }
80314
+ continue;
80315
+ }
80316
+ const prevTrail = prev.trailingComments;
80317
+ const lastLead = lead[lead.length - 1];
80318
+ if (prevTrail && prevTrail.length > 0 && prevTrail[prevTrail.length - 1] === lastLead) continue;
80319
+ prev.trailingComments = [...prevTrail ?? [], ...lead];
80320
+ }
80321
+ }
80322
+ /**
79350
80323
  * WR-01 ROOT CAUSE 2 — re-project an author function-type annotation written
79351
80324
  * on a `VariableDeclarator` `id` (`const f: (e: E) => R = …`) onto a rebuilt
79352
80325
  * `FunctionDeclaration` (which has no annotatable `id`). Each declarator-type
@@ -79472,7 +80445,7 @@ function emitScript$1(ir, collectors, _registry) {
79472
80445
  }
79473
80446
  }
79474
80447
  for (const ref of ir.refs) hookLines.push(`let ${ref.name}Ref: HTMLElement | null = null;`);
79475
- const filteredStmts = [];
80448
+ const residualStmts = [];
79476
80449
  for (const stmt of rewriteResult.rewrittenProgram.program.body) {
79477
80450
  if (t$3.isVariableDeclaration(stmt)) {
79478
80451
  if (stmt.declarations.every((d) => d.init && t$3.isCallExpression(d.init) && t$3.isIdentifier(d.init.callee) && d.init.callee.name === "$computed")) continue;
@@ -79482,8 +80455,10 @@ function emitScript$1(ir, collectors, _registry) {
79482
80455
  const callee = stmt.expression.callee;
79483
80456
  if (t$3.isIdentifier(callee) && (callee.name === "$onMount" || callee.name === "$onUnmount" || callee.name === "$onUpdate" || callee.name === "$watch" || callee.name === "$expose" || callee.name === "$provide")) continue;
79484
80457
  }
79485
- filteredStmts.push(tryHoistArrowToFunction(stmt) ?? stmt);
80458
+ residualStmts.push(stmt);
79486
80459
  }
80460
+ mirrorSpliceBoundaryComments(residualStmts);
80461
+ const filteredStmts = residualStmts.map((stmt) => tryHoistArrowToFunction(stmt) ?? stmt);
79487
80462
  let userArrowsSection = "";
79488
80463
  let scriptMap = null;
79489
80464
  if (filteredStmts.length > 0) {
@@ -80184,6 +81159,10 @@ function composeClassValue(attrs, ir, exprOpts, runtime) {
80184
81159
  if (attrs.length === 1 && attrs[0].kind === "binding") {
80185
81160
  const a = attrs[0];
80186
81161
  if (t$3.isObjectExpression(a.expression)) return renderExpr(a.expression, ir, exprOpts);
81162
+ if (a.wrapForDisplay && !t$3.isTemplateLiteral(a.expression)) {
81163
+ runtime?.add("rozieClass");
81164
+ return `rozieClass(${renderExpr(a.expression, ir, exprOpts)})`;
81165
+ }
80187
81166
  return renderExpr(a.expression, ir, exprOpts);
80188
81167
  }
80189
81168
  if (attrs.length === 1 && attrs[0].kind === "interpolated") {
@@ -80202,7 +81181,10 @@ function composeClassValue(attrs, ir, exprOpts, runtime) {
80202
81181
  const parts = [];
80203
81182
  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).`);
80204
81183
  else if (a.kind === "static") parts.push(renderStaticClassValue(a.value));
80205
- else if (a.kind === "binding") parts.push(`(${renderExpr(a.expression, ir, exprOpts)})`);
81184
+ else if (a.kind === "binding") if (a.wrapForDisplay && !t$3.isTemplateLiteral(a.expression)) {
81185
+ runtime?.add("rozieClass");
81186
+ parts.push(`rozieClass(${renderExpr(a.expression, ir, exprOpts)})`);
81187
+ } else parts.push(`(${renderExpr(a.expression, ir, exprOpts)})`);
80206
81188
  else if (a.kind === "spreadBinding") throw new Error(`Solid target: spreadBinding not valid in class array context (Phase 14).`);
80207
81189
  else {
80208
81190
  let lit = "";
@@ -82853,7 +83835,8 @@ function composeSourceMap(ms, opts) {
82853
83835
  outputOffset: opts.scriptOutputOffset
82854
83836
  }] : [],
82855
83837
  fileExt: ".tsx",
82856
- userCodeLineOffset: opts.userCodeLineOffset
83838
+ userCodeLineOffset: opts.userCodeLineOffset,
83839
+ partialLineOffsets: opts.partialLineOffsets
82857
83840
  });
82858
83841
  }
82859
83842
  //#endregion
@@ -82959,7 +83942,8 @@ function emitSolid(ir, opts = {}) {
82959
83942
  source: opts.source,
82960
83943
  scriptMap: shell.scriptMap,
82961
83944
  scriptOutputOffset: shell.scriptOutputOffset,
82962
- userCodeLineOffset: shell.userCodeLineOffset
83945
+ userCodeLineOffset: shell.userCodeLineOffset,
83946
+ partialLineOffsets: buildPartialLineOffsets(ir.setupBody.scriptProgram)
82963
83947
  }) : null;
82964
83948
  const diagnostics = [
82965
83949
  ...scriptResult.diagnostics,
@@ -85831,6 +86815,10 @@ function emitAttribute(attr, ir, tagName, tagKind = "html", opts) {
85831
86815
  return `style=\${styleMap(${expr})}`;
85832
86816
  }
85833
86817
  const expr = rewriteTemplateExpression(attr.expression, ir);
86818
+ if (attr.name === "style") {
86819
+ opts?.runtime.add("rozieStyle");
86820
+ return `style=\${rozieStyle(${expr})}`;
86821
+ }
85834
86822
  if (tagKind === "component" || tagKind === "self") {
85835
86823
  const propName = attr.name.includes("-") ? attr.name.replace(/-([a-z])/g, (_, ch) => ch.toUpperCase()) : attr.name;
85836
86824
  if (opts?._state && (t$3.isArrayExpression(attr.expression) || t$3.isObjectExpression(attr.expression)) && isPureLiteral(attr.expression)) {
@@ -86250,9 +87238,12 @@ function emitElementOpenTag(node, ir, irName, opts) {
86250
87238
  const expr = rewriteTemplateExpression(bindingClass.expression, ir);
86251
87239
  const staticPart = staticClassValues.length > 0 ? `${staticClassValues.join(" ")} ` : "";
86252
87240
  let classExpr = expr;
86253
- if (bindingClass.wrapForDisplay) {
87241
+ if (bindingClass.wrapForDisplay) if (t$3.isTemplateLiteral(bindingClass.expression)) {
86254
87242
  opts.runtime.add("rozieDisplay");
86255
87243
  classExpr = `rozieDisplay(${expr})`;
87244
+ } else {
87245
+ opts.runtime.add("rozieClass");
87246
+ classExpr = `rozieClass(${expr})`;
86256
87247
  }
86257
87248
  parts.push(`class="${staticPart}\${(${classExpr})}"`);
86258
87249
  } else if (bindingClass.kind === "interpolated") {