@llui/vite-plugin 0.0.30 → 0.0.33

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.
@@ -1 +1 @@
1
- {"version":3,"file":"msg-schema.js","sourceRoot":"","sources":["../src/msg-schema.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\n\nexport interface MsgSchema {\n discriminant: string\n variants: Record<string, Record<string, string | { enum: string[] }>>\n}\n\nexport function extractMsgSchema(source: string): MsgSchema | null {\n return extractDiscriminatedUnionSchema(source, 'Msg')\n}\n\nexport function extractEffectSchema(source: string): MsgSchema | null {\n return extractDiscriminatedUnionSchema(source, 'Effect')\n}\n\nfunction extractDiscriminatedUnionSchema(source: string, typeName: string): MsgSchema | null {\n const sf = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true)\n\n for (const stmt of sf.statements) {\n if (!ts.isTypeAliasDeclaration(stmt)) continue\n if (stmt.name.text !== typeName) continue\n\n const variants: MsgSchema['variants'] = {}\n collectVariants(stmt.type, variants)\n\n if (Object.keys(variants).length === 0) return null\n\n return { discriminant: 'type', variants }\n }\n\n return null\n}\n\nfunction collectVariants(type: ts.TypeNode, variants: MsgSchema['variants']): void {\n if (ts.isUnionTypeNode(type)) {\n for (const member of type.types) {\n collectVariants(member, variants)\n }\n return\n }\n\n if (ts.isTypeLiteralNode(type)) {\n let discriminantValue: string | null = null\n const fields: Record<string, string | { enum: string[] }> = {}\n\n for (const member of type.members) {\n if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name)) continue\n\n const name = member.name.text\n const memberType = member.type\n\n if (name === 'type' && memberType) {\n // Extract the discriminant value\n if (ts.isLiteralTypeNode(memberType) && ts.isStringLiteral(memberType.literal)) {\n discriminantValue = memberType.literal.text\n }\n continue\n }\n\n if (!memberType) {\n fields[name] = 'unknown'\n continue\n }\n\n fields[name] = resolveFieldType(memberType)\n }\n\n if (discriminantValue) {\n variants[discriminantValue] = fields\n }\n }\n}\n\nfunction resolveFieldType(type: ts.TypeNode): string | { enum: string[] } {\n // Primitive keywords\n if (type.kind === ts.SyntaxKind.StringKeyword) return 'string'\n if (type.kind === ts.SyntaxKind.NumberKeyword) return 'number'\n if (type.kind === ts.SyntaxKind.BooleanKeyword) return 'boolean'\n\n // String literal union: 'a' | 'b' | 'c'\n if (ts.isUnionTypeNode(type)) {\n const literals: string[] = []\n let allLiterals = true\n for (const member of type.types) {\n if (ts.isLiteralTypeNode(member) && ts.isStringLiteral(member.literal)) {\n literals.push(member.literal.text)\n } else {\n allLiterals = false\n break\n }\n }\n if (allLiterals && literals.length > 0) {\n return { enum: literals }\n }\n }\n\n return 'unknown'\n}\n"]}
1
+ {"version":3,"file":"msg-schema.js","sourceRoot":"","sources":["../src/msg-schema.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\n\n/**\n * The \"bare type\" of a field. Covers four cases:\n * - primitive keyword as a string: `'string'`, `'number'`, `'boolean'`, `'unknown'`\n * - string-literal union: `{enum: ['a', 'b']}`\n * - nested object shape: `{kind: 'object', shape: {...}}` — emitted when\n * a field's type is a local interface/type alias the extractor could\n * follow (depth-limited; cross-file references stay `'unknown'`).\n * - array of element type: `{kind: 'array', element: <bare type>}`.\n *\n * The synthesizer in `@llui/agent`'s `list_actions` walks these to build\n * copy-paste-ready payload examples; the validator in `send_message`\n * walks them too (treating object/array as \"any\" since deep validation\n * is the reducer's job).\n */\nexport type MsgFieldType =\n | string\n | { enum: string[] }\n | { kind: 'object'; shape: Record<string, MsgField> }\n | { kind: 'array'; element: MsgFieldType }\n\n/**\n * Rich per-field descriptor. Emitted only when there's something\n * beyond the bare type to communicate — optionality, an explicit\n * priority hint, or a freeform agent hint. When everything but `type`\n * is unset, the producer emits the bare `MsgFieldType` instead so\n * variants without annotations stay byte-cheap in the bundle.\n */\nexport interface MsgFieldRich {\n type: MsgFieldType\n /** Mirrors TypeScript's `?:` optional marker. Required fields omit this. */\n optional?: boolean\n /**\n * Strength signal for optional fields. Borrows RFC 2119's `SHOULD`:\n * the LLM ought to fill it in unless it has a specific reason not\n * to. Required fields don't carry a priority — TS already conveys\n * \"must\" via the type system. Currently the only level; future\n * extensions could add `'recommended'` or similar.\n */\n priority?: 'should'\n /** Freeform consequence-shaped explanation. Surfaced verbatim to\n * the LLM at affordance time. */\n hint?: string\n}\n\nexport type MsgField = MsgFieldType | MsgFieldRich\n\nexport interface MsgSchema {\n discriminant: string\n variants: Record<string, Record<string, MsgField>>\n}\n\n/** True when `f` is a rich descriptor (object with `type` key). */\nexport function isRichField(f: MsgField): f is MsgFieldRich {\n return typeof f === 'object' && f !== null && !Array.isArray(f) && 'type' in f\n}\n\n/** Extracts the bare type from either descriptor form. */\nexport function fieldType(f: MsgField): MsgFieldType {\n return isRichField(f) ? f.type : f\n}\n\nexport function extractMsgSchema(source: string, typeName: string = 'Msg'): MsgSchema | null {\n return extractDiscriminatedUnionSchema(source, typeName)\n}\n\nexport function extractEffectSchema(source: string, typeName: string = 'Effect'): MsgSchema | null {\n return extractDiscriminatedUnionSchema(source, typeName)\n}\n\nfunction extractDiscriminatedUnionSchema(source: string, typeName: string): MsgSchema | null {\n const sf = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true)\n const typeIndex = buildTypeIndex(sf)\n\n for (const stmt of sf.statements) {\n if (!ts.isTypeAliasDeclaration(stmt)) continue\n if (stmt.name.text !== typeName) continue\n\n const variants: MsgSchema['variants'] = {}\n collectVariants(stmt.type, variants, source, typeIndex)\n\n if (Object.keys(variants).length === 0) return null\n\n return { discriminant: 'type', variants }\n }\n\n return null\n}\n\n/**\n * Index of type aliases and interfaces visible from a source file,\n * keyed by name. Lets the field-type resolver follow `Criterion[]` →\n * `interface Criterion { … }` and emit a nested object shape rather\n * than `'unknown'`.\n *\n * The cross-file resolver pipeline (`cross-file-resolver.ts`) builds\n * an enriched index that includes types imported from sibling files —\n * follow `GridSorting` → `'rank' | 'crit-X' | 'crit-Y'` → `{enum: […]}`\n * even when the alias lives in `./state.ts` not the Msg-defining file.\n */\nexport type TypeIndex = Map<string, ts.TypeNode | ts.InterfaceDeclaration>\n\nfunction buildTypeIndex(sf: ts.SourceFile): TypeIndex {\n const index: TypeIndex = new Map()\n for (const stmt of sf.statements) {\n if (ts.isTypeAliasDeclaration(stmt)) {\n index.set(stmt.name.text, stmt.type)\n } else if (ts.isInterfaceDeclaration(stmt)) {\n index.set(stmt.name.text, stmt)\n }\n }\n return index\n}\n\nfunction collectVariants(\n type: ts.TypeNode,\n variants: MsgSchema['variants'],\n source: string,\n typeIndex: TypeIndex,\n): void {\n if (ts.isUnionTypeNode(type)) {\n for (const member of type.types) {\n collectVariants(member, variants, source, typeIndex)\n }\n return\n }\n\n if (ts.isTypeLiteralNode(type)) {\n let discriminantValue: string | null = null\n const fields: Record<string, MsgField> = {}\n\n for (const member of type.members) {\n if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name)) continue\n\n const name = member.name.text\n const memberType = member.type\n\n if (name === 'type' && memberType) {\n // Extract the discriminant value\n if (ts.isLiteralTypeNode(memberType) && ts.isStringLiteral(memberType.literal)) {\n discriminantValue = memberType.literal.text\n }\n continue\n }\n\n fields[name] = buildFieldDescriptor(member, source, typeIndex)\n }\n\n if (discriminantValue) {\n variants[discriminantValue] = fields\n }\n }\n}\n\n/**\n * Build a single field descriptor from a property signature: type,\n * optionality, and any `@should(\"…\")` JSDoc hint. Emits the compact\n * bare form when there's nothing extra to communicate; otherwise the\n * rich `{type, optional?, priority?, hint?}` shape.\n *\n * Exported so the cross-file resolver (which walks the same property\n * signatures when the Msg type lives in a different file from the\n * `component()` call) can produce identical descriptors. Without\n * sharing this helper, JSDoc hints would silently disappear whenever\n * a Msg union got resolved across module boundaries.\n */\nexport function buildFieldDescriptor(\n member: ts.PropertySignature,\n source: string,\n typeIndex: TypeIndex = new Map(),\n): MsgField {\n const baseType: MsgFieldType = member.type\n ? resolveFieldType(member.type, typeIndex, MAX_FIELD_DEPTH)\n : 'unknown'\n const optional = member.questionToken !== undefined\n const jsdoc = readMemberJSDoc(source, member)\n const hint = readShouldHint(jsdoc)\n\n if (!optional && hint === null) {\n return baseType\n }\n const rich: MsgFieldRich = { type: baseType }\n if (optional) rich.optional = true\n if (hint !== null) {\n rich.priority = 'should'\n rich.hint = hint\n }\n return rich\n}\n\n/**\n * Recursion bound for nested type resolution. Stops the extractor\n * before it spirals on self-referential or mutually-recursive types\n * (`type Tree = { children: Tree[] }`). At depth 0 every reference\n * collapses to `'unknown'`; the synthesizer emits `null` and the\n * agent falls back to free-form filling.\n *\n * 3 covers the common cases (Msg payload → Criterion → ValueMeta),\n * keeps the bundle bounded, and is well under the Tarjan-style\n * depths needed for actual recursive types.\n */\nconst MAX_FIELD_DEPTH = 3\n\nexport function resolveFieldType(\n type: ts.TypeNode,\n typeIndex: TypeIndex = new Map(),\n depth = MAX_FIELD_DEPTH,\n): MsgFieldType {\n // Primitive keywords\n if (type.kind === ts.SyntaxKind.StringKeyword) return 'string'\n if (type.kind === ts.SyntaxKind.NumberKeyword) return 'number'\n if (type.kind === ts.SyntaxKind.BooleanKeyword) return 'boolean'\n\n // String literal union: 'a' | 'b' | 'c'\n if (ts.isUnionTypeNode(type)) {\n const literals: string[] = []\n let allLiterals = true\n for (const member of type.types) {\n if (ts.isLiteralTypeNode(member) && ts.isStringLiteral(member.literal)) {\n literals.push(member.literal.text)\n } else {\n allLiterals = false\n break\n }\n }\n if (allLiterals && literals.length > 0) {\n return { enum: literals }\n }\n }\n\n // Below this point, all branches need depth budget. Bail out cheaply.\n if (depth <= 0) return 'unknown'\n\n // Inline object literal — `{a: number; b: string}` directly.\n if (ts.isTypeLiteralNode(type)) {\n return { kind: 'object', shape: collectInlineShape(type, typeIndex, depth - 1) }\n }\n\n // Array type — `T[]` and `readonly T[]`.\n if (ts.isArrayTypeNode(type)) {\n return { kind: 'array', element: resolveFieldType(type.elementType, typeIndex, depth - 1) }\n }\n // Generic Array<T> (less common in app code but compiler may produce it).\n if (\n ts.isTypeReferenceNode(type) &&\n ts.isIdentifier(type.typeName) &&\n type.typeName.text === 'Array' &&\n type.typeArguments?.length === 1 &&\n type.typeArguments[0]\n ) {\n return {\n kind: 'array',\n element: resolveFieldType(type.typeArguments[0], typeIndex, depth - 1),\n }\n }\n // ReadonlyArray<T> → same shape; the readonly modifier is purely a\n // TypeScript-side concern that the agent never observes at runtime.\n if (\n ts.isTypeReferenceNode(type) &&\n ts.isIdentifier(type.typeName) &&\n type.typeName.text === 'ReadonlyArray' &&\n type.typeArguments?.length === 1 &&\n type.typeArguments[0]\n ) {\n return {\n kind: 'array',\n element: resolveFieldType(type.typeArguments[0], typeIndex, depth - 1),\n }\n }\n // `readonly T[]` parses as TypeOperator(readonly) wrapping ArrayType.\n if (ts.isTypeOperatorNode(type) && type.operator === ts.SyntaxKind.ReadonlyKeyword) {\n return resolveFieldType(type.type, typeIndex, depth)\n }\n\n // Named type reference — chase it through the local index.\n if (ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName)) {\n const target = typeIndex.get(type.typeName.text)\n if (target) {\n if (ts.isInterfaceDeclaration(target)) {\n return {\n kind: 'object',\n shape: collectInterfaceShape(target, typeIndex, depth - 1),\n }\n }\n // Type alias — recurse on its body. `type Foo = …` could resolve\n // to anything (object literal, array, union, primitive); each\n // already has its own branch above.\n return resolveFieldType(target, typeIndex, depth - 1)\n }\n // Reference to a type the index doesn't know about — typically\n // imported from another module. Cross-file resolution is the\n // separate cross-file-resolver pipeline's job; leave this as\n // unknown rather than fabricating a misleading shape.\n return 'unknown'\n }\n\n return 'unknown'\n}\n\nfunction collectInlineShape(\n lit: ts.TypeLiteralNode,\n typeIndex: TypeIndex,\n depth: number,\n): Record<string, MsgField> {\n const shape: Record<string, MsgField> = {}\n for (const member of lit.members) {\n if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name)) continue\n const name = member.name.text\n const baseType: MsgFieldType = member.type\n ? resolveFieldType(member.type, typeIndex, depth)\n : 'unknown'\n const optional = member.questionToken !== undefined\n if (!optional) {\n shape[name] = baseType\n } else {\n shape[name] = { type: baseType, optional: true }\n }\n }\n return shape\n}\n\nfunction collectInterfaceShape(\n iface: ts.InterfaceDeclaration,\n typeIndex: TypeIndex,\n depth: number,\n): Record<string, MsgField> {\n const shape: Record<string, MsgField> = {}\n for (const member of iface.members) {\n if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name)) continue\n const name = member.name.text\n const baseType: MsgFieldType = member.type\n ? resolveFieldType(member.type, typeIndex, depth)\n : 'unknown'\n const optional = member.questionToken !== undefined\n if (!optional) {\n shape[name] = baseType\n } else {\n shape[name] = { type: baseType, optional: true }\n }\n }\n return shape\n}\n\n/**\n * Read the leading JSDoc block immediately above `member`. The\n * TypeScript parser doesn't attach JSDoc to interior property\n * signatures, so we re-scan the source between the previous member's\n * end (or the type-literal's `{`) and this member's start, and return\n * the last `/** … *\\/` block found there. Returns `''` when none.\n */\nfunction readMemberJSDoc(source: string, member: ts.PropertySignature): string {\n const ranges = ts.getLeadingCommentRanges(source, member.pos) ?? []\n // Walk in order, keeping only `/** */` blocks. Multiple back-to-back\n // JSDocs concatenate (matches msg-annotations.ts's existing behavior).\n const docs = ranges\n .filter((r) => r.kind === ts.SyntaxKind.MultiLineCommentTrivia)\n .map((r) => source.slice(r.pos, r.end))\n .filter((txt) => txt.startsWith('/**'))\n return docs.join('\\n')\n}\n\n/**\n * Match `@should(\"…\")` (and curly-quote variant) anywhere in the\n * JSDoc. Mirrors msg-annotations.ts's `@intent` parser — same grammar,\n * same tolerance for either ASCII or curly quotes.\n *\n * Returns the unescaped string content, or null when the tag is\n * absent or malformed.\n */\nfunction readShouldHint(comment: string): string | null {\n if (!comment) return null\n const match = comment.match(/@should\\s*\\(\\s*[\"“]([^\"”]*)[\"”]\\s*\\)/)\n return match?.[1] ?? null\n}\n"]}
@@ -1,4 +1,7 @@
1
1
  import ts from 'typescript';
