@abide/abide 0.48.0 → 0.50.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 (360) hide show
  1. package/AGENTS.md +85 -59
  2. package/CHANGELOG.md +216 -0
  3. package/README.md +21 -10
  4. package/package.json +15 -4
  5. package/src/abideLsp.ts +5 -6
  6. package/src/abideResolverPlugin.ts +197 -169
  7. package/src/build.ts +79 -56
  8. package/src/buildCli.ts +3 -5
  9. package/src/buildDisconnected.ts +2 -5
  10. package/src/bundleApp.ts +2 -3
  11. package/src/checkAbide.ts +12 -2
  12. package/src/compile.ts +2 -4
  13. package/src/devEntry.ts +113 -28
  14. package/src/discoveryEntry.ts +3 -2
  15. package/src/lib/bundle/disconnected.abide +69 -73
  16. package/src/lib/bundle/infoPlist.ts +13 -7
  17. package/src/lib/bundle/installDownloads.ts +13 -3
  18. package/src/lib/bundle/installMacMenu.ts +13 -3
  19. package/src/lib/bundle/spawnEmbeddedServer.ts +13 -4
  20. package/src/lib/bundle/waitForServer.ts +6 -1
  21. package/src/lib/cli/parseArgvForRpc.ts +36 -9
  22. package/src/lib/cli/tokenizeArgvFlags.ts +4 -2
  23. package/src/lib/mcp/createMcpResourceServer.ts +13 -2
  24. package/src/lib/mcp/mcpTools.ts +15 -6
  25. package/src/lib/mcp/toolResultFromResponse.ts +40 -12
  26. package/src/lib/server/error.ts +6 -6
  27. package/src/lib/server/json.ts +17 -3
  28. package/src/lib/server/rpc/defineRpc.ts +58 -24
  29. package/src/lib/server/rpc/parseArgs.ts +113 -14
  30. package/src/lib/server/rpc/resolveRpcJsonSchema.ts +28 -0
  31. package/src/lib/server/rpc/runWithRpcTimeout.ts +12 -1
  32. package/src/lib/server/rpc/types/RpcHelper.ts +75 -105
  33. package/src/lib/server/rpc/types/RpcRegistryEntry.ts +24 -9
  34. package/src/lib/server/rpc/validationError.ts +1 -1
  35. package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +0 -15
  36. package/src/lib/server/runtime/buildCacheSnapshot.ts +10 -9
  37. package/src/lib/server/runtime/buildInspectorSurface.ts +6 -4
  38. package/src/lib/server/runtime/buildOpenApiSpec.ts +55 -11
  39. package/src/lib/server/runtime/buildPreloadManifest.ts +12 -10
  40. package/src/lib/server/runtime/createAppAssetServer.ts +18 -15
  41. package/src/lib/server/runtime/createAppRouteResolver.ts +145 -0
  42. package/src/lib/server/runtime/createPlumbingRouter.ts +212 -0
  43. package/src/lib/server/runtime/createRouteDispatcher.ts +4 -10
  44. package/src/lib/server/runtime/createServer.ts +120 -324
  45. package/src/lib/server/runtime/createUiPageRenderer.ts +137 -26
  46. package/src/lib/server/runtime/devClientFingerprint.ts +19 -34
  47. package/src/lib/server/runtime/finalizeResponse.ts +11 -1
  48. package/src/lib/server/runtime/installAmbientScopeStore.ts +30 -9
  49. package/src/lib/server/runtime/logExposedSurfaces.ts +8 -7
  50. package/src/lib/server/runtime/pathStore.ts +15 -0
  51. package/src/lib/server/runtime/registryManifests.ts +1 -1
  52. package/src/lib/server/runtime/renderCellBarrierStore.ts +15 -0
  53. package/src/lib/server/runtime/runWithRequestScope.ts +3 -0
  54. package/src/lib/server/runtime/serializeCacheSnapshot.ts +6 -4
  55. package/src/lib/server/runtime/snapshotEntryFromCache.ts +24 -16
  56. package/src/lib/server/runtime/streamCacheResolutions.ts +14 -7
  57. package/src/lib/server/runtime/streamFromIterator.ts +30 -2
  58. package/src/lib/server/runtime/textResponse.ts +23 -0
  59. package/src/lib/server/runtime/types/DevReloadStamp.ts +6 -10
  60. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
  61. package/src/lib/server/runtime/types/RequestStore.ts +27 -0
  62. package/src/lib/server/runtime/warnUnguardedMcp.ts +15 -5
  63. package/src/lib/server/sockets/createSocketDispatcher.ts +9 -11
  64. package/src/lib/server/sockets/defineSocket.ts +5 -4
  65. package/src/lib/server/sse.ts +5 -1
  66. package/src/lib/shared/ASYNC_CELL.ts +5 -0
  67. package/src/lib/shared/DEV_REBUILD_PATH.ts +6 -0
  68. package/src/lib/shared/HTTP_METHODS.ts +6 -0
  69. package/src/lib/shared/HttpError.ts +1 -5
  70. package/src/lib/shared/MCP_PATH.ts +6 -0
  71. package/src/lib/shared/PROXIED_SERVER_SUBDIRS.ts +15 -0
  72. package/src/lib/shared/REF_JSON_TAGS.ts +13 -1
  73. package/src/lib/shared/RPC_SHIM_GLOBALS.ts +9 -0
  74. package/src/lib/shared/activeCacheStore.ts +1 -0
  75. package/src/lib/shared/activePage.ts +1 -0
  76. package/src/lib/shared/buildArtifact.ts +20 -0
  77. package/src/lib/shared/buildRpcRequest.ts +6 -2
  78. package/src/lib/shared/buildSocketOverChannel.ts +4 -3
  79. package/src/lib/shared/bundleGraphFromMetafile.ts +68 -0
  80. package/src/lib/shared/cache.ts +156 -131
  81. package/src/lib/shared/canonicalJson.ts +24 -1
  82. package/src/lib/shared/changeAffectsClient.ts +12 -7
  83. package/src/lib/shared/createCacheStore.ts +31 -8
  84. package/src/lib/shared/createChannelLog.ts +24 -0
  85. package/src/lib/shared/createLifecycleChannel.ts +7 -1
  86. package/src/lib/shared/createReachable.ts +47 -78
  87. package/src/lib/shared/createRemoteFunction.ts +52 -30
  88. package/src/lib/shared/createRpcServerProgram.ts +820 -0
  89. package/src/lib/shared/decodeRefJson.ts +4 -4
  90. package/src/lib/shared/decodeResponse.ts +2 -2
  91. package/src/lib/shared/decodeWireBody.ts +35 -0
  92. package/src/lib/shared/detectRpcMethod.ts +8 -1
  93. package/src/lib/shared/done.ts +8 -2
  94. package/src/lib/shared/encodeRefJson.ts +4 -4
  95. package/src/lib/shared/encodeWireBody.ts +18 -0
  96. package/src/lib/shared/exitOnBuildFailure.ts +4 -3
  97. package/src/lib/{server/rpc → shared}/fieldErrorsFromIssues.ts +5 -1
  98. package/src/lib/shared/generateDeclarations.ts +72 -0
  99. package/src/lib/shared/hasSeedableRequest.ts +25 -0
  100. package/src/lib/shared/hydrationWindow.ts +53 -0
  101. package/src/lib/shared/isAsyncCell.ts +11 -0
  102. package/src/lib/shared/isAsyncIterable.ts +8 -0
  103. package/src/lib/shared/isSubscribable.ts +3 -3
  104. package/src/lib/shared/isThenable.ts +9 -0
  105. package/src/lib/shared/jsonSchemaForType.ts +258 -0
  106. package/src/lib/shared/lenientDecode.ts +15 -0
  107. package/src/lib/shared/loadProjectTsConfig.ts +28 -0
  108. package/src/lib/shared/markFrameworkSourcesIgnored.ts +1 -1
  109. package/src/lib/shared/matchRoute.ts +4 -14
  110. package/src/lib/shared/parseEnv.ts +17 -6
  111. package/src/lib/shared/peek.ts +14 -0
  112. package/src/lib/shared/pending.ts +9 -6
  113. package/src/lib/shared/pendingAsyncCellsSlot.ts +13 -0
  114. package/src/lib/shared/prepareRpcModule.ts +91 -93
  115. package/src/lib/shared/prepareSocketModule.ts +3 -2
  116. package/src/lib/shared/probeRegistries.ts +5 -18
  117. package/src/lib/shared/reachable.ts +7 -10
  118. package/src/lib/shared/refresh.ts +12 -2
  119. package/src/lib/shared/refreshing.ts +8 -2
  120. package/src/lib/shared/resolvedCellsSlot.ts +13 -0
  121. package/src/lib/shared/reviveWireField.ts +49 -0
  122. package/src/lib/shared/reviveWireOutput.ts +36 -0
  123. package/src/lib/shared/rpcServerForRoot.ts +25 -0
  124. package/src/lib/shared/scanPages.ts +25 -0
  125. package/src/lib/shared/serializeEnv.ts +18 -6
  126. package/src/lib/shared/snapshotShippable.ts +8 -7
  127. package/src/lib/shared/snippet.ts +11 -6
  128. package/src/lib/shared/streamedCellsSlot.ts +14 -0
  129. package/src/lib/shared/subscribableFromResponse.ts +4 -4
  130. package/src/lib/shared/subscribableProbes.ts +1 -1
  131. package/src/lib/shared/tailProbeSlot.ts +1 -1
  132. package/src/lib/shared/types/AsyncComputed.ts +20 -0
  133. package/src/lib/shared/types/AsyncState.ts +13 -0
  134. package/src/lib/shared/types/CacheEntry.ts +7 -7
  135. package/src/lib/shared/types/CacheOptions.ts +23 -37
  136. package/src/lib/shared/types/CachePolicy.ts +25 -0
  137. package/src/lib/shared/types/CacheSnapshotEntry.ts +6 -5
  138. package/src/lib/shared/types/ErrorJsonSchemas.ts +8 -0
  139. package/src/lib/shared/types/HttpMethod.ts +3 -1
  140. package/src/lib/shared/types/InputCoercion.ts +17 -0
  141. package/src/lib/shared/types/{Subscribable.ts → NamedAsyncIterable.ts} +1 -1
  142. package/src/lib/shared/types/OutputWirePlan.ts +17 -0
  143. package/src/lib/shared/types/PagesScan.ts +7 -0
  144. package/src/lib/shared/types/PendingAsyncCells.ts +10 -0
  145. package/src/lib/shared/types/RawRemoteFunction.ts +6 -0
  146. package/src/lib/shared/types/RemoteCallable.ts +8 -7
  147. package/src/lib/shared/types/RemoteFunction.ts +16 -15
  148. package/src/lib/shared/types/ResolvedCells.ts +11 -0
  149. package/src/lib/shared/types/ReturnBody.ts +15 -0
  150. package/src/lib/shared/types/RpcBuildStamps.ts +23 -0
  151. package/src/lib/shared/types/RpcErrorGuard.ts +3 -8
  152. package/src/lib/shared/types/Socket.ts +4 -4
  153. package/src/lib/shared/types/SsrPayload.ts +5 -1
  154. package/src/lib/shared/types/StreamPolicy.ts +11 -0
  155. package/src/lib/shared/types/StreamedCells.ts +19 -0
  156. package/src/lib/shared/types/StreamedResolution.ts +24 -6
  157. package/src/lib/shared/types/TailHooks.ts +1 -1
  158. package/src/lib/shared/types/WireKind.ts +11 -0
  159. package/src/lib/shared/url.ts +5 -0
  160. package/src/lib/shared/validationHttpError.ts +33 -0
  161. package/src/lib/shared/warmSeedKey.ts +16 -0
  162. package/src/lib/shared/wireJsonReplacer.ts +30 -0
  163. package/src/lib/shared/writeRpcDts.ts +11 -1
  164. package/src/lib/shared/writeTestSocketsDts.ts +1 -1
  165. package/src/lib/test/createTestApp.ts +19 -1
  166. package/src/lib/ui/README.md +1 -1
  167. package/src/lib/ui/activePendingCells.ts +14 -0
  168. package/src/lib/ui/compile/BLOCK_KEYWORDS.ts +21 -0
  169. package/src/lib/ui/compile/SSR_ESCAPE.ts +4 -1
  170. package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +35 -7
  171. package/src/lib/ui/compile/abideUiPlugin.ts +19 -2
  172. package/src/lib/ui/compile/analyzeComponent.ts +89 -6
  173. package/src/lib/ui/compile/assignmentTargetNames.ts +54 -0
  174. package/src/lib/ui/compile/asyncInterpolationFields.ts +152 -0
  175. package/src/lib/ui/compile/asyncValuePositionError.ts +21 -0
  176. package/src/lib/ui/compile/asyncValuePositionInterpolations.ts +64 -0
  177. package/src/lib/ui/compile/attrLiftPosition.ts +18 -0
  178. package/src/lib/ui/compile/bindListenEvent.ts +5 -6
  179. package/src/lib/ui/compile/cachedSourceFile.ts +40 -0
  180. package/src/lib/ui/compile/catchBinding.ts +7 -5
  181. package/src/lib/ui/compile/classifyInterpolationType.ts +46 -0
  182. package/src/lib/ui/compile/collectAbideDiagnostics.ts +96 -0
  183. package/src/lib/ui/compile/compileComponent.ts +19 -3
  184. package/src/lib/ui/compile/compileModule.ts +27 -58
  185. package/src/lib/ui/compile/compileSSR.ts +52 -13
  186. package/src/lib/ui/compile/compileShadow.ts +265 -42
  187. package/src/lib/ui/compile/composeProps.ts +15 -4
  188. package/src/lib/ui/compile/createShadowLanguageService.ts +116 -68
  189. package/src/lib/ui/compile/createShadowProgram.ts +35 -6
  190. package/src/lib/ui/compile/declaredNames.ts +36 -0
  191. package/src/lib/ui/compile/desugarSignals.ts +419 -37
  192. package/src/lib/ui/compile/expressionIsPrefixEvaluable.ts +19 -0
  193. package/src/lib/ui/compile/generateBuild.ts +74 -17
  194. package/src/lib/ui/compile/generateSSR.ts +286 -75
  195. package/src/lib/ui/compile/hoistableAwaits.ts +193 -0
  196. package/src/lib/ui/compile/hoistableChildRenders.ts +112 -0
  197. package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +3 -1
  198. package/src/lib/ui/compile/interpolationClassifierForRoot.ts +37 -0
  199. package/src/lib/ui/compile/isSpuriousAsyncReadDiagnostic.ts +174 -0
  200. package/src/lib/ui/compile/isWhitespaceText.ts +10 -2
  201. package/src/lib/ui/compile/liftAsyncSubExpressions.ts +166 -0
  202. package/src/lib/ui/compile/lowerAsyncInterpolations.ts +92 -0
  203. package/src/lib/ui/compile/lowerContext.ts +10 -0
  204. package/src/lib/ui/compile/lowerDocAccess.ts +51 -10
  205. package/src/lib/ui/compile/lowerScript.ts +40 -4
  206. package/src/lib/ui/compile/nodeAtShadowOffset.ts +28 -0
  207. package/src/lib/ui/compile/parseTemplate.ts +18 -1090
  208. package/src/lib/ui/compile/parseTemplateRecovering.ts +1385 -0
  209. package/src/lib/ui/compile/referencedIdentifiers.ts +35 -0
  210. package/src/lib/ui/compile/renameSignalRefs.ts +26 -40
  211. package/src/lib/ui/compile/resolveReactiveExport.ts +4 -7
  212. package/src/lib/ui/compile/scopeCss.ts +27 -4
  213. package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +65 -0
  214. package/src/lib/ui/compile/shadowInterpolationClassifier.ts +47 -0
  215. package/src/lib/ui/compile/sourceFileOptionsSignature.ts +20 -0
  216. package/src/lib/ui/compile/structuralHeadTokens.ts +107 -0
  217. package/src/lib/ui/compile/templateSemanticTokens.ts +21 -0
  218. package/src/lib/ui/compile/templateStartOffset.ts +15 -0
  219. package/src/lib/ui/compile/tryPlan.ts +4 -3
  220. package/src/lib/ui/compile/types/AnalyzedComponent.ts +4 -0
  221. package/src/lib/ui/compile/types/AsyncInterpolationField.ts +19 -0
  222. package/src/lib/ui/compile/types/InterpolationClassifier.ts +12 -0
  223. package/src/lib/ui/compile/types/InterpolationKind.ts +7 -0
  224. package/src/lib/ui/compile/types/ParseDiagnostic.ts +8 -0
  225. package/src/lib/ui/compile/types/SeedTypeClassifier.ts +15 -0
  226. package/src/lib/ui/compile/types/TemplateNode.ts +36 -4
  227. package/src/lib/ui/compile/types/ValuePositionInterpolation.ts +13 -0
  228. package/src/lib/ui/compile/writtenTemplateNames.ts +84 -0
  229. package/src/lib/ui/computed.ts +28 -1
  230. package/src/lib/ui/createScope.ts +35 -68
  231. package/src/lib/ui/dom/anchoredBranch.ts +142 -0
  232. package/src/lib/ui/dom/appendText.ts +8 -3
  233. package/src/lib/ui/dom/awaitBlock.ts +62 -75
  234. package/src/lib/ui/dom/bindProp.ts +22 -0
  235. package/src/lib/ui/dom/bindableProp.ts +47 -0
  236. package/src/lib/ui/dom/cellPending.ts +24 -0
  237. package/src/lib/ui/dom/discardBoundary.ts +24 -6
  238. package/src/lib/ui/dom/disposeRange.ts +2 -1
  239. package/src/lib/ui/dom/each.ts +41 -12
  240. package/src/lib/ui/dom/eachAsync.ts +39 -3
  241. package/src/lib/ui/dom/fillBefore.ts +6 -7
  242. package/src/lib/ui/dom/fillBoundary.ts +2 -3
  243. package/src/lib/ui/dom/fillRange.ts +4 -4
  244. package/src/lib/ui/dom/hydrate.ts +6 -12
  245. package/src/lib/ui/dom/isComment.ts +1 -1
  246. package/src/lib/ui/dom/matchingRangeClose.ts +29 -0
  247. package/src/lib/ui/dom/mergeProps.ts +1 -1
  248. package/src/lib/ui/dom/mount.ts +1 -2
  249. package/src/lib/ui/dom/mountChild.ts +9 -40
  250. package/src/lib/ui/dom/mountRange.ts +2 -3
  251. package/src/lib/ui/dom/mountSlot.ts +1 -1
  252. package/src/lib/ui/dom/mountStreamedChild.ts +84 -0
  253. package/src/lib/ui/dom/mountSwappableRange.ts +46 -4
  254. package/src/lib/ui/dom/mutateDocContainer.ts +65 -0
  255. package/src/lib/ui/dom/on.ts +2 -2
  256. package/src/lib/ui/dom/readCell.ts +40 -0
  257. package/src/lib/ui/dom/restProps.ts +2 -2
  258. package/src/lib/ui/dom/spreadProps.ts +3 -4
  259. package/src/lib/ui/dom/switchBlock.ts +30 -7
  260. package/src/lib/ui/dom/tryBlock.ts +233 -70
  261. package/src/lib/ui/dom/types/SwitchCase.ts +5 -1
  262. package/src/lib/ui/dom/when.ts +12 -2
  263. package/src/lib/ui/dom/withScope.ts +9 -2
  264. package/src/lib/ui/finalizeStreamedChildren.ts +89 -0
  265. package/src/lib/ui/flight.ts +56 -0
  266. package/src/lib/ui/html.ts +12 -1
  267. package/src/lib/ui/isolateCellBarrier.ts +17 -0
  268. package/src/lib/ui/linked.ts +48 -2
  269. package/src/lib/ui/props.ts +17 -0
  270. package/src/lib/ui/remoteProxy.ts +84 -159
  271. package/src/lib/ui/renderChain.ts +53 -13
  272. package/src/lib/ui/renderToStream.ts +22 -18
  273. package/src/lib/ui/resumeSeedScript.ts +1 -1
  274. package/src/lib/ui/router.ts +86 -53
  275. package/src/lib/ui/runtime/AsyncCellError.ts +20 -0
  276. package/src/lib/ui/runtime/CELL_SEED.ts +14 -0
  277. package/src/lib/ui/runtime/CHILD_PRESENT.ts +2 -2
  278. package/src/lib/ui/runtime/CURRENT_BOUNDARY.ts +11 -0
  279. package/src/lib/ui/runtime/CURRENT_PATH.ts +29 -0
  280. package/src/lib/ui/runtime/RENDER.ts +10 -7
  281. package/src/lib/ui/runtime/RESUME.ts +4 -4
  282. package/src/lib/ui/runtime/STREAMED_CELLS.ts +57 -0
  283. package/src/lib/ui/runtime/ambientPathBacking.ts +32 -0
  284. package/src/lib/ui/runtime/applyPatchToTree.ts +17 -4
  285. package/src/lib/ui/runtime/blockId.ts +31 -0
  286. package/src/lib/ui/runtime/boundaryFor.ts +11 -0
  287. package/src/lib/ui/runtime/cellBarrierBacking.ts +30 -0
  288. package/src/lib/ui/runtime/createAsyncCell.ts +300 -0
  289. package/src/lib/ui/runtime/createDoc.ts +31 -54
  290. package/src/lib/ui/runtime/createEffectNode.ts +9 -0
  291. package/src/lib/ui/runtime/enterRenderPass.ts +5 -4
  292. package/src/lib/ui/runtime/flushEffects.ts +33 -1
  293. package/src/lib/ui/runtime/isAsyncFunction.ts +14 -0
  294. package/src/lib/ui/runtime/nextBlockId.ts +8 -7
  295. package/src/lib/ui/runtime/renderPath.ts +16 -0
  296. package/src/lib/ui/runtime/scope.ts +11 -0
  297. package/src/lib/ui/runtime/toTeardown.ts +10 -3
  298. package/src/lib/ui/runtime/types/Boundary.ts +9 -0
  299. package/src/lib/ui/runtime/types/RenderContext.ts +9 -7
  300. package/src/lib/ui/runtime/types/SsrRender.ts +9 -2
  301. package/src/lib/ui/runtime/types/UiComponent.ts +1 -5
  302. package/src/lib/ui/runtime/types/UiProps.ts +6 -5
  303. package/src/lib/ui/runtime/withOptionalPath.ts +8 -0
  304. package/src/lib/ui/runtime/withPath.ts +16 -0
  305. package/src/lib/ui/runtime/withPathFrom.ts +20 -0
  306. package/src/lib/ui/runtime/withoutHydration.ts +22 -0
  307. package/src/lib/ui/seedStreamedResolution.ts +31 -6
  308. package/src/lib/ui/settleAsyncCells.ts +24 -0
  309. package/src/lib/ui/socketProxy.ts +1 -1
  310. package/src/lib/ui/startClient.ts +16 -31
  311. package/src/lib/ui/state.ts +9 -3
  312. package/src/lib/ui/trackedComputed.ts +68 -0
  313. package/src/lib/ui/types/Scope.ts +8 -24
  314. package/src/lib/ui/watch.ts +3 -3
  315. package/src/serverEntry.ts +14 -0
  316. package/template/src/server/rpc/getHello.ts +15 -13
  317. package/template/test/app.test.ts +1 -1
  318. package/src/lib/server/runtime/devHotModuleResponse.ts +0 -41
  319. package/src/lib/shared/DEV_HOT_PREFIX.ts +0 -7
  320. package/src/lib/shared/UNREACHABLE_STATUSES.ts +0 -13
  321. package/src/lib/shared/hasReplayableRequest.ts +0 -17
  322. package/src/lib/shared/hydratingSlot.ts +0 -12
  323. package/src/lib/shared/outboxProbeSlot.ts +0 -20
  324. package/src/lib/shared/types/Outbox.ts +0 -9
  325. package/src/lib/shared/types/OutboxEntry.ts +0 -27
  326. package/src/lib/shared/types/SmartReadOptions.ts +0 -36
  327. package/src/lib/shared/wakeHydrationPeeks.ts +0 -20
  328. package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +0 -5
  329. package/src/lib/ui/compile/REACTIVE_CALLEES.ts +0 -11
  330. package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +0 -80
  331. package/src/lib/ui/compile/markupTokens.ts +0 -313
  332. package/src/lib/ui/compile/structuralBlockTokens.ts +0 -100
  333. package/src/lib/ui/dom/applyResolved.ts +0 -87
  334. package/src/lib/ui/dom/scopeLabel.ts +0 -21
  335. package/src/lib/ui/dom/text.ts +0 -20
  336. package/src/lib/ui/history.ts +0 -101
  337. package/src/lib/ui/installHotBridge.ts +0 -93
  338. package/src/lib/ui/installInspectorBridge.ts +0 -140
  339. package/src/lib/ui/outbox.ts +0 -35
  340. package/src/lib/ui/persist.ts +0 -115
  341. package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +0 -281
  342. package/src/lib/ui/rpcOutbox/outboxRegistry.ts +0 -69
  343. package/src/lib/ui/runtime/PATCH_BUS.ts +0 -28
  344. package/src/lib/ui/runtime/captureModelDoc.ts +0 -28
  345. package/src/lib/ui/runtime/hotInstances.ts +0 -10
  346. package/src/lib/ui/runtime/hotReloadEnabled.ts +0 -8
  347. package/src/lib/ui/runtime/hotReplace.ts +0 -38
  348. package/src/lib/ui/runtime/liveScopes.ts +0 -15
  349. package/src/lib/ui/runtime/localStoragePersistence.ts +0 -47
  350. package/src/lib/ui/runtime/registerHotInstance.ts +0 -23
  351. package/src/lib/ui/runtime/seedModelDoc.ts +0 -26
  352. package/src/lib/ui/runtime/types/HotInstance.ts +0 -22
  353. package/src/lib/ui/runtime/types/PatchEvent.ts +0 -16
  354. package/src/lib/ui/seedResolved.ts +0 -28
  355. package/src/lib/ui/sync.ts +0 -48
  356. package/src/lib/ui/types/History.ts +0 -14
  357. package/src/lib/ui/types/PersistHandle.ts +0 -11
  358. package/src/lib/ui/types/PersistenceStore.ts +0 -12
  359. package/src/lib/ui/types/ResolvedFrame.ts +0 -15
  360. package/src/lib/ui/types/SyncTransport.ts +0 -13
