@abide/abide 0.46.0 → 0.47.0

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 (185) hide show
  1. package/AGENTS.md +365 -320
  2. package/CHANGELOG.md +80 -0
  3. package/README.md +203 -168
  4. package/package.json +12 -8
  5. package/src/abideLsp.ts +11 -12
  6. package/src/abideResolverPlugin.ts +9 -4
  7. package/src/lib/bundle/disconnected.abide +3 -0
  8. package/src/lib/cli/printCommandHelp.ts +43 -0
  9. package/src/lib/cli/printSessionHelp.ts +2 -1
  10. package/src/lib/cli/{printHelp.ts → printTopLevelHelp.ts} +3 -40
  11. package/src/lib/cli/runCli.ts +2 -1
  12. package/src/lib/mcp/buildPrompts.ts +25 -0
  13. package/src/lib/mcp/dispatchMcpRequest.ts +8 -6
  14. package/src/lib/mcp/mcpResourceServerSlot.ts +5 -10
  15. package/src/lib/mcp/mcpSurface.ts +13 -252
  16. package/src/lib/mcp/mcpTools.ts +187 -0
  17. package/src/lib/mcp/renderPrompt.ts +25 -0
  18. package/src/lib/mcp/types/McpSurface.ts +15 -0
  19. package/src/lib/mcp/types/PromptDescriptor.ts +5 -0
  20. package/src/lib/mcp/types/PromptMessage.ts +2 -0
  21. package/src/lib/mcp/types/ToolDescriptor.ts +7 -0
  22. package/src/lib/mcp/types/ToolResult.ts +2 -0
  23. package/src/lib/server/agent.ts +1 -1
  24. package/src/lib/server/jsonl.ts +2 -2
  25. package/src/lib/server/rpc/defineRpc.ts +5 -0
  26. package/src/lib/server/runtime/containsTraversal.ts +21 -17
  27. package/src/lib/server/runtime/createAppAssetServer.ts +11 -16
  28. package/src/lib/server/runtime/createServer.ts +101 -71
  29. package/src/lib/server/runtime/createUiPageRenderer.ts +23 -4
  30. package/src/lib/server/runtime/devClientFingerprint.ts +3 -3
  31. package/src/lib/server/runtime/devHotModuleResponse.ts +2 -1
  32. package/src/lib/server/runtime/finalizeResponse.ts +32 -0
  33. package/src/lib/server/runtime/snapshotEntryFromCache.ts +8 -17
  34. package/src/lib/server/socket.ts +1 -1
  35. package/src/lib/server/sockets/createSocketDispatcher.ts +28 -5
  36. package/src/lib/server/sockets/defineSocket.ts +26 -7
  37. package/src/lib/server/sockets/types/SocketRegistryEntry.ts +1 -1
  38. package/src/lib/server/sockets/types/SocketRoutes.ts +1 -1
  39. package/src/lib/server/sse.ts +2 -2
  40. package/src/lib/shared/DEFER.ts +8 -0
  41. package/src/lib/shared/activeCacheStore.ts +2 -2
  42. package/src/lib/shared/activePage.ts +2 -2
  43. package/src/lib/shared/attachRpcSelectorMethods.ts +42 -0
  44. package/src/lib/shared/attachSocketSelectorMethods.ts +34 -0
  45. package/src/lib/shared/basePath.ts +2 -2
  46. package/src/lib/shared/baseSlot.ts +6 -5
  47. package/src/lib/shared/bodyValueForKind.ts +1 -2
  48. package/src/lib/shared/buildSocketOverChannel.ts +40 -4
  49. package/src/lib/shared/cache.ts +477 -110
  50. package/src/lib/shared/cacheEntryFromSnapshot.ts +3 -22
  51. package/src/lib/shared/cacheStoreSlot.ts +9 -5
  52. package/src/lib/{server/runtime → shared}/createReachable.ts +2 -2
  53. package/src/lib/shared/createRemoteFunction.ts +58 -9
  54. package/src/lib/shared/createResolverSlot.ts +24 -31
  55. package/src/lib/shared/createSocketSubRegistry.ts +2 -2
  56. package/src/lib/shared/decodeRefJson.ts +4 -2
  57. package/src/lib/shared/decodeResponse.ts +8 -6
  58. package/src/lib/shared/done.ts +14 -0
  59. package/src/lib/shared/encodeRefJson.ts +4 -2
  60. package/src/lib/shared/globalCacheStoreSlot.ts +9 -6
  61. package/src/lib/shared/hydratingSlot.ts +12 -0
  62. package/src/lib/shared/isLayoutFile.ts +12 -0
  63. package/src/lib/shared/keyForRemoteCall.ts +2 -1
  64. package/src/lib/shared/keyPrefixForRemote.ts +12 -0
  65. package/src/lib/shared/matchRoute.ts +175 -0
  66. package/src/lib/shared/normalizePathname.ts +11 -0
  67. package/src/lib/shared/pageSlot.ts +14 -5
  68. package/src/lib/shared/pageUrlForFile.ts +3 -3
  69. package/src/lib/shared/parseRouteSegments.ts +16 -7
  70. package/src/lib/shared/patch.ts +41 -0
  71. package/src/lib/shared/peek.ts +35 -0
  72. package/src/lib/shared/prepareRemoteExport.ts +40 -0
  73. package/src/lib/shared/prepareRpcModule.ts +98 -16
  74. package/src/lib/shared/prepareSocketModule.ts +11 -15
  75. package/src/lib/shared/queryStringFromArgs.ts +11 -0
  76. package/src/lib/shared/reachable.ts +102 -0
  77. package/src/lib/shared/refresh.ts +22 -0
  78. package/src/lib/shared/requestScopeSlot.ts +10 -7
  79. package/src/lib/shared/routeParamsShape.ts +9 -9
  80. package/src/lib/shared/rpcErrorRegistry.ts +69 -0
  81. package/src/lib/shared/selectorPrefix.ts +3 -10
  82. package/src/lib/shared/setOwnProperty.ts +19 -0
  83. package/src/lib/shared/snippet.ts +1 -1
  84. package/src/lib/shared/subscribableProbes.ts +111 -0
  85. package/src/lib/shared/tailProbeSlot.ts +8 -1
  86. package/src/lib/shared/types/CacheEntry.ts +9 -15
  87. package/src/lib/shared/types/CacheOptions.ts +8 -0
  88. package/src/lib/shared/types/CacheSnapshotEntry.ts +0 -5
  89. package/src/lib/shared/types/RemoteCallable.ts +25 -7
  90. package/src/lib/shared/types/RemoteFunction.ts +48 -13
  91. package/src/lib/shared/types/ResolverSlot.ts +7 -4
  92. package/src/lib/shared/types/RpcError.ts +26 -0
  93. package/src/lib/shared/types/SmartReadOptions.ts +32 -0
  94. package/src/lib/shared/types/Socket.ts +54 -0
  95. package/src/lib/shared/types/SsrBootState.ts +27 -0
  96. package/src/lib/shared/types/SsrPayload.ts +21 -0
  97. package/src/lib/shared/types/Subscribable.ts +13 -13
  98. package/src/lib/shared/types/TailHooks.ts +2 -1
  99. package/src/lib/shared/url.ts +29 -14
  100. package/src/lib/shared/wakeHydrationPeeks.ts +20 -0
  101. package/src/lib/shared/writeTestSocketsDts.ts +1 -1
  102. package/src/lib/test/createScriptedSurface.ts +1 -1
  103. package/src/lib/test/createTestApp.ts +2 -2
  104. package/src/lib/test/createTestSocketChannel.ts +3 -3
  105. package/src/lib/ui/compile/REACTIVE_CALLEES.ts +5 -6
  106. package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +10 -7
  107. package/src/lib/ui/compile/abideUiPlugin.ts +2 -2
  108. package/src/lib/ui/compile/analyzeComponent.ts +28 -6
  109. package/src/lib/ui/compile/compileModule.ts +137 -11
  110. package/src/lib/ui/compile/compileShadow.ts +101 -84
  111. package/src/lib/ui/compile/desugarSignals.ts +120 -107
  112. package/src/lib/ui/compile/generateBuild.ts +48 -16
  113. package/src/lib/ui/compile/generateSSR.ts +117 -14
  114. package/src/lib/ui/compile/identifierReferencePattern.ts +13 -0
  115. package/src/lib/ui/compile/lowerContext.ts +10 -4
  116. package/src/lib/ui/compile/lowerScript.ts +72 -5
  117. package/src/lib/ui/compile/parseTemplate.ts +1 -14
  118. package/src/lib/ui/compile/prepareNestedScript.ts +31 -17
  119. package/src/lib/ui/compile/resolveReactiveExport.ts +95 -0
  120. package/src/lib/ui/compile/signalCallee.ts +24 -0
  121. package/src/lib/ui/compile/stripEffects.ts +14 -11
  122. package/src/lib/ui/compile/types/AnalyzedComponent.ts +5 -0
  123. package/src/lib/ui/compile/types/TemplateNode.ts +0 -5
  124. package/src/lib/ui/dom/appendStatic.ts +9 -1
  125. package/src/lib/ui/dom/appendText.ts +15 -3
  126. package/src/lib/ui/dom/assertClaimedText.ts +20 -0
  127. package/src/lib/ui/dom/awaitBlock.ts +19 -83
  128. package/src/lib/ui/dom/bindSelectValue.ts +48 -0
  129. package/src/lib/ui/dom/each.ts +12 -1
  130. package/src/lib/ui/dom/hydrate.ts +10 -0
  131. package/src/lib/ui/dom/mountChild.ts +2 -5
  132. package/src/lib/ui/dom/mountRange.ts +0 -75
  133. package/src/lib/ui/effect.ts +4 -3
  134. package/src/lib/ui/installHotBridge.ts +4 -2
  135. package/src/lib/ui/remoteProxy.ts +18 -2
  136. package/src/lib/ui/renderToStream.ts +9 -13
  137. package/src/lib/ui/resumeSeedScript.ts +2 -2
  138. package/src/lib/ui/router.ts +21 -2
  139. package/src/lib/ui/runtime/RESUME.ts +0 -6
  140. package/src/lib/ui/runtime/ambientScopeBacking.ts +1 -1
  141. package/src/lib/ui/runtime/types/SsrRender.ts +3 -4
  142. package/src/lib/ui/scope.ts +6 -3
  143. package/src/lib/ui/seedBootState.ts +53 -0
  144. package/src/lib/ui/socketChannel.ts +2 -2
  145. package/src/lib/ui/socketProxy.ts +9 -3
  146. package/src/lib/ui/startClient.ts +16 -30
  147. package/src/lib/ui/state.ts +44 -13
  148. package/src/lib/ui/sync.ts +11 -5
  149. package/src/lib/ui/tryEncodeResume.ts +2 -5
  150. package/src/lib/ui/types/Scope.ts +14 -10
  151. package/src/lib/ui/watch.ts +140 -0
  152. package/src/serverEntry.ts +6 -6
  153. package/template/CLAUDE.md +1 -1
  154. package/template/package.json +1 -1
  155. package/template/src/server/rpc/getHello.ts +3 -3
  156. package/template/src/ui/pages/page.abide +2 -2
  157. package/template/test/app.test.ts +1 -1
  158. package/src/lib/server/reachable.ts +0 -45
  159. package/src/lib/server/sockets/types/Socket.ts +0 -23
  160. package/src/lib/shared/CACHE_WRAPPED.ts +0 -9
  161. package/src/lib/shared/DEFER_MIN_ARRAY_LENGTH.ts +0 -14
  162. package/src/lib/shared/baseResolver.ts +0 -10
  163. package/src/lib/shared/cacheKeyOf.ts +0 -7
  164. package/src/lib/shared/cacheKeyStore.ts +0 -8
  165. package/src/lib/shared/cacheStoreResolver.ts +0 -12
  166. package/src/lib/shared/globalCacheStoreResolver.ts +0 -12
  167. package/src/lib/shared/pageResolver.ts +0 -16
  168. package/src/lib/shared/recordCacheKey.ts +0 -6
  169. package/src/lib/shared/requestScopeResolver.ts +0 -12
  170. package/src/lib/shared/setBaseResolver.ts +0 -4
  171. package/src/lib/shared/setCacheStoreResolver.ts +0 -4
  172. package/src/lib/shared/setGlobalCacheStoreResolver.ts +0 -4
  173. package/src/lib/shared/setPageResolver.ts +0 -4
  174. package/src/lib/shared/setRequestScopeResolver.ts +0 -4
  175. package/src/lib/shared/toBunRoutePattern.ts +0 -28
  176. package/src/lib/shared/types/TailOptions.ts +0 -10
  177. package/src/lib/ui/deferResume.ts +0 -36
  178. package/src/lib/ui/dom/firstElementBetween.ts +0 -14
  179. package/src/lib/ui/matchRoute.ts +0 -106
  180. package/src/lib/ui/runtime/scheduleWake.ts +0 -28
  181. package/src/lib/ui/runtime/whenIdle.ts +0 -21
  182. package/src/lib/ui/runtime/whenVisible.ts +0 -105
  183. package/src/lib/ui/tail.ts +0 -324
  184. /package/src/lib/{server/sockets → shared}/types/SocketClientFrame.ts +0 -0
  185. /package/src/lib/{server/sockets → shared}/types/SocketServerFrame.ts +0 -0