2
+ import { extractMsgSchema, extractEffectSchema } from './msg-schema.js';
3
+ import { extractMsgAnnotations } from './msg-annotations.js';
4
+ import { extractStateSchema } from './state-schema.js';
2
5
  /**
3
6
  * Transform a source file containing @llui/dom imports.
4
7
  * Returns the transformed source or null if no transformation needed.
@@ -8,7 +11,50 @@ export interface TransformEdit {
8
11
  end: number;
9
12
  replacement: string;
10
13
  }
11
- export declare function transformLlui(source: string, _filename: string, devMode?: boolean, emitAgentMetadata?: boolean, mcpPort?: number | null, verbose?: boolean): {
14
+ /**
15
+ * Pre-resolved external type sources from the cross-file resolver.
16
+ * When the plugin's vite hook detects that `State` / `Msg` / `Effect`
17
+ * for a `component<...>()` call are imported (not declared in the
18
+ * current file), it walks the imports and re-exports to find the
19
+ * declaring file, then passes the source + local name here. Each
20
+ * extractor below uses the resolved source instead of falling back to
21
+ * the file-local search (which would miss the type entirely).
22
+ */
23
+ export interface ExternalTypeSources {
24
+ state?: {
25
+ source: string;
26
+ typeName: string;
27
+ };
28
+ msg?: {
29
+ source: string;
30
+ typeName: string;
31
+ };
32
+ effect?: {
33
+ source: string;
34
+ typeName: string;
35
+ };
36
+ }
37
+ /**
38
+ * Schemas already extracted by the plugin's async hook before invoking
39
+ * the sync transform. Used for cases the file-local sync extractors
40
+ * can't handle on their own:
41
+ * - The Msg/Effect/State alias lives in another file (cross-file
42
+ * resolution, see `cross-file-resolver.ts`).
43
+ * - The Msg/Effect alias is a *composition* — a union mixing inline
44
+ * `{ type: 'literal' }` members with TypeReferences pointing at
45
+ * other (often imported) Msg unions.
46
+ *
47
+ * When provided, transformLlui uses these instead of running its own
48
+ * file-local extractors. When omitted (the test path that constructs
49
+ * a single-source string), the file-local extractors run as before.
50
+ */
51
+ export interface PreExtractedSchemas {
52
+ msgSchema?: ReturnType<typeof extractMsgSchema>;
53
+ msgAnnotations?: ReturnType<typeof extractMsgAnnotations>;
54
+ stateSchema?: ReturnType<typeof extractStateSchema>;
55
+ effectSchema?: ReturnType<typeof extractEffectSchema>;
56
+ }
57
+ export declare function transformLlui(source: string, _filename: string, devMode?: boolean, emitAgentMetadata?: boolean, mcpPort?: number | null, verbose?: boolean, typeSources?: ExternalTypeSources, preExtracted?: PreExtractedSchemas): {
12
58
  output: string;
13
59
  edits: TransformEdit[];
14
60
  } | null;