@@ -1,5 +1,8 @@
1
1
  import ts from 'typescript'
2
2
  import { ABIDE_PACKAGE_NAME } from '../../shared/ABIDE_PACKAGE_NAME.ts'
3
+ import { attrLiftPosition } from './attrLiftPosition.ts'
4
+ import { isWhitespaceText } from './isWhitespaceText.ts'
5
+ import { type LiftPosition, liftAsyncSubExpressions } from './liftAsyncSubExpressions.ts'
3
6
  import { parseTemplate } from './parseTemplate.ts'
4
7
  import {
5
8
  NESTED_REACTIVE_BINDINGS,
@@ -8,7 +11,9 @@ import {
8
11
  resolveReactiveExport,
9
12
  } from './resolveReactiveExport.ts'
10
13
  import { signalCallee } from './signalCallee.ts'
14
+ import { LEADING_SCRIPT } from './templateStartOffset.ts'
11
15
  import type { CompiledShadow, ShadowDiagnostic, ShadowMapping } from './types/CompiledShadow.ts'
16
+ import type { InterpolationClassifier } from './types/InterpolationClassifier.ts'
12
17
  import type { TemplateNode } from './types/TemplateNode.ts'
13
18
 
14
19
  /*
@@ -22,8 +27,10 @@ OMITTED (see `shadowPreamble`) to avoid a duplicate identifier. Otherwise
22
27
  `state`/`linked`/`computed` are declared ambiently and `effect` is imported as a fallback
23
28
  for a bare/nested use the top-level rewrite doesn't project (unused when projected — fine,
24
29
  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.
30
+ (`const { a = 1 } = props<Shape>()`); a required import (`abide/ui/props`), so the shadow
31
+ declares it only when imported, returning the file's contextual shape (route params or
32
+ `Record<string, any>`) intersected with its own type argument — additive, so declared props
33
+ like `children: Snippet` layer on top instead of re-spelling the route shape.
27
34
  */
28
35
  function shadowPreamble(importedReactives: ReadonlySet<string>): string {
29
36
  /* Omit the ambient fallback for a primitive the author imports — its own binding is
@@ -46,7 +53,6 @@ function shadowPreamble(importedReactives: ReadonlySet<string>): string {
46
53
  importedReactives.has('computed')
47
54
  ? undefined
48
55
  : 'declare function computed<T>(compute: () => T): { readonly value: T }',
49
- 'declare const children: (() => void) | undefined',
50
56
  /* `effect` is defined either way — the preamble import or the author's own import. */
51
57
  'void [effect, snippet, scope]',
52
58
  ]
@@ -68,23 +74,56 @@ The script's signal surface is rewritten to value types:
68
74
  Everything else (functions, plain consts, imports) is emitted verbatim, so
69
75
  expressions inside it (e.g. a computed's compute body) are checked and mapped too.
70
76
  */
71
- export function compileShadow(source: string, propsType = 'Record<string, any>'): CompiledShadow {
72
- const builder = createBuilder()
73
- const leadingScript = source.match(/^\s*<script[^>]*>([\s\S]*?)<\/script>/)
77
+ export function compileShadow(
78
+ source: string,
79
+ propsType = 'Record<string, any>',
80
+ classify?: InterpolationClassifier,
81
+ ): CompiledShadow {
82
+ const builder = createBuilder(classify)
83
+ const leadingScript = source.match(LEADING_SCRIPT)
74
84
  const scriptBody = leadingScript?.[1] ?? ''
75
85
  /* Body starts just past the opening `<script …>`; template just past `</script>`. */
76
86
  const scriptStart = leadingScript ? source.indexOf('>', leadingScript.index) + 1 : 0
77
87
  const templateStart = leadingScript ? (leadingScript.index ?? 0) + leadingScript[0].length : 0
78
88
 
79
- const { imports, types, scope, propsShapes, diagnostics, importedReactives } = analyzeScript(
80
- scriptBody,
81
- scriptStart,
82
- )
89
+ const { imports, types, scope, propsShapes, diagnostics, importedReactives, propsLocalName } =
90
+ analyzeScript(scriptBody, scriptStart)
83
91
  builder.raw(shadowPreamble(importedReactives))
84
- /* `props()` defaults to the file's type a page/layout's route param shape, else
85
- `Record<string, any>` so `const { id } = props()` infers from the route. */
86
- builder.raw(`declare function props<T = ${propsType}>(): T\n`)
92
+ /* The peek helpers the async-interpolation wrap targets (ADR-0032): `$$peek` unwraps a promise
93
+ sub-expression to its resolved value (`undefined` while pending) and `$$peekStream` reads an
94
+ async iterable's latest frame, so `getFoo()?.name`/`{#if getFoo()}` type-check against the
95
+ RESOLVED value the runtime peeks — not the raw `Promise`. Emitted only when a classifier is
96
+ present (the wrap is type-directed); their names carry the reserved `$$` prefix an author
97
+ binding can never take, so they cannot collide with user code. */
98
+ if (classify !== undefined) {
99
+ builder.raw('declare function $$peek<T>(v: T): Awaited<T> | undefined\n')
100
+ builder.raw(
101
+ 'declare function $$peekStream<S>(v: S): (S extends AsyncIterable<infer F> ? F : unknown) | undefined\n',
102
+ )
103
+ }
104
+ /* `props` is a required import (`abide/ui/props`). The shadow owns its type so the
105
+ return is file-contextual — the route param shape (page/layout) or `Record<string,
106
+ any>` (component), intersected with the author's annotation `T` so declared props
107
+ (notably `children: Snippet`) are ADDITIVE and route params never need re-spelling.
108
+ Emitted only when imported: a missing import surfaces as "Cannot find name 'props'"
109
+ (or the author's local alias), so `abide check` flags it. Declared under the LOCAL
110
+ binding name (alias-safe, like `state`/`effect`) — a `props as p` import must get a
111
+ `p` declare, or `p()` reads as undefined once the import below is stripped. */
112
+ if (propsLocalName !== undefined) {
113
+ builder.raw(`declare function ${propsLocalName}<T = {}>(): (${propsType}) & T\n`)
114
+ }
115
+ const propsSpecifier = `${ABIDE_PACKAGE_NAME}/ui/props`
87
116
  for (const line of imports) {
117
+ /* The `props` import is replaced by the contextual `declare function` above;
118
+ emitting it too would be a duplicate-identifier error. Matched by EXACT specifier
119
+ (either quote style, not a loose suffix match) so an unrelated user module merely
120
+ named `.../ui/props` survives verbatim. */
121
+ if (
122
+ line.text.includes(`from '${propsSpecifier}'`) ||
123
+ line.text.includes(`from "${propsSpecifier}"`)
124
+ ) {
125
+ continue
126
+ }
88
127
  builder.flush(line)
89
128
  }
90
129
  /* Component-local `type`/`interface` declarations are hoisted to module scope —
@@ -123,6 +162,19 @@ export function compileShadow(source: string, propsType = 'Record<string, any>')
123
162
  /* Nested `<script>` blocks inline into the synchronous `build()` too, so a top-level
124
163
  await in one is the same build-breaker — flag it, mapped via the node's body offset. */
125
164
  collectNestedScriptAwaitDiagnostics(templateNodes, diagnostics)
165
+ /* Emit the DOM-typed attachment aliases only when an element `attach` uses them: they
166
+ reference `HTMLElementTagNameMap`/`Element`, so a template with no element attach never
167
+ forces DOM lib into its shadow. `__ElementFor` maps a tag to its element interface
168
+ (HTML-first; unknown/custom tags fall back to `Element`); `__Attachment` sources its
169
+ return type from the real `attach` runtime signature so it never drifts. */
170
+ if (hasElementAttach(templateNodes)) {
171
+ builder.raw(
172
+ 'type __ElementFor<T extends string> = T extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[T] : T extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[T] : T extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[T] : Element;\n',
173
+ )
174
+ builder.raw(
175
+ `type __Attachment<E extends Element> = (node: E) => ReturnType<Parameters<typeof import('${ABIDE_PACKAGE_NAME}/ui/dom/attach').attach>[1]>;\n`,
176
+ )
177
+ }
126
178
  emitNodes(templateNodes, builder)
127
179
  builder.raw('}\n')
128
180
  return { ...builder.result(), diagnostics }
@@ -140,6 +192,12 @@ type Builder = {
140
192
  mapped: (text: string, sourceLoc: number | undefined) => void
141
193
  expr: (code: string, sourceLoc: number | undefined) => void
142
194
  stmt: (code: string, sourceLoc: number | undefined) => void
195
+ /* Like `expr`/`stmt` but for a template interpolation: async (sub)expressions are wrapped in a
196
+ peek helper (`$$peek`/`$$peekStream`) so their RESOLVED type composes (ADR-0032). Every source
197
+ span still maps 1:1 — the inserted wrapper chars are unmapped — so diagnostics land precisely.
198
+ With no classifier (or no async sub-expression) these are byte-identical to `expr`/`stmt`. */
199
+ asyncExpr: (code: string, sourceLoc: number | undefined, position: LiftPosition) => void
200
+ asyncStmt: (code: string, sourceLoc: number | undefined, position: LiftPosition) => void
143
201
  flush: (line: ScopeLine) => void
144
202
  /* A fresh shadow-local binding name (`__<base>_<n>`) — for synthesised bindings
145
203
  like an await's resolved value, kept distinct so nested blocks never collide. */
@@ -151,10 +209,55 @@ type Builder = {
151
209
  emitted verbatim from the original script), offset-relative to the line start. */
152
210
  type ScopeLine = { text: string; segments: ShadowMapping[] }
153
211
 
154
- function createBuilder(): Builder {
212
+ function createBuilder(classify?: InterpolationClassifier): Builder {
155
213
  let code = ''
156
214
  let uniqueCounter = 0
157
215
  const mappings: ShadowMapping[] = []
216
+ /* The non-blocking async (sub)expression spans in one interpolation — the peek-wrap targets.
217
+ A leading `await` (blocking) stays verbatim: `await X` already resolves in the async shadow
218
+ render fn. Fail-open: no classifier, an unparseable field, or the D4a `AsyncIterable`-in-`each`
219
+ throw all yield no spans, so the emit is verbatim (`collectValuePositionDiagnostics` still
220
+ reports the D4a error separately). */
221
+ const peekSpans = (exprCode: string, sourceLoc: number, position: LiftPosition) => {
222
+ if (classify === undefined) {
223
+ return []
224
+ }
225
+ try {
226
+ let counter = 0
227
+ const result = liftAsyncSubExpressions(
228
+ exprCode,
229
+ sourceLoc,
230
+ classify,
231
+ () => `__v${counter++}`,
232
+ position,
233
+ )
234
+ return result.spans.filter((span) => !span.blocking)
235
+ } catch {
236
+ return []
237
+ }
238
+ }
239
+ /* Appends `exprCode` mapped 1:1 to source, but wraps each async span in its peek helper — the
240
+ wrapper chars are unmapped, every original char keeps its true source offset. */
241
+ const emitPeeked = (exprCode: string, sourceLoc: number, position: LiftPosition): void => {
242
+ const spans = peekSpans(exprCode, sourceLoc, position)
243
+ if (spans.length === 0) {
244
+ builder.mapped(exprCode, sourceLoc)
245
+ return
246
+ }
247
+ let cursor = 0
248
+ for (const span of spans) {
249
+ if (span.start > cursor) {
250
+ builder.mapped(exprCode.slice(cursor, span.start), sourceLoc + cursor)
251
+ }
252
+ builder.raw(span.kind === 'asyncIterable' ? '$$peekStream(' : '$$peek(')
253
+ builder.mapped(exprCode.slice(span.start, span.end), sourceLoc + span.start)
254
+ builder.raw(')')
255
+ cursor = span.end
256
+ }
257
+ if (cursor < exprCode.length) {
258
+ builder.mapped(exprCode.slice(cursor), sourceLoc + cursor)
259
+ }
260
+ }
158
261
  const builder: Builder = {
159
262
  unique(base) {
160
263
  return `__${base}_${uniqueCounter++}`
@@ -188,6 +291,21 @@ function createBuilder(): Builder {
188
291
  builder.expr(exprCode, sourceLoc)
189
292
  code += ';\n'
190
293
  },
294
+ asyncExpr(exprCode, sourceLoc, position) {
295
+ /* No source offset ⇒ can't map or classify — emit verbatim, like `expr`. */
296
+ if (sourceLoc === undefined) {
297
+ code += `(${exprCode})`
298
+ return
299
+ }
300
+ code += '('
301
+ emitPeeked(exprCode, sourceLoc, position)
302
+ code += ')'
303
+ },
304
+ asyncStmt(exprCode, sourceLoc, position) {
305
+ code += ';'
306
+ builder.asyncExpr(exprCode, sourceLoc, position)
307
+ code += ';\n'
308
+ },
191
309
  flush(line) {
192
310
  const base = code.length
193
311
  for (const segment of line.segments) {
@@ -209,6 +327,10 @@ type ScriptAnalysis = {
209
327
  /* The reactive primitives the author imports (`state`/`effect`), so the preamble
210
328
  omits the ambient fallback for each and avoids a duplicate-identifier error. */
211
329
  importedReactives: Set<string>
330
+ /* The LOCAL binding name the author's `props` import is bound to (alias-safe — `props`
331
+ for the canonical import, `p` for `props as p`), or undefined when not imported. The
332
+ `declare function` for `props` must target this name, not the canonical `'props'`. */
333
+ propsLocalName: string | undefined
212
334
  }
213
335
 
214
336
  /* Pushes a diagnostic for every author binding whose name starts with the reserved `$$`
@@ -321,7 +443,15 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
321
443
  const propsShapes: string[] = []
322
444
  const diagnostics: ShadowDiagnostic[] = []
323
445
  if (scriptBody.trim() === '') {
324
- return { imports, types, scope, propsShapes, diagnostics, importedReactives: new Set() }
446
+ return {
447
+ imports,
448
+ types,
449
+ scope,
450
+ propsShapes,
451
+ diagnostics,
452
+ importedReactives: new Set(),
453
+ propsLocalName: undefined,
454
+ }
325
455
  }
326
456
  const file = ts.createSourceFile('script.ts', scriptBody, ts.ScriptTarget.Latest, true)
327
457
  /* The author's reactive import bindings (alias-safe) — recognition source for
@@ -329,6 +459,15 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
329
459
  the preamble omits its ambient fallback for. */
330
460
  const bindings = reactiveImportBindings(file)
331
461
  const importedReactives = new Set(bindings.direct.values())
462
+ /* The local name bound to `props` (alias-safe): the key whose value is the canonical
463
+ `'props'`. At most one — a file imports `props` from one specifier. */
464
+ let propsLocalName: string | undefined
465
+ for (const [local, canonical] of bindings.direct) {
466
+ if (canonical === 'props') {
467
+ propsLocalName = local
468
+ break
469
+ }
470
+ }
332
471
  /* The `$$` prefix is reserved for the compiler's injected runtime (`$$each`, `$$model`,
333
472
  `$$scope`, …), so an author binding may not start with it — that's the contract that
334
473
  lets a user freely name a variable after any helper. Flag every such declaration. */
@@ -368,7 +507,7 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
368
507
  scope.push(scopeLineFor(declaration, propsShapes, verbatim, span, bindings))
369
508
  }
370
509
  }
371
- return { imports, types, scope, propsShapes, diagnostics, importedReactives }
510
+ return { imports, types, scope, propsShapes, diagnostics, importedReactives, propsLocalName }
372
511
  }
373
512
 
374
513
  /* Value-projects a nested control-flow `<script>` body the way `analyzeScript`
@@ -429,12 +568,20 @@ function scopeLineFor(
429
568
  `verbatim(call.expression)` below keeps the AUTHOR's text for the trailing hover ref. */
430
569
  const callee = resolveReactiveExport(call.expression, bindings)
431
570
  if (callee === 'props') {
432
- /* `const {…} = props<Shape>()`: the type arg (default `Record<string, any>`)
433
- IS the parent-facing prop shape, and the destructure projects verbatim against
434
- the declared typed `props()` so each binding inherits its value type. */
435
- const shape = call.typeArguments?.[0]
571
+ /* The parent-facing prop shape. Like `state`/`computed` below, it comes from the
572
+ generic (`const {…} = props<Shape>()`) OR the destructure's binding annotation
573
+ (`const {…}: Shape = props()`) either TS-idiomatic form resolves through the
574
+ shadow's own imports, so an imported/aliased type just works, no inline gotcha.
575
+ Default `Record<string, any>`; the destructure then projects verbatim so each
576
+ binding inherits its value type. */
577
+ const shape = call.typeArguments?.[0] ?? declaration.type
436
578
  propsShapes.push(shape === undefined ? 'Record<string, any>' : verbatim(shape))
437
- return { text: `const ${verbatim(declaration)};`, segments: [span(declaration, 6)] }
579
+ /* `let`, not `const`: a prop the component writes back through a two-way `bind:prop`
580
+ (`value += 1`, or forwarding it to another `bind:`) is a legal reassignment, so the
581
+ destructure must not read as a `const`. Bindability is dynamic (decided by what the
582
+ parent passed), so the shadow can't single out which props are written — it relaxes
583
+ the whole destructure, matching the runtime's usage-inferred upgrade. */
584
+ return { text: `let ${verbatim(declaration)};`, segments: [span(declaration, 4)] }
438
585
  }
439
586
  /* The rewrite drops the callee from the line, so hovering `state`/`computed`/
440
587
  `linked` at its call site has nothing to resolve. Append the callee as a
@@ -446,14 +593,22 @@ function scopeLineFor(
446
593
  })
447
594
  if (callee === 'state') {
448
595
  /* state<T>(initial): T is the value type — carry it onto the `let` so an
449
- explicit annotation isn't lost to `any`/`any[]` inference of the initial. */
450
- const typeNode = call.typeArguments?.[0]
596
+ explicit annotation isn't lost to `any`/`any[]` inference of the initial. The
597
+ type comes from the generic (`state<T>(v)`), else the binding annotation
598
+ (`let x: T = state(v)`) — either form pins the cell type, so a narrow/`any`
599
+ inference of the initial (`state([])` → `any[]`) can't leak. */
600
+ const typeNode = call.typeArguments?.[0] ?? declaration.type
451
601
  const annotation = typeNode === undefined ? '' : `: ${verbatim(typeNode)}`
452
602
  const init = call.arguments[0]
453
- if (init === undefined) {
454
- /* No initial (`state<T>()`): the value is `T | undefined`. A definite-
455
- assignment assertion (`!`) gives that union without a use-before-assign
456
- false-positive AND without control-flow narrowing it to just `undefined`
603
+ /* A literal `undefined` initial is the no-arg form spelled out (`state<T>(undefined)`):
604
+ the value is `T | undefined`, never `= (undefined)` checked against a non-optional
605
+ `T` which would spuriously flag "undefined not assignable to T". */
606
+ const initIsUndefined =
607
+ init !== undefined && ts.isIdentifier(init) && init.text === 'undefined'
608
+ if (init === undefined || initIsUndefined) {
609
+ /* No initial (`state<T>()` / `state<T>(undefined)`): the value is `T | undefined`.
610
+ A definite-assignment assertion (`!`) gives that union without a use-before-
611
+ assign false-positive AND without control-flow narrowing it to just `undefined`
457
612
  (an `= undefined` initializer, never reassigned in the shadow, would make
458
613
  a guard like `x !== undefined` collapse to `never`). Unguarded access is
459
614
  then correctly flagged possibly-undefined; a guard narrows cleanly. */
@@ -471,9 +626,12 @@ function scopeLineFor(
471
626
  })
472
627
  }
473
628
  /* computed<T>(compute) / linked<T>(seed) — the only callees left: T is the value
474
- type — the call's first arg is a thunk, so invoking it yields the value. Annotate
475
- so an explicit type argument isn't lost to inference of the thunk's return. */
476
- const typeNode = call.typeArguments?.[0]
629
+ type — the call's first arg is a thunk, so invoking it yields the value. The type
630
+ comes from the generic (`computed<T>(fn)`), else the binding annotation
631
+ (`let x: T = state.computed(fn)`) — same fallback as `state` above, so a binding
632
+ annotation isn't dropped and `abide check` doesn't emit `never`-inference false
633
+ positives on the thunk's return. */
634
+ const typeNode = call.typeArguments?.[0] ?? declaration.type
477
635
  const annotation = typeNode === undefined ? '' : `: ${verbatim(typeNode)}`
478
636
  const fn = call.arguments[0]
479
637
  /* `linked` is a writable `State<T>` at runtime (it reseeds AND accepts `.value =`
@@ -494,6 +652,28 @@ function scopeLineFor(
494
652
  })
495
653
  }
496
654
 
655
+ /* Whether any ELEMENT in the tree carries an `attach` — gates emitting the DOM-typed
656
+ attachment aliases. All nested content (block bodies, await/switch branches, snippet
657
+ bodies) routes through `children`, so a recursive `children` walk is complete. */
658
+ function hasElementAttach(nodes: TemplateNode[]): boolean {
659
+ for (const node of nodes) {
660
+ if (node === undefined) {
661
+ continue
662
+ }
663
+ if (node.kind === 'element') {
664
+ for (const attr of node.attrs) {
665
+ if (attr.kind === 'attach') {
666
+ return true
667
+ }
668
+ }
669
+ }
670
+ if ('children' in node && node.children !== undefined && hasElementAttach(node.children)) {
671
+ return true
672
+ }
673
+ }
674
+ return false
675
+ }
676
+
497
677
  /* Emits a sibling list — each node standalone via `emitNode`. */
498
678
  function emitNodes(nodes: TemplateNode[], builder: Builder): void {
499
679
  for (const node of nodes) {
@@ -511,22 +691,43 @@ function emitNode(node: TemplateNode, builder: Builder): void {
511
691
  case 'text':
512
692
  for (const part of node.parts) {
513
693
  if (part.kind === 'expression') {
514
- builder.stmt(part.code, part.loc)
694
+ builder.asyncStmt(part.code, part.loc, 'content')
515
695
  }
516
696
  }
517
697
  return
518
698
  case 'element':
519
699
  for (const attr of node.attrs) {
520
- /* An interpolated value checks each `{expr}` part on its own offset; every
521
- other dynamic attribute checks its single `code`. */
522
- if (attr.kind === 'interpolated') {
700
+ if (attr.kind === 'attach') {
701
+ /* `attach={code}` types its callback's `node` param from the element's tag
702
+ (via `__ElementFor`) and checks the whole value is an attachment: an inline
703
+ arrow's `node` reads the specific DOM interface, and a non-function value is
704
+ rejected. Same IIFE-parameter contextual-typing trick the component-prop
705
+ path uses; the leading `;` guards an unterminated preceding statement. */
706
+ builder.raw(
707
+ `;((__attach: __Attachment<__ElementFor<${JSON.stringify(node.tag)}>>) => {})(`,
708
+ )
709
+ builder.expr(attr.code, attr.loc)
710
+ builder.raw(');\n')
711
+ } else if (attr.kind === 'interpolated') {
712
+ /* An interpolated value checks each `{expr}` part on its own offset. */
523
713
  for (const part of attr.parts) {
524
714
  if (part.kind === 'expression') {
525
- builder.stmt(part.code, part.loc)
715
+ builder.asyncStmt(part.code, part.loc, 'attribute')
526
716
  }
527
717
  }
528
718
  } else if (attr.kind !== 'static') {
529
- builder.stmt(attr.code, attr.loc)
719
+ /* Every other dynamic attribute checks its single `code`. `attrLiftPosition` —
720
+ the shared gate the runtime front-end lifts through — decides whether the
721
+ value is async-liftable: only `name={code}` peek-wraps (its promise/stream
722
+ sub-expressions project to their resolved type), while a directive
723
+ (`on*`/`bind:`/`class:`/`style:`/`{...}`) checks its code verbatim, matching
724
+ that the runtime never injects a cell for it. */
725
+ const position = attrLiftPosition(attr)
726
+ if (position !== undefined) {
727
+ builder.asyncStmt(attr.code, attr.loc, position)
728
+ } else {
729
+ builder.stmt(attr.code, attr.loc)
730
+ }
530
731
  }
531
732
  }
532
733
  emitNodes(node.children, builder)
@@ -548,6 +749,12 @@ function emitNode(node: TemplateNode, builder: Builder): void {
548
749
  prop.name.startsWith('style:') ||
549
750
  prop.name === 'attach'
550
751
  const hasSpread = node.props.some((prop) => prop.spread)
752
+ /* Default slot content (`<Card>…</Card>`) lowers to a `children` Snippet prop at
753
+ runtime (see generateBuild.slotPart), so the completeness check must treat
754
+ `children` as supplied when it's present (handled below). Mirror the runtime's
755
+ predicate: any child that isn't whitespace-only text is renderable slot content
756
+ — an empty/whitespace slot sets no `children` layer, so it doesn't count. */
757
+ const hasSlotContent = node.children.some((child) => !isWhitespaceText(child))
551
758
  for (const prop of node.props.filter(handled)) {
552
759
  /* Lead with a defensive `;`: an IIFE/object-literal arg starts with `(` or
553
760
  `{`, so without it a preceding scope statement left unterminated (a script
@@ -583,14 +790,30 @@ function emitNode(node: TemplateNode, builder: Builder): void {
583
790
  builder.raw(');\n')
584
791
  }
585
792
  } else {
586
- builder.raw(`;((__c: Parameters<typeof ${node.name}>[0]): void => { void __c })({`)
793
+ /* With slot content the child's `children` prop is supplied by the slot, so
794
+ check completeness against the shape with `children` made OPTIONAL rather
795
+ than removed — dropping it via a bare `Omit` collapses a children-only
796
+ shape to `{}`, a weak type that then lets excess props slip by. `Omit` +
797
+ `Partial<Pick<…>>` keeps every other prop required and `children` as a
798
+ known-optional key (a no-op `Partial<{}>` when the child declares none), so
799
+ completeness and excess-prop checks both stay intact. The explicit
800
+ `children={…}` attribute is dropped from the literal — the slot's key wins
801
+ (`composeProps`), and leaving it in would read as excess. */
802
+ const propsShape = `Parameters<typeof ${node.name}>[0]`
803
+ const completenessType = hasSlotContent
804
+ ? `Omit<${propsShape}, 'children'> & Partial<Pick<${propsShape}, Extract<keyof ${propsShape}, 'children'>>>`
805
+ : propsShape
806
+ const literalProps = hasSlotContent
807
+ ? dataProps.filter((prop) => prop.name !== 'children')
808
+ : dataProps
809
+ builder.raw(`;((__c: ${completenessType}): void => { void __c })({`)
587
810
  /* A zero-length anchor right after `{`, pointing at the tag: a missing-
588
811
  required-prop error spans the literal from `{`, so it overlaps this and
589
812
  maps to the tag (an empty span trivially satisfies the source-text ==
590
813
  shadow-text invariant). */
591
814
  builder.mapped('', node.loc)
592
815
  builder.raw('\n')
593
- for (const prop of dataProps) {
816
+ for (const prop of literalProps) {
594
817
  /* The key mapped to its source name (excess-prop errors land on the key);
595
818
  the value verbatim-mapped (wrong-type errors land on the value).
596
819
  Hyphenated names (`aria-label`, `data-*`) aren't valid identifiers, so
@@ -630,7 +853,7 @@ function emitNode(node: TemplateNode, builder: Builder): void {
630
853
  )
631
854
  const thenChildren = node.children.filter((child) => child.kind !== 'case')
632
855
  builder.raw('if ')
633
- builder.expr(node.condition, node.loc)
856
+ builder.asyncExpr(node.condition, node.loc, 'if')
634
857
  builder.raw(' {\n')
635
858
  emitNodes(thenChildren, builder)
636
859
  builder.raw('}')
@@ -639,7 +862,7 @@ function emitNode(node: TemplateNode, builder: Builder): void {
639
862
  for (const branch of branches) {
640
863
  if (branch.condition !== undefined) {
641
864
  builder.raw(' else if ')
642
- builder.expr(branch.condition, branch.loc)
865
+ builder.asyncExpr(branch.condition, branch.loc, 'if')
643
866
  builder.raw(' {\n')
644
867
  } else {
645
868
  builder.raw(' else {\n')
@@ -721,7 +944,7 @@ function emitNode(node: TemplateNode, builder: Builder): void {
721
944
  non-case children (whitespace between cases) carry nothing and are
722
945
  skipped. `break` keeps cases independent under `noFallthroughCasesInSwitch`. */
723
946
  builder.raw('switch (')
724
- builder.expr(node.subject, node.loc)
947
+ builder.asyncExpr(node.subject, node.loc, 'switch')
725
948
  builder.raw(') {\n')
726
949
  for (const child of node.children) {
727
950
  if (child.kind !== 'case') {
@@ -773,7 +996,7 @@ function emitNode(node: TemplateNode, builder: Builder): void {
773
996
  builder.mapped(node.params, node.loc)
774
997
  }
775
998
  /* Wrap the body in `snippet(() => …)` so the shadow types the snippet as
776
- `(args) => Snippet<…>` — mirroring the runtime lowering (which returns
999
+ `(args) => SnippetValue` — mirroring the runtime lowering (which returns
777
1000
  `$$snippet(($host) => …)`) so a snippet passed to a `Snippet`-typed prop
778
1001
  type-checks instead of reading as `() => void`. */
779
1002
  builder.raw(') => snippet(() => {\n')
@@ -11,17 +11,28 @@ SSR/client prop congruence rests on. No spread → the plain object literal of n
11
11
  value thunks (+ the trailing slot). With a `{...expr}` spread → a `mergeProps` of
12
12
  ordered layers — explicit-prop runs, `$$spreadProps(expr)` spreads, the slot —
13
13
  resolved last-wins per key, so source order decides overrides (like JSX).
14
- `lowerExpression` is the caller's expression lowering; `slotPart` is its `$children`
15
- layer (a host-taking builder for the client, a string-returning thunk for SSR) or
14
+ `lowerExpression` is the caller's expression lowering; `slotPart` is its `children`
15
+ layer slotted content as a `Snippet` under the `children` prop key (a builder-
16
+ returning callable for the client, a `$snip`-string-returning callable for SSR) — or
16
17
  undefined when the component has no slotted children.
17
18
  */
18
19
  export function composeProps(
19
20
  props: ComponentProps,
20
21
  lowerExpression: (code: string) => string,
21
22
  slotPart: string | undefined,
23
+ /* The two-way bind accessors (`lowerContext`): `bindRead` lowers a target to its
24
+ current value, `bindWrite` to the statement that writes a value back. Shared with
25
+ element `bind:`, so a component bind accepts the same lvalue / `{ get, set }` targets. */
26
+ bindRead: (code: string) => string,
27
+ bindWrite: (code: string, valueExpr: string) => string,
22
28
  ): string {
23
- const propThunk = (prop: { name: string; code: string }): string =>
24
- `${JSON.stringify(prop.name)}: () => (${lowerExpression(prop.code)})`
29
+ /* A `bind:<name>={target}` prop: the normal `() => value` entry, annotated with a
30
+ `set` write-back channel (`bindProp`) so the child can push changes upstream. A
31
+ read-only consumer still just calls the thunk; the setter rides along unseen. */
32
+ const propThunk = (prop: { name: string; code: string; bind?: true }): string =>
33
+ prop.bind === true
34
+ ? `${JSON.stringify(prop.name)}: $$bindProp(() => (${bindRead(prop.code)}), ($value) => { ${bindWrite(prop.code, '$value')} })`
35
+ : `${JSON.stringify(prop.name)}: () => (${lowerExpression(prop.code)})`
25
36
  if (!props.some((prop) => prop.spread)) {
26
37
  const parts = props.map(propThunk)
27
38
  if (slotPart !== undefined) {