@@ -1,44 +1,58 @@
1
1
  import ts from 'typescript'
2
2
  import { ABIDE_PACKAGE_NAME } from '../../shared/ABIDE_PACKAGE_NAME.ts'
3
3
  import { parseTemplate } from './parseTemplate.ts'
4
- import { REACTIVE_CALLEES } from './REACTIVE_CALLEES.ts'
4
+ import {
5
+ NESTED_REACTIVE_BINDINGS,
6
+ type ReactiveImportBindings,
7
+ reactiveImportBindings,
8
+ resolveReactiveExport,
9
+ } from './resolveReactiveExport.ts'
10
+ import { signalCallee } from './signalCallee.ts'
5
11
  import type { CompiledShadow, ShadowDiagnostic, ShadowMapping } from './types/CompiledShadow.ts'
6
12
  import type { TemplateNode } from './types/TemplateNode.ts'
7
13
 
8
14
  /*
9
- Runtime helpers published as package subpaths solely so generated component code
10
- can import them; an author must reach them through their namespaced surface
11
- instead. The module resolver can't tell codegen from user source (same specifier),
12
- so the ban can only live in the check map each forbidden specifier to the
13
- guidance shown when an author imports it.
15
+ Framework callables the `.abide` loader injects into a component's scope. `snippet`
16
+ and `scope` keep their real published types via imports so author calls type-check —
17
+ `scope()` is the internal lowering host (a captured handle `const s = scope()` + its
18
+ capability calls type-check against the real `Scope`; generated code targets it too).
19
+ `state`/`linked`/`computed`/`effect` are the imported reactive surface; when the author
20
+ imports one its own binding provides the type, so the ambient fallback for that name is
21
+ OMITTED (see `shadowPreamble`) to avoid a duplicate identifier. Otherwise
22
+ `state`/`linked`/`computed` are declared ambiently and `effect` is imported as a fallback
23
+ for a bare/nested use the top-level rewrite doesn't project (unused when projected — fine,
24
+ the shadow disables noUnusedLocals). `props` is the prop reader — destructured
25
+ (`const { a = 1 } = props<Shape>()`); declared returning its type argument (default
26
+ `Record<string, any>`) so each binding inherits its prop type and its `= default` narrows.
14
27
  */