@@ -34,10 +80,10 @@ export interface UseClientTransformResult {
34
80
  * - `export function foo() {}` and `export class Foo {}` — rewritten
35
81
  * as stubs but the caller may be surprised that `foo` and `Foo` are
36
82
  * ComponentDef-shaped objects during SSR.
37
- * - `export { a, b } from './other'` — re-export forms are not
83
+ * - `export { a, b } from './other.js'` — re-export forms are not
38
84
  * detected; they pass through and will still pull `./other` into
39
85
  * the SSR graph.
40
- * - `export * from './other'` — same as above.
86
+ * - `export * from './other.js'` — same as above.
41
87
  * - `export type ...` — type exports are erased by TS so nothing to
42
88
  * stub; left untouched.
43
89
  */
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAiL3B;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,UAAQ,EACf,iBAAiB,UAAQ,EACzB,OAAO,GAAE,MAAM,GAAG,IAAW,EAC7B,OAAO,UAAQ,GACd;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAA;CAAE,GAAG,IAAI,CA8TnD;AAwoID,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,wBAAwB,GAAG,IAAI,CAoGjC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CA4B7D;AAID;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI3D;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,GAAG,MAAM,GAAG,IAAI,CAcrF;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CASzF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC,GAChE,MAAM,CAYR"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EAIpB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,qBAAqB,EAA2B,MAAM,sBAAsB,CAAA;AACrF,OAAO,EAAE,kBAAkB,EAAkB,MAAM,mBAAmB,CAAA;AA6KtE;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C,GAAG,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1C,MAAM,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAC9C;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAA;IAC/C,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAA;IACzD,WAAW,CAAC,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAA;IACnD,YAAY,CAAC,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAA;CACtD;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,UAAQ,EACf,iBAAiB,UAAQ,EACzB,OAAO,GAAE,MAAM,GAAG,IAAW,EAC7B,OAAO,UAAQ,EACf,WAAW,CAAC,EAAE,mBAAmB,EACjC,YAAY,CAAC,EAAE,mBAAmB,GACjC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAA;CAAE,GAAG,IAAI,CAqYnD;AAgqID,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,wBAAwB,GAAG,IAAI,CAoGjC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CA4B7D;AAID;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI3D;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,GAAG,MAAM,GAAG,IAAI,CAcrF;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CASzF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC,GAChE,MAAM,CAYR"}
package/dist/transform.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import ts from 'typescript';
2
2
  import { collectDeps } from './collect-deps.js';