15
- const INTERNAL_ONLY_IMPORTS: Record<string, string> = {
16
- [`${ABIDE_PACKAGE_NAME}/ui/effect`]:
17
- '`effect` is compiler-internal author it as `scope().effect(...)`',
28
+ function shadowPreamble(importedReactives: ReadonlySet<string>): string {
29
+ /* Omit the ambient fallback for a primitive the author imports — its own binding is
30
+ the type, and a second declaration would be a duplicate-identifier error. */
31
+ const lines = [
32
+ importedReactives.has('effect')
33
+ ? undefined
34
+ : `import { effect } from '${ABIDE_PACKAGE_NAME}/ui/effect'`,
35
+ importedReactives.has('watch')
36
+ ? undefined
37
+ : `import { watch } from '${ABIDE_PACKAGE_NAME}/ui/watch'`,
38
+ `import { snippet } from '${ABIDE_PACKAGE_NAME}/shared/snippet'`,
39
+ `import { scope } from '${ABIDE_PACKAGE_NAME}/ui/currentScope'`,
40
+ importedReactives.has('state')
41
+ ? undefined
42
+ : 'declare function state<T>(initial?: T, transform?: (next: T, previous: T) => T): { value: T }',
43
+ importedReactives.has('linked')
44
+ ? undefined
45
+ : 'declare function linked<T>(seed: () => T, transform?: (next: T, previous: T) => T): { value: T }',
46
+ importedReactives.has('computed')
47
+ ? undefined
48
+ : 'declare function computed<T>(compute: () => T): { readonly value: T }',
49
+ 'declare const children: (() => void) | undefined',
50
+ /* `effect` is defined either way — the preamble import or the author's own import. */
51
+ 'void [effect, snippet, scope]',
52
+ ]
53
+ return `${lines.filter((line) => line !== undefined).join('\n')}\n`
18
54
  }
19
55
 
20
- /*
21
- Framework callables the `.abide` loader injects into a component's scope. `effect`,
22
- `snippet`, and `scope` keep their real published types via imports so author
23
- calls type-check — `scope()` is the authored reactive surface (`scope().state(...)` /
24
- `.computed(...)` / `.undo()` …), so it must resolve like any import. `state`/`linked`/
25
- `computed` are ALSO declared ambiently as a fallback for the rare bare/nested use the
26
- top-level rewrite doesn't project (their top-level declarations become value types, so
27
- these are normally unused — fine, the shadow disables noUnusedLocals). `props` is the prop
28
- reader — destructured (`const { a = 1 } = props<Shape>()`); declared returning its type
29
- argument (default `Record<string, any>`) so each binding inherits its prop type and its
30
- `= default` narrows.
31
- */
32
- const SHADOW_PREAMBLE = `import { effect } from '${ABIDE_PACKAGE_NAME}/ui/effect'
33
- import { snippet } from '${ABIDE_PACKAGE_NAME}/shared/snippet'
34
- import { scope } from '${ABIDE_PACKAGE_NAME}/ui/scope'
35
- declare function state<T>(initial?: T, transform?: (next: T, previous: T) => T): { value: T }
36
- declare function linked<T>(seed: () => T, transform?: (next: T, previous: T) => T): { value: T }
37
- declare function computed<T>(compute: () => T): { readonly value: T }
38
- declare const children: (() => void) | undefined
39
- void [effect, snippet, scope]
40
- `
41
-
42
56
  /*
43
57
  Compiles a `.abide` component into its type-checking shadow — a synthetic TS
44
58
  module that reconstructs the author scope with value types and references every
@@ -62,11 +76,11 @@ export function compileShadow(source: string, propsType = 'Record<string, any>')
62
76
  const scriptStart = leadingScript ? source.indexOf('>', leadingScript.index) + 1 : 0
63
77
  const templateStart = leadingScript ? (leadingScript.index ?? 0) + leadingScript[0].length : 0
64
78
 
65
- const { imports, types, scope, propsShapes, diagnostics } = analyzeScript(
79
+ const { imports, types, scope, propsShapes, diagnostics, importedReactives } = analyzeScript(
66
80
  scriptBody,
67
81
  scriptStart,
68
82
  )
69
- builder.raw(SHADOW_PREAMBLE)
83
+ builder.raw(shadowPreamble(importedReactives))
70
84
  /* `props()` defaults to the file's type — a page/layout's route param shape, else
71
85
  `Record<string, any>` — so `const { id } = props()` infers from the route. */
72
86
  builder.raw(`declare function props<T = ${propsType}>(): T\n`)
@@ -80,6 +94,17 @@ export function compileShadow(source: string, propsType = 'Record<string, any>')
80
94
  for (const line of types) {
81
95
  builder.flush(line)
82
96
  }
97
+ /* The author's scope (value consts, functions, reactive projections, the `props()`
98
+ destructure) is hoisted to module scope — above `__Props` — for the same reason the
99
+ types are: a prop annotation can then reference a value const by `typeof`
100
+ (`size: keyof typeof sizes`), which fails when the const sits in the function body and
101
+ `__Props` at module scope can't see it. Narrowing is unaffected: a template's guard and
102
+ read are both in the render function below, so control-flow narrowing flows regardless
103
+ of where the binding is declared, and reactive projections are never rebound (writes go
104
+ through `.value`), so they narrow like consts even into nested handlers. */
105
+ for (const line of scope) {
106
+ builder.flush(line)
107
+ }
83
108
  /* `__Props` is the parent-facing prop shape: each `props<Shape>()` destructure
84
109
  contributes its whole `Shape` (intersected if there's more than one), or an empty
85
110
  object for a component that reads no props. */
@@ -94,9 +119,6 @@ export function compileShadow(source: string, propsType = 'Record<string, any>')
94
119
  builder.raw('export default async function (__props: __Props): Promise<void> {\n')
95
120
  /* Reference props so an all-optional bag with no reads doesn't read as unused. */
96
121
  builder.raw('void __props;\n')
97
- for (const line of scope) {
98
- builder.flush(line)
99
- }
100
122
  const templateNodes = parseTemplate(source.slice(templateStart), templateStart).nodes
101
123
  /* Nested `<script>` blocks inline into the synchronous `build()` too, so a top-level
102
124
  await in one is the same build-breaker — flag it, mapped via the node's body offset. */
@@ -184,6 +206,9 @@ type ScriptAnalysis = {
184
206
  scope: ScopeLine[]
185
207
  propsShapes: string[]
186
208
  diagnostics: ShadowDiagnostic[]
209
+ /* The reactive primitives the author imports (`state`/`effect`), so the preamble
210
+ omits the ambient fallback for each and avoids a duplicate-identifier error. */
211
+ importedReactives: Set<string>
187
212
  }
188
213
 
189
214
  /* Pushes a diagnostic for every author binding whose name starts with the reserved `$$`
@@ -296,9 +321,14 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
296
321
  const propsShapes: string[] = []
297
322
  const diagnostics: ShadowDiagnostic[] = []
298
323
  if (scriptBody.trim() === '') {
299
- return { imports, types, scope, propsShapes, diagnostics }
324
+ return { imports, types, scope, propsShapes, diagnostics, importedReactives: new Set() }
300
325
  }
301
326
  const file = ts.createSourceFile('script.ts', scriptBody, ts.ScriptTarget.Latest, true)
327
+ /* The author's reactive import bindings (alias-safe) — recognition source for
328
+ `state`/`state.linked`/`state.computed`/`effect`, and the set of imported primitives
329
+ the preamble omits its ambient fallback for. */
330
+ const bindings = reactiveImportBindings(file)
331
+ const importedReactives = new Set(bindings.direct.values())
302
332
  /* The `$$` prefix is reserved for the compiler's injected runtime (`$$each`, `$$model`,
303
333
  `$$scope`, …), so an author binding may not start with it — that's the contract that
304
334
  lets a user freely name a variable after any helper. Flag every such declaration. */
@@ -317,21 +347,9 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
317
347
 
318
348
  for (const statement of file.statements) {
319
349
  if (ts.isImportDeclaration(statement)) {
320
- const specifier = ts.isStringLiteral(statement.moduleSpecifier)
321
- ? statement.moduleSpecifier.text
322
- : undefined
323
- const banned = specifier !== undefined ? INTERNAL_ONLY_IMPORTS[specifier] : undefined
324
- if (banned !== undefined) {
325
- /* Flag and drop it — leaving it out keeps `effect` resolving via the
326
- preamble (no duplicate-identifier noise on top of our message). */
327
- diagnostics.push({
328
- start: scriptStart + statement.getStart(file),
329
- length: statement.getEnd() - statement.getStart(file),
330
- message: banned,
331
- })
332
- continue
333
- }
334
- /* Emit verbatim with a span so hover/go-to resolve on the imported names. */
350
+ /* Emit verbatim with a span so hover/go-to resolve on the imported names.
351
+ `abide/ui/state` / `abide/ui/effect` are ordinary author imports now — the
352
+ preamble drops its ambient fallback for whichever names are imported. */
335
353
  imports.push({ text: verbatim(statement), segments: [span(statement, 0)] })
336
354
  continue
337
355
  }
@@ -340,17 +358,17 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
340
358
  types.push({ text: verbatim(statement), segments: [span(statement, 0)] })
341
359
  continue
342
360
  }
343
- const reactive = reactiveDeclarations(statement)
361
+ const reactive = reactiveDeclarations(statement, bindings)
344
362
  if (reactive === undefined) {
345
363
  /* Plain statement (function, const, expression) — emit verbatim, mapped. */
346
364
  scope.push({ text: verbatim(statement), segments: [span(statement, 0)] })
347
365
  continue
348
366
  }
349
367
  for (const declaration of reactive) {
350
- scope.push(scopeLineFor(declaration, propsShapes, verbatim, span))
368
+ scope.push(scopeLineFor(declaration, propsShapes, verbatim, span, bindings))
351
369
  }
352
370
  }
353
- return { imports, types, scope, propsShapes, diagnostics }
371
+ return { imports, types, scope, propsShapes, diagnostics, importedReactives }
354
372
  }
355
373
 
356
374
  /* Value-projects a nested control-flow `<script>` body the way `analyzeScript`
@@ -360,16 +378,20 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
360
378
  reads a nested signal as its value type instead of the raw `State`/`Computed`. */
361
379
  function projectNestedScript(code: string): string {
362
380
  const file = ts.createSourceFile('nested.ts', code, ts.ScriptTarget.Latest, true)
381
+ /* A nested script can't import — it inherits the module-scope surface by canonical name. */
382
+ const bindings = NESTED_REACTIVE_BINDINGS
363
383
  const verbatim = (node: ts.Node): string => code.slice(node.getStart(file), node.getEnd())
364
384
  /* No mapping: a zero-length segment the caller drops. */
365
385
  const span = (): ShadowMapping => ({ shadowStart: 0, sourceStart: 0, length: 0 })
366
386
  return file.statements
367
387
  .flatMap((statement) => {
368
- const reactive = reactiveDeclarations(statement)
388
+ const reactive = reactiveDeclarations(statement, bindings)
369
389
  if (reactive === undefined) {
370
390
  return [verbatim(statement)]
371
391
  }
372
- return reactive.map((declaration) => scopeLineFor(declaration, [], verbatim, span).text)
392
+ return reactive.map(
393
+ (declaration) => scopeLineFor(declaration, [], verbatim, span, bindings).text,
394
+ )
373
395
  })
374
396
  .join('\n')
375
397
  }