3
- import { extractMsgSchema, extractEffectSchema } from './msg-schema.js';
3
+ import { extractMsgSchema, extractEffectSchema, isRichField, } from './msg-schema.js';
4
4
  import { extractMsgAnnotations } from './msg-annotations.js';
5
5
  import { extractStateSchema } from './state-schema.js';
6
6
  import { computeSchemaHash } from './schema-hash.js';
7
- import { extractBindingDescriptors } from './binding-descriptors.js';
7
+ import { tagDispatchHandlers, injectScopeVariantRegistrations } from './binding-descriptors.js';
8
8
  import { compilerCache } from './compiler-cache.js';
9
9
  function createMaskLiteral(f, mask) {
10
10
  if (mask >= 0)
@@ -176,8 +176,8 @@ function resolveKey(key, kind) {
176
176
  return 'class';
177
177
  return key;
178
178
  }
179
- export function transformLlui(source, _filename, devMode = false, emitAgentMetadata = false, mcpPort = 5200, verbose = false) {
180
- const sourceFile = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true);
179
+ export function transformLlui(source, _filename, devMode = false, emitAgentMetadata = false, mcpPort = 5200, verbose = false, typeSources, preExtracted) {
180
+ let sourceFile = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true);
181
181
  // Find the @llui/dom import
182
182
  const imp = findLluiImport(sourceFile);
183
183
  if (!imp)
@@ -187,6 +187,38 @@ export function transformLlui(source, _filename, devMode = false, emitAgentMetad
187
187
  const importedHelpers = getImportedHelpers(lluiImport);
188
188
  if (importedHelpers.size === 0 && !hasReactiveAccessors(sourceFile))
189
189
  return null;
190
+ // Connect-pattern pass: detects `*.connect(get, sendFn, …)` call
191
+ // sites and inserts a runtime `__registerScopeVariants([...])`
192
+ // adjacent to each, with the variants statically extracted from the
193
+ // sendFn's body. Handles the dispatch-translation case at the
194
+ // syntactic level — handler propagation via `tagSend` covers the
195
+ // rest. Runs FIRST so its `collectLocalFns` resolver still sees raw
196
+ // arrow initializers in const declarations (the universal tagger
197
+ // below replaces those initializers with `Object.assign(...)`
198
+ // wrappers).
199
+ //
200
+ // Universal handler-tagger pass: walks every arrow/function
201
+ // expression and wraps any whose body contains literal
202
+ // `send({type:'X'})` / `dispatch({type:'X'})` calls with
203
+ // `Object.assign(arrow, {__lluiVariants: ['X']})`. The runtime
204
+ // (`@llui/dom` `elements.ts` / `el-split.ts`) reads the tag from
205
+ // event-handler bindings only — so tags placed on functions in
206
+ // non-handler positions are runtime-inert. This deliberately covers
207
+ // three patterns at once:
208
+ // • Inline event handlers (`onClick: () => send(...)`)
209
+ // • Const-bound translators (`const sendMenu = (m) => dispatch(...)`)
210
+ // • Positional-arg helpers (`navButton(label, () => dispatch(...))`)
211
+ //
212
+ // Both passes gated on dev/agent-metadata so production bundles
213
+ // without agent integration don't pay the per-handler `Object.assign`
214
+ // cost.
215
+ let scopeRegistrationsInjected = false;
216
+ if (devMode || emitAgentMetadata) {
217
+ const injection = injectScopeVariantRegistrations(sourceFile, ts.factory);
218
+ sourceFile = injection.sf;
219
+ scopeRegistrationsInjected = injection.injected;
220
+ sourceFile = tagDispatchHandlers(sourceFile, ts.factory);
221
+ }
190
222
  // Pass 2 pre-scan: collect all state access paths
191
223
  // Only use precise masks in files that define a component() — the __dirty
192
224
  // function is generated per-component, so bit assignments in other files
@@ -302,10 +334,27 @@ export function transformLlui(source, _filename, devMode = false, emitAgentMetad
302
334
  usesApplyBinding = true;
303
335
  // Extract schema data once — used both for devMode injections and the
304
336
  // unconditional __schemaHash (spec §7.4: hash ships in prod too).
305
- const msgSchema = extractMsgSchema(source);
306
- const msgAnnotations = extractMsgAnnotations(source);
307
- const stateSchema = extractStateSchema(source);
308
- const bindingDescriptors = extractBindingDescriptors(source);
337
+ //
338
+ // Resolution priority for each schema:
339
+ // 1. preExtracted.* used when the plugin's async hook has already
340
+ // done cross-file + composition extraction (the production path).
341
+ // 2. typeSources.* — file-local extraction against an alternate
342
+ // source file (legacy path; covers cross-file but not composition).
343
+ // 3. file-local — the test path: extract from `source` itself.
344
+ //
345
+ // When `preExtracted` is provided, treat it as authoritative even
346
+ // when the value is `null` (the resolver was run and found
347
+ // nothing) — falling back to local extraction would mask the
348
+ // resolver's "not extractable" verdict.
349
+ const msgSchema = preExtracted?.msgSchema !== undefined
350
+ ? preExtracted.msgSchema
351
+ : extractMsgSchema(typeSources?.msg?.source ?? source, typeSources?.msg?.typeName ?? 'Msg');
352
+ const msgAnnotations = preExtracted?.msgAnnotations !== undefined
353
+ ? preExtracted.msgAnnotations
354
+ : extractMsgAnnotations(typeSources?.msg?.source ?? source, typeSources?.msg?.typeName ?? 'Msg');
355
+ const stateSchema = preExtracted?.stateSchema !== undefined
356
+ ? preExtracted.stateSchema
357
+ : extractStateSchema(typeSources?.state?.source ?? source, typeSources?.state?.typeName ?? 'State');
309
358
  const shouldEmitAgentMetadata = devMode || emitAgentMetadata;
310
359
  if (shouldEmitAgentMetadata) {
311
360
  if (msgSchema) {
@@ -317,13 +366,18 @@ export function transformLlui(source, _filename, devMode = false, emitAgentMetad
317
366
  if (stateSchema) {
318
367
  result = injectStateSchema(result ?? node, stateSchema.fields, f);
319
368
  }
320
- const effectSchema = extractEffectSchema(source);
369
+ const effectSchema = preExtracted?.effectSchema !== undefined
370
+ ? preExtracted.effectSchema
371
+ : extractEffectSchema(typeSources?.effect?.source ?? source, typeSources?.effect?.typeName ?? 'Effect');
321
372
  if (effectSchema) {
322
373
  result = injectEffectSchema(result ?? node, effectSchema, f);
323
374
  }
324
- if (bindingDescriptors.length > 0) {
325
- result = injectBindingDescriptors(result ?? node, bindingDescriptors, f);
326
- }
375
+ // Note: binding descriptors are no longer emitted on the
376
+ // component def. They're now collected at runtime by walking
377
+ // event-handler arrows that the `tagEventHandlerSends` pass
378
+ // wrapped with `__lluiVariants` metadata. See
379
+ // `binding-descriptors.ts` (compiler) and the matching
380
+ // `@llui/dom binding-descriptors.ts` (runtime registry).
327
381
  // Populate compiler cache — preSource and msgMaskMap are known now;
328
382
  // postSource is filled in after the full output is assembled.
329
383
  const cachedComponentName = extractComponentNameFromConfig(node);
@@ -364,7 +418,7 @@ export function transformLlui(source, _filename, devMode = false, emitAgentMetad
364
418
  // Pass 3: Clean up imports — use the old cleanupImports approach
365
419
  // which operates on the transformed SourceFile safely
366
420
  const safeToRemove = new Set([...compiledHelpers].filter((h) => !bailedHelpers.has(h)));
367
- transformed = cleanupImports(transformed, lluiImport, importedHelpers, safeToRemove, usesElSplit, usesElTemplate, usesMemo, usesApplyBinding, usesCloneStaticTemplate, f);
421
+ transformed = cleanupImports(transformed, lluiImport, importedHelpers, safeToRemove, usesElSplit, usesElTemplate, usesMemo, usesApplyBinding, usesCloneStaticTemplate, scopeRegistrationsInjected, f);
368
422
  if (edits.length === 0)
369
423
  return null;
370
424
  // Find component declarations for HMR and agent metadata
@@ -1034,7 +1088,10 @@ function tryInjectDirty(node, fieldBits, f) {
1034
1088
  // it changes. Lets introspection tools decode runtime dirty masks to field names.
1035
1089
  const legendProps = [];
1036
1090
  for (const [field, bit] of topLevelBits) {
1037
- legendProps.push(f.createPropertyAssignment(field, createMaskLiteral(f, bit)));
1091
+ // Use string literal — state field names declared via string keys
1092
+ // (e.g. `{ "weird-key": ... }`) would otherwise emit as bare
1093
+ // identifiers and break the printed output.
1094
+ legendProps.push(f.createPropertyAssignment(f.createStringLiteral(field), createMaskLiteral(f, bit)));
1038
1095
  }
1039
1096
  const legendProp = f.createPropertyAssignment('__maskLegend', f.createObjectLiteralExpression(legendProps, false));
1040
1097
  // Structural mask — used by both __update and __handlers
@@ -2001,13 +2058,14 @@ function buildAccess(f, root, parts) {
2001
2058
  return expr;
2002
2059
  }
2003
2060
  // ── Pass 3: Import cleanup ───────────────────────────────────────
2004
- function cleanupImports(sf, lluiImport, _helpers, compiled, usesElSplit, usesElTemplate, usesMemo, usesApplyBinding, usesCloneStaticTemplate, f) {
2061
+ function cleanupImports(sf, lluiImport, _helpers, compiled, usesElSplit, usesElTemplate, usesMemo, usesApplyBinding, usesCloneStaticTemplate, usesRegisterScopeVariants, f) {
2005
2062
  if (compiled.size === 0 &&
2006
2063
  !usesElTemplate &&
2007
2064
  !usesElSplit &&
2008
2065
  !usesMemo &&
2009
2066
  !usesApplyBinding &&
2010
- !usesCloneStaticTemplate)
2067
+ !usesCloneStaticTemplate &&
2068
+ !usesRegisterScopeVariants)
2011
2069
  return sf;
2012
2070
  const clause = lluiImport.importClause;
2013
2071
  if (!clause?.namedBindings || !ts.isNamedImports(clause.namedBindings))
@@ -2037,6 +2095,13 @@ function cleanupImports(sf, lluiImport, _helpers, compiled, usesElSplit, usesElT
2037
2095
  remaining.push(f.createImportSpecifier(false, undefined, f.createIdentifier('__handleMsg')));
2038
2096
  }
2039
2097
  }
2098
+ // The connect-pattern injector (binding-descriptors.ts) emits
2099
+ // `__registerScopeVariants([...])` calls; ensure the runtime
2100
+ // helper is imported when at least one was inserted.
2101
+ const hasRegisterScopeVariants = clause.namedBindings.elements.some((s) => s.name.text === '__registerScopeVariants');
2102
+ if (!hasRegisterScopeVariants && usesRegisterScopeVariants) {
2103
+ remaining.push(f.createImportSpecifier(false, undefined, f.createIdentifier('__registerScopeVariants')));
2104
+ }
2040
2105
  const newBindings = f.createNamedImports(remaining);
2041
2106
  const newClause = f.createImportClause(false, undefined, newBindings);
2042
2107
  const newImportDecl = f.createImportDeclaration(undefined, newClause, lluiImport.moduleSpecifier);
@@ -2103,7 +2168,7 @@ function stateTypeToLiteral(t, f) {
2103
2168
  // object
2104
2169
  const fieldProps = [];
2105
2170
  for (const [k, v] of Object.entries(t.fields)) {
2106
- fieldProps.push(f.createPropertyAssignment(k, stateTypeToLiteral(v, f)));
2171
+ fieldProps.push(f.createPropertyAssignment(f.createStringLiteral(k), stateTypeToLiteral(v, f)));
2107
2172
  }
2108
2173
  return f.createObjectLiteralExpression([
2109
2174
  f.createPropertyAssignment('kind', f.createStringLiteral('object')),
@@ -2152,15 +2217,11 @@ function injectMsgSchema(node, schema, f) {
2152
2217
  const variantProps = [];
2153
2218
  for (const [variant, fields] of Object.entries(schema.variants)) {
2154
2219
  const fieldProps = [];
2155
- for (const [field, type] of Object.entries(fields)) {
2156
- if (typeof type === 'string') {
2157
- fieldProps.push(f.createPropertyAssignment(field, f.createStringLiteral(type)));
2158
- }
2159
- else {
2160
- fieldProps.push(f.createPropertyAssignment(field, f.createObjectLiteralExpression([
2161
- f.createPropertyAssignment('enum', f.createArrayLiteralExpression(type.enum.map((v) => f.createStringLiteral(v)))),
2162
- ])));
2163
- }
2220
+ for (const [field, descriptor] of Object.entries(fields)) {
2221
+ // Always wrap user-derived keys with createStringLiteral — bare
2222
+ // strings get printed as identifiers, which breaks fields named
2223
+ // with reserved words ('delete'), hyphens, or other non-id chars.
2224
+ fieldProps.push(f.createPropertyAssignment(f.createStringLiteral(field), buildFieldDescriptorExpr(descriptor, f)));
2164
2225
  }
2165
2226
  variantProps.push(f.createPropertyAssignment(f.createStringLiteral(variant), f.createObjectLiteralExpression(fieldProps)));
2166
2227
  }
@@ -2175,6 +2236,60 @@ function injectMsgSchema(node, schema, f) {
2175
2236
  ...node.arguments.slice(1),
2176
2237
  ]);
2177
2238
  }
2239
+ /**
2240
+ * Emit the AST for a single field descriptor. Bare forms (`'string'`,
2241
+ * `{enum: [...]}`) print as their compact form; rich descriptors emit
2242
+ * an object literal carrying `type`, `optional`, `priority`, and `hint`
2243
+ * properties as set.
2244
+ */
2245
+ function buildFieldDescriptorExpr(descriptor, f) {
2246
+ if (typeof descriptor === 'string') {
2247
+ return f.createStringLiteral(descriptor);
2248
+ }
2249
+ if (isRichField(descriptor)) {
2250
+ const props = [];
2251
+ // `type` is the bare type; recurse to emit either a string or the
2252
+ // enum object.
2253
+ props.push(f.createPropertyAssignment('type', buildFieldDescriptorExpr(descriptor.type, f)));
2254
+ if (descriptor.optional) {
2255
+ props.push(f.createPropertyAssignment('optional', f.createTrue()));
2256
+ }
2257
+ if (descriptor.priority) {
2258
+ props.push(f.createPropertyAssignment('priority', f.createStringLiteral(descriptor.priority)));
2259
+ }
2260
+ if (descriptor.hint !== undefined) {
2261
+ props.push(f.createPropertyAssignment('hint', f.createStringLiteral(descriptor.hint)));
2262
+ }
2263
+ return f.createObjectLiteralExpression(props);
2264
+ }
2265
+ // The remaining cases are bare type-shape variants emitted by
2266
+ // `resolveFieldType`: enum, object, array. Discriminate by which
2267
+ // key is present so we never confuse an inline object literal
2268
+ // ({enum: [...]}) with a deeply-nested shape descriptor.
2269
+ if ('enum' in descriptor) {
2270
+ return f.createObjectLiteralExpression([
2271
+ f.createPropertyAssignment('enum', f.createArrayLiteralExpression(descriptor.enum.map((v) => f.createStringLiteral(v)))),
2272
+ ]);
2273
+ }
2274
+ if ('kind' in descriptor && descriptor.kind === 'object') {
2275
+ // Nested object shape — recurse per field. Fields may themselves
2276
+ // be rich descriptors (optional, etc.), so route each through the
2277
+ // same builder.
2278
+ const shapeProps = [];
2279
+ for (const [k, v] of Object.entries(descriptor.shape)) {
2280
+ shapeProps.push(f.createPropertyAssignment(f.createStringLiteral(k), buildFieldDescriptorExpr(v, f)));
2281
+ }
2282
+ return f.createObjectLiteralExpression([
2283
+ f.createPropertyAssignment('kind', f.createStringLiteral('object')),
2284
+ f.createPropertyAssignment('shape', f.createObjectLiteralExpression(shapeProps)),
2285
+ ]);
2286
+ }
2287
+ // Array — `{kind: 'array', element: <bare type>}`.
2288
+ return f.createObjectLiteralExpression([
2289
+ f.createPropertyAssignment('kind', f.createStringLiteral('array')),
2290
+ f.createPropertyAssignment('element', buildFieldDescriptorExpr(descriptor.element, f)),
2291
+ ]);
2292
+ }
2178
2293
  function hasNonDefaultAnnotation(a) {
2179
2294
  for (const v of Object.values(a)) {
2180
2295
  if (v.intent !== null)
@@ -2183,7 +2298,7 @@ function hasNonDefaultAnnotation(a) {
2183
2298
  return true;
2184
2299
  if (v.requiresConfirm)
2185
2300
  return true;
2186
- if (v.humanOnly)
2301
+ if (v.dispatchMode !== 'shared')
2187
2302
  return true;
2188
2303
  }
2189
2304
  return false;
@@ -2191,13 +2306,18 @@ function hasNonDefaultAnnotation(a) {
2191
2306
  function annotationsToObjectLiteral(a) {
2192
2307
  const props = [];
2193
2308
  for (const [variant, ann] of Object.entries(a)) {
2194
- props.push(ts.factory.createPropertyAssignment(variant, ts.factory.createObjectLiteralExpression([
2309
+ props.push(ts.factory.createPropertyAssignment(
2310
+ // Wrap with createStringLiteral — the printer treats bare strings
2311
+ // as identifiers, which produces invalid JS for discriminants
2312
+ // containing characters like '/' (e.g. 'Router/RouteChanged'),
2313
+ // reserved words ('delete'), or hyphens.
2314
+ ts.factory.createStringLiteral(variant), ts.factory.createObjectLiteralExpression([
2195
2315
  ts.factory.createPropertyAssignment('intent', ann.intent === null
2196
2316
  ? ts.factory.createNull()
2197
2317
  : ts.factory.createStringLiteral(ann.intent)),
2198
2318
  ts.factory.createPropertyAssignment('alwaysAffordable', ann.alwaysAffordable ? ts.factory.createTrue() : ts.factory.createFalse()),
2199
2319
  ts.factory.createPropertyAssignment('requiresConfirm', ann.requiresConfirm ? ts.factory.createTrue() : ts.factory.createFalse()),
2200
- ts.factory.createPropertyAssignment('humanOnly', ann.humanOnly ? ts.factory.createTrue() : ts.factory.createFalse()),
2320
+ ts.factory.createPropertyAssignment('dispatchMode', ts.factory.createStringLiteral(ann.dispatchMode)),
2201
2321
  ], true)));
2202
2322
  }
2203
2323
  return ts.factory.createObjectLiteralExpression(props, true);
@@ -2254,15 +2374,12 @@ function injectEffectSchema(node, schema, f) {
2254
2374
  const variantProps = [];
2255
2375
  for (const [variant, fields] of Object.entries(schema.variants)) {
2256
2376
  const fieldProps = [];
2257
- for (const [field, type] of Object.entries(fields)) {
2258
- if (typeof type === 'string') {
2259
- fieldProps.push(f.createPropertyAssignment(field, f.createStringLiteral(type)));
2260
- }
2261
- else {
2262
- fieldProps.push(f.createPropertyAssignment(field, f.createObjectLiteralExpression([
2263
- f.createPropertyAssignment('enum', f.createArrayLiteralExpression(type.enum.map((v) => f.createStringLiteral(v)))),
2264
- ])));
2265
- }
2377
+ for (const [field, descriptor] of Object.entries(fields)) {
2378
+ // Effects share the same descriptor pipeline as messages — they
2379
+ // can carry @should/optional annotations too, even though typical
2380
+ // Effect unions don't need them. Reusing the helper keeps both
2381
+ // schemas's wire formats consistent.
2382
+ fieldProps.push(f.createPropertyAssignment(f.createStringLiteral(field), buildFieldDescriptorExpr(descriptor, f)));
2266
2383
  }
2267
2384
  variantProps.push(f.createPropertyAssignment(f.createStringLiteral(variant), f.createObjectLiteralExpression(fieldProps)));
2268
2385
  }
@@ -2277,29 +2394,6 @@ function injectEffectSchema(node, schema, f) {
2277
2394
  ...node.arguments.slice(1),
2278
2395
  ]);
2279
2396
  }
2280
- function injectBindingDescriptors(node, descs, f) {
2281
- const configArg = node.arguments[0];
2282
- if (!configArg || !ts.isObjectLiteralExpression(configArg))
2283
- return node;
2284
- // Don't inject if already present
2285
- for (const prop of configArg.properties) {
2286
- if (ts.isPropertyAssignment(prop) &&
2287
- ts.isIdentifier(prop.name) &&
2288
- prop.name.text === '__bindingDescriptors') {
2289
- return node;
2290
- }
2291
- }
2292
- const descsProp = f.createPropertyAssignment('__bindingDescriptors', bindingDescriptorsToArrayLiteral(descs));
2293
- const newConfig = f.createObjectLiteralExpression([...configArg.properties, descsProp], true);
2294
- return f.createCallExpression(node.expression, node.typeArguments, [
2295
- newConfig,
2296
- ...node.arguments.slice(1),
2297
- ]);
2298
- }
2299
- function bindingDescriptorsToArrayLiteral(descs) {
2300
- const entries = descs.map((d) => ts.factory.createObjectLiteralExpression([ts.factory.createPropertyAssignment('variant', ts.factory.createStringLiteral(d.variant))], false));
2301
- return ts.factory.createArrayLiteralExpression(entries, true);
2302
- }
2303
2397
  // ── Per-item accessor detection ──────────────────────────────────
2304
2398
  // ── Item selector deduplication ──────────────────────────────────
2305
2399
  /**
@@ -3304,10 +3398,10 @@ function resolveChain(node, paramName) {
3304
3398
  * - `export function foo() {}` and `export class Foo {}` — rewritten
3305
3399
  * as stubs but the caller may be surprised that `foo` and `Foo` are
3306
3400
  * ComponentDef-shaped objects during SSR.
3307
- * - `export { a, b } from './other'` — re-export forms are not
3401
+ * - `export { a, b } from './other.js'` — re-export forms are not
3308
3402
  * detected; they pass through and will still pull `./other` into
3309
3403
  * the SSR graph.
3310
- * - `export * from './other'` — same as above.
3404
+ * - `export * from './other.js'` — same as above.
3311
3405
  * - `export type ...` — type exports are erased by TS so nothing to
3312
3406
  * stub; left untouched.
3313
3407
  */
@@ -3365,7 +3459,7 @@ export function transformUseClientSsr(source, _filename) {
3365
3459
  hasDefaultExport = true;
3366
3460
  continue;
3367
3461
  }
3368
- // `export { a, b }` / `export { a } from './x'` / `export * from './x'`
3462
+ // `export { a, b }` / `export { a } from './x.js'` / `export * from './x.js'`
3369
3463
  if (ts.isExportDeclaration(stmt)) {
3370
3464
  if (stmt.moduleSpecifier) {
3371
3465
  warnings.push("[llui/use-client] `export ... from '...'` re-export forms still pull the source module into the SSR graph and bypass stubbing. Either drop the re-export or move the 'use client' directive to the source module.");