@@ -377,32 +399,20 @@ function projectNestedScript(code: string): string {
377
399
  /* The `state`/`computed`/`linked`/`props()` declarations in a variable statement, or
378
400
  undefined if it isn't one declaring them (so the caller emits it verbatim). A statement
379
401
  mixing reactive and plain declarations is rare; treated as all-verbatim. */
380
- function reactiveDeclarations(statement: ts.Statement): ts.VariableDeclaration[] | undefined {
402
+ function reactiveDeclarations(
403
+ statement: ts.Statement,
404
+ bindings: ReactiveImportBindings,
405
+ ): ts.VariableDeclaration[] | undefined {
381
406
  if (!ts.isVariableStatement(statement)) {
382
407
  return undefined
383
408
  }
384
409
  const declarations = statement.declarationList.declarations
385
- const reactive = declarations.filter((declaration) => signalCallee(declaration) !== undefined)
410
+ const reactive = declarations.filter(
411
+ (declaration) => signalCallee(declaration, bindings) !== undefined,
412
+ )
386
413
  return reactive.length === declarations.length && reactive.length > 0 ? reactive : undefined
387
414
  }
388
415
 
389
- /* The callee name of a `NAME = state(...)` / `linked(...)` / `computed(...)` /
390
- `props()` decl — bare or the explicit scope form (`scope().state(...)` / `c.state(...)`),
391
- receiver-agnostic (the method name marks it reactive). */
392
- function signalCallee(declaration: ts.VariableDeclaration): string | undefined {
393
- const initializer = declaration.initializer
394
- if (initializer === undefined || !ts.isCallExpression(initializer)) {
395
- return undefined
396
- }
397
- const callee = initializer.expression
398
- const name = ts.isIdentifier(callee)
399
- ? callee.text
400
- : ts.isPropertyAccessExpression(callee)
401
- ? callee.name.text
402
- : undefined
403
- return name !== undefined && REACTIVE_CALLEES.has(name) ? name : undefined
404
- }
405
-
406
416
  /* Builds one scope line for a reactive declaration, projecting it to its value
407
417
  type. A `props()` destructure contributes its whole shape (pushed into `propsShapes`). */
408
418
  function scopeLineFor(
@@ -410,13 +420,14 @@ function scopeLineFor(
410
420
  propsShapes: string[],
411
421
  verbatim: (node: ts.Node) => string,
412
422
  span: (node: ts.Node, prefixLength: number) => ShadowMapping,
423
+ bindings: ReactiveImportBindings,
413
424
  ): ScopeLine {
414
425
  const name = ts.isIdentifier(declaration.name) ? declaration.name.text : '_'
415
426
  const call = declaration.initializer as ts.CallExpression
416
- /* Bare callee (`state`) or member callee (`scope().state` / `c.state`). */
417
- const callee = ts.isPropertyAccessExpression(call.expression)
418
- ? call.expression.name.text
419
- : (call.expression as ts.Identifier).text
427
+ /* The CANONICAL primitive drives value-projection resolved purely from the import
428
+ binding (so an aliased `s(0)` / `state.computed(...)` projects correctly).
429
+ `verbatim(call.expression)` below keeps the AUTHOR's text for the trailing hover ref. */
430
+ const callee = resolveReactiveExport(call.expression, bindings)
420
431
  if (callee === 'props') {
421
432
  /* `const {…} = props<Shape>()`: the type arg (default `Record<string, any>`)
422
433
  IS the parent-facing prop shape, and the destructure projects verbatim against
@@ -737,8 +748,10 @@ function emitNode(node: TemplateNode, builder: Builder): void {
737
748
  emitNodes(node.children, builder)
738
749
  return
739
750
  case 'branch':
740
- /* Reached only for a stray branch outside an await (none today); the await
741
- handler binds resolved/error types for its own branch children. */
751
+ /* Reached for a `{#try}`'s `catch`/`finally`: the `try` handler emits its
752
+ children directly (guarded content + these branch nodes), so a `{:catch err}`
753
+ binds its error here as `any` (statically unknowable). The `await`/`if`/`switch`
754
+ handlers consume their own branches inline and never route through this case. */
742
755
  builder.raw('{\n')
743
756
  if (node.as !== undefined) {
744
757
  builder.raw(`const ${node.as} = undefined as any;\n`)
@@ -759,9 +772,13 @@ function emitNode(node: TemplateNode, builder: Builder): void {
759
772
  if (node.params !== undefined) {
760
773
  builder.mapped(node.params, node.loc)
761
774
  }
762
- builder.raw(') => {\n')
775
+ /* Wrap the body in `snippet(() => )` so the shadow types the snippet as
776
+ `(args) => Snippet<…>` — mirroring the runtime lowering (which returns
777
+ `$$snippet(($host) => …)`) so a snippet passed to a `Snippet`-typed prop
778
+ type-checks instead of reading as `() => void`. */
779
+ builder.raw(') => snippet(() => {\n')
763
780
  emitNodes(node.children, builder)
764
- builder.raw('};\n')
781
+ builder.raw('});\n')
765
782
  return
766
783
  case 'script':
767
784
  /* A scoped reactive `<script>`: value-project its reactive declarations to