@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,15 +1,14 @@
1
1
  /*
2
2
  The abide-ui runtime names every compiled component module depends on, each
3
- paired with its package subpath (after the package name). One source of truth so
4
- three things can't drift: the normal module's import block (`compileModule`), the
5
- hot module's `window.__abide` destructure (dev component HMR), and the dev bridge
6
- that populates `window.__abide`. Order is the emit order.
3
+ paired with its package subpath (after the package name). One source of truth for
4
+ the import block `compileModule` emits at the head of every component module, so the
5
+ emitted imports can't drift from the real package exports (a test checks each
6
+ resolves). Order is the emit order.
7
7
 
8
8
  `alias` (when set) is the LOCAL name codegen emits — a `$$`-prefixed form reserved
9
9
  for the compiler so a user variable of the helper's bare name can never collide
10
- (`each as $$each`). It defaults to `name`; the dev bridge keys stay bare (the import
11
- source), so a hot module destructures `{ name: alias }`. As each helper's emit sites
12
- flip to its `$$` alias, set `alias` here in lockstep.
10
+ (`each as $$each`). It defaults to `name`. As each helper's emit sites flip to its
11
+ `$$` alias, set `alias` here in lockstep.
13
12
  */
14
13
  export const UI_RUNTIME_IMPORTS: { name: string; specifier: string; alias?: string }[] = [
15
14
  { name: 'snippet', specifier: 'shared/snippet', alias: '$$snippet' },
@@ -44,14 +43,43 @@ export const UI_RUNTIME_IMPORTS: { name: string; specifier: string; alias?: stri
44
43
  { name: 'mountSlot', specifier: 'ui/dom/mountSlot', alias: '$$mountSlot' },
45
44
  { name: 'outlet', specifier: 'ui/dom/outlet', alias: '$$outlet' },
46
45
  { name: 'mountChild', specifier: 'ui/dom/mountChild', alias: '$$mountChild' },
46
+ {
47
+ name: 'mountStreamedChild',
48
+ specifier: 'ui/dom/mountStreamedChild',
49
+ alias: '$$mountStreamedChild',
50
+ },
47
51
  { name: 'mergeProps', specifier: 'ui/dom/mergeProps', alias: '$$mergeProps' },
48
52
  { name: 'spreadProps', specifier: 'ui/dom/spreadProps', alias: '$$spreadProps' },
49
53
  { name: 'restProps', specifier: 'ui/dom/restProps', alias: '$$restProps' },
54
+ { name: 'bindProp', specifier: 'ui/dom/bindProp', alias: '$$bindProp' },
55
+ { name: 'bindableProp', specifier: 'ui/dom/bindableProp', alias: '$$bindableProp' },
50
56
  { name: 'spreadAttrs', specifier: 'ui/dom/spreadAttrs', alias: '$$spreadAttrs' },
51
57
  { name: 'readCall', specifier: 'ui/dom/readCall', alias: '$$readCall' },
58
+ { name: 'readCell', specifier: 'ui/dom/readCell', alias: '$$readCell' },
59
+ { name: 'cellPending', specifier: 'ui/dom/cellPending', alias: '$$cellPending' },
60
+ { name: 'withPath', specifier: 'ui/runtime/withPath', alias: '$$withPath' },
61
+ { name: 'settleAsyncCells', specifier: 'ui/settleAsyncCells', alias: '$$settleAsyncCells' },
62
+ { name: 'flight', specifier: 'ui/flight', alias: '$$flight' },
63
+ {
64
+ name: 'isolateCellBarrier',
65
+ specifier: 'ui/isolateCellBarrier',
66
+ alias: '$$isolateCellBarrier',
67
+ },
68
+ { name: 'renderPath', specifier: 'ui/runtime/renderPath', alias: '$$renderPath' },
69
+ {
70
+ name: 'finalizeStreamedChildren',
71
+ specifier: 'ui/finalizeStreamedChildren',
72
+ alias: '$$finalizeStreamedChildren',
73
+ },
74
+ {
75
+ name: 'mutateDocContainer',
76
+ specifier: 'ui/dom/mutateDocContainer',
77
+ alias: '$$mutateDocContainer',
78
+ },
52
79
  { name: 'hydrate', specifier: 'ui/dom/hydrate', alias: '$$hydrate' },
53
80
  { name: 'escapeKey', specifier: 'ui/runtime/escapeKey', alias: '$$escapeKey' },
54
81
  { name: 'nextBlockId', specifier: 'ui/runtime/nextBlockId', alias: '$$nextBlockId' },
82
+ { name: 'blockId', specifier: 'ui/runtime/blockId', alias: '$$blockId' },
55
83
  {
56
84
  name: 'enterRenderPass',
57
85
  specifier: 'ui/runtime/enterRenderPass',
@@ -4,8 +4,11 @@ import { isLayoutFile } from '../../shared/isLayoutFile.ts'
4
4
  import { messageFromError } from '../../shared/messageFromError.ts'
5
5
  import { AbideCompileError } from './AbideCompileError.ts'
6
6
  import { compileModule } from './compileModule.ts'
7
+ import type { ShadowProgram } from './createShadowProgram.ts'
8
+ import { interpolationClassifierForRoot } from './interpolationClassifierForRoot.ts'
7
9
  import { nearestProjectRoot } from './nearestProjectRoot.ts'
8
10
  import { offsetToLineColumn } from './offsetToLineColumn.ts'
11
+ import { seedTypeClassifierForRoot } from './seedTypeClassifierForRoot.ts'
9
12
 
10
13
  /*
11
14
  Bun plugin that loads `.abide` single-file components: compiles each to the ES
@@ -38,11 +41,25 @@ export const abideUiPlugin: BunPlugin = {
38
41
  /* Scoped CSS keyed by its virtual specifier, filled as each `.abide` loads and
39
42
  read back when Bun resolves the matching `abide-style:` import (browser only). */
40
43
  const cssByVirtual = new Map<string, string>()
44
+ /* One WARM shadow program per project root (ADR-0019, Stage B): the checker +
45
+ per-file mappings type-directed lowering needs, built lazily on first `.abide`
46
+ in a root and reused for every component there — not rebuilt per module. */
47
+ const shadowByRoot = new Map<string, ShadowProgram | undefined>()
41
48
 
42
49
  build.onLoad({ filter: /\.abide$/ }, async (args) => {
43
50
  const source = await Bun.file(args.path).text()
44
- const moduleId = relative(nearestProjectRoot(args.path, process.cwd()), args.path)
51
+ const root = nearestProjectRoot(args.path, process.cwd())
52
+ const moduleId = relative(root, args.path)
45
53
  const isLayout = isLayoutFile(args.path)
54
+ /* The classifier for this file over its root's warm shadow program. Fail-open:
55
+ a program that can't build (or a file with no shadow) yields `undefined`, so
56
+ the module compiles exactly as before. */
57
+ const classify = interpolationClassifierForRoot(shadowByRoot, root, args.path)
58
+ /* The seed classifier for this file over the SAME warm shadow program (ADR-0023):
59
+ reuses the `shadowByRoot` cache `classify` just warmed, so no second program is
60
+ built. Fail-open identically — a no-marker `computed(seed)` routes by type when
61
+ it resolves, else by the `isBareCallComputed` syntax heuristic. */
62
+ const seedClassify = seedTypeClassifierForRoot(shadowByRoot, root, args.path)
46
63
  /* Bun frames a plugin throw at `<file>:0` regardless of the real spot, so
47
64
  carry the component path + resolved line:col in the message — otherwise a
48
65
  control-flow / compile error reads as `:0` and (in deep imports) can look
@@ -66,7 +83,7 @@ export const abideUiPlugin: BunPlugin = {
66
83
  the styles come from the analysis `compileModule` already ran, so the loader no
67
84
  longer re-analyzes the source just to recover them. */
68
85
  const { code, styles } = compileAbide(() =>
69
- compileModule(source, { isLayout, moduleId }),
86
+ compileModule(source, { isLayout, moduleId, classify, seedClassify }),
70
87
  )
71
88
  /* Browser build with `<style>`(s): concatenate every scoped block's CSS and
72
89
  pull it into the bundle via one virtual import, keyed by `moduleId` so the
@@ -1,8 +1,13 @@
1
+ import { lowerAsyncInterpolations } from './lowerAsyncInterpolations.ts'
1
2
  import { lowerScript } from './lowerScript.ts'
2
3
  import { parseTemplate } from './parseTemplate.ts'
3
4
  import { scopeCss } from './scopeCss.ts'
5
+ import { LEADING_SCRIPT } from './templateStartOffset.ts'
4
6
  import type { AnalyzedComponent } from './types/AnalyzedComponent.ts'
7
+ import type { InterpolationClassifier } from './types/InterpolationClassifier.ts'
8
+ import type { SeedTypeClassifier } from './types/SeedTypeClassifier.ts'
5
9
  import type { TemplateNode } from './types/TemplateNode.ts'
10
+ import { writtenTemplateNames } from './writtenTemplateNames.ts'
6
11
 
7
12
  /*
8
13
  The shared compile front-end: splits the leading `<script>` off the template,
@@ -20,21 +25,89 @@ component+position identity, not CSS text: an edit to a `<style>` keeps the same
20
25
  `data-a-…`, so the live elements still match and the CSS can hot-swap in place.
21
26
  Absent (direct compile calls / tests) it falls back to hashing the style body.
22
27
  */
23
- export function analyzeComponent(source: string, scopeSeed?: string): AnalyzedComponent {
28
+ export function analyzeComponent(
29
+ source: string,
30
+ scopeSeed?: string,
31
+ classify?: InterpolationClassifier,
32
+ /* Type-directed SEED classifier (ADR-0023): resolves a no-marker `computed(seed)`'s
33
+ async-ness from the seed's checker type over the SAME warm shadow program `classify`
34
+ uses. Optional and fail-open — absent, every seed routes by the `isBareCallComputed`
35
+ syntax heuristic (today's behavior). */
36
+ seedClassify?: SeedTypeClassifier,
37
+ ): AnalyzedComponent {
24
38
  /* Only the LEADING `<script>` is the component script; scripts nested in the
25
39
  template (scoped reactive blocks) survive into the parsed nodes. The `<style>`
26
40
  is left in the template for the parser to extract structurally (below), so a
27
41
  `<style>` quoted inside an expression is never mistaken for the component's. */
28
- const scriptMatch = source.match(/^\s*<script[^>]*>([\s\S]*?)<\/script>/)
29
- const scriptBody = (scriptMatch?.[1] ?? '').trim()
30
- const template = source.replace(/^\s*<script[^>]*>[\s\S]*?<\/script>/, '').trim()
42
+ const scriptMatch = source.match(LEADING_SCRIPT)
43
+ const rawScriptBody = scriptMatch?.[1] ?? ''
44
+ const scriptBody = rawScriptBody.trim()
45
+ /* Absolute `.abide` source offset where the TRIMMED `<script>` body begins — the base that
46
+ maps a desugared `computed(seed)` node (offset-relative to the trimmed body) back to an
47
+ original source location, so the type-directed seed classifier (ADR-0023) resolves it
48
+ against the shadow's source→shadow `mappings`, exactly as `templateBase` anchors each
49
+ interpolation's `loc`. `indexOf('>')` lands just past the opening `<script …>` (matching
50
+ `compileShadow`'s `scriptStart`); the trim delta re-adds the leading whitespace the parsed
51
+ script dropped. */
52
+ const scriptContentBase =
53
+ (scriptMatch ? source.indexOf('>', scriptMatch.index) + 1 : 0) +
54
+ (rawScriptBody.length - rawScriptBody.trimStart().length)
55
+ const afterScript = scriptMatch ? source.slice(scriptMatch[0].length) : source
56
+ const template = afterScript.trim()
57
+ /* Absolute source offset of the trimmed template's first char (script length + the
58
+ remainder's leading whitespace). Passed as the parse base so each interpolation's
59
+ `loc` is an offset into the ORIGINAL source — the coordinate the shadow's
60
+ source→shadow `mappings` use, so a type-directed classifier resolves against it.
61
+ The runtime back-ends ignore `loc`, so this only affects the classifier's lookup. */
62
+ const templateBase =
63
+ (scriptMatch ? scriptMatch[0].length : 0) +
64
+ (afterScript.length - afterScript.trimStart().length)
31
65
 
32
66
  /* Parse the template first so the nested branch `<script>`s are in hand before the
33
67
  leading script lowers: their raw code feeds the dead-import usage check, keeping a
34
68
  reactive import (`state`) alive when only a nested branch uses it (its
35
69
  `state.computed(...)` stays literal, unlike the desugared leading script). */
36
- const { nodes } = parseTemplate(template)
70
+ const parsed = parseTemplate(template, templateBase)
71
+ /* Type-directed async lowering (ADR-0032): lift every promise/`AsyncIterable`-typed
72
+ (sub)expression — in content AND value positions — to an injected peek-cell, rewriting it
73
+ in place to a bare `{__vN}` reference BEFORE the script lowers and scopes annotate, so it
74
+ reads `undefined` while pending and composes with `??`/`?.`. Runs unconditionally: with no
75
+ classifier only syntactic `await`s lift (fail-open), so a value-position `{await …}` still
76
+ works while a bare async position degrades to today's plain binding. */
77
+ const { nodes, cells } = lowerAsyncInterpolations(parsed.nodes, classify)
78
+ /* Each lifted async (sub)expression was rewritten to a bare `{__vN}` reference; APPEND a
79
+ matching `const __vN = computed(<seed>)` to the script so the seed's author-signal reads
80
+ lower through the normal pipeline and the declaration desugars to a `trackedComputed` cell
81
+ (read via `$$readCell(__vN)`). `computed` here is a bare, unimported callee — `desugarSignals`
82
+ recognizes it by the injected name (`injectedCellNames`), not by import resolution. A
83
+ BLOCKING cell (author `await`, `blockingCellNames`) joins the SSR barrier; a streaming one
84
+ ships pending. Appended (after the author's `state(...)` inits, not before) so a signal-arg
85
+ seed `{getStream(count)}` reads the INITIALIZED `count`; the cell only feeds the render,
86
+ which runs after the whole script, so end-of-script placement is safe. */
87
+ const injectedCellNames = new Set(cells.map((cell) => cell.name))
88
+ const blockingCellNames = new Set(
89
+ cells.filter((cell) => cell.blocking).map((cell) => cell.name),
90
+ )
91
+ /* A promise seed is wrapped as an async thunk `async () => await (<code>)` — inside an async
92
+ arrow `await` is a keyword and the parens are unambiguous (a bare-text `computed(await (X))`
93
+ reparsed at module scope reads `await(X)` as a call). A stream seed stays a bare
94
+ `computed(<code>)` (byte-identical to an explicit `state.computed(getStream())`). */
95
+ const injectedScript = cells.map((cell) =>
96
+ cell.kind === 'promise'
97
+ ? `const ${cell.name} = computed(async () => await (${cell.code}));`
98
+ : `const ${cell.name} = computed(${cell.code});`,
99
+ )
100
+ const fullScriptBody =
101
+ injectedScript.length === 0
102
+ ? scriptBody
103
+ : scriptBody.length > 0
104
+ ? `${scriptBody}\n${injectedScript.join('\n')}`
105
+ : injectedScript.join('\n')
37
106
  const nestedScriptCode = collectNestedScriptCode(nodes)
107
+ /* Names the markup writes or forwards as a `bind:` target — unioned (in `desugarSignals`)
108
+ with the script's own writes so a `props()` binding used two-way desugars to a writable
109
+ cell (`bindableProp`) rather than a read-only derive. */
110
+ const templateWrittenNames = writtenTemplateNames(nodes)
38
111
  /* `lowerScript` parses the script ONCE and chains signal desugaring, reference
39
112
  renaming, and doc-access lowering over that single tree, then hoists top-level
40
113
  imports off the tree structurally — imports live at module scope, not inside the
@@ -46,8 +119,17 @@ export function analyzeComponent(source: string, scopeSeed?: string): AnalyzedCo
46
119
  stateNames,
47
120
  derivedNames,
48
121
  computedNames,
122
+ cellReadNames,
49
123
  droppedReactiveImports,
50
- } = lowerScript(scriptBody, nestedScriptCode)
124
+ } = lowerScript(
125
+ fullScriptBody,
126
+ nestedScriptCode,
127
+ injectedCellNames,
128
+ blockingCellNames,
129
+ templateWrittenNames,
130
+ seedClassify,
131
+ scriptContentBase,
132
+ )
51
133
  /* `annotateScopes` mutates the tree — assigning each style its scope attribute and
52
134
  stamping covered elements — and returns the scoped CSS per block for the bundler. */
53
135
  const styles = annotateScopes(nodes, [], scopeSeed, { count: 0 })
@@ -59,6 +141,7 @@ export function analyzeComponent(source: string, scopeSeed?: string): AnalyzedCo
59
141
  stateNames,
60
142
  derivedNames,
61
143
  computedNames,
144
+ cellReadNames,
62
145
  nodes,
63
146
  styles,
64
147
  /* Hydration adopts every block in place — including `await`, which resumes
@@ -0,0 +1,54 @@
1
+ import ts from 'typescript'
2
+
3
+ /*
4
+ Collects every identifier NAME that is written somewhere in `source` — the target of
5
+ an assignment (`x = …`, `x += …`, and every compound form) or the operand of a `++`/`--`
6
+ update. A write to a member (`x.foo = …`, `x[i]++`) counts as a write to its root object
7
+ identifier (`x`), since mutating the object the prop holds still needs a writable cell.
8
+ Reads never count. Used to decide which props a component actually writes, so only those
9
+ are upgraded from a read-only derive to a writable cell (`bindableProp`).
10
+
11
+ Accumulates into `into` so several fragments (the script, each template expression) can
12
+ feed one set. Best-effort and syntactic — it does not resolve scope, so a shadowing local
13
+ of the same name is conservatively treated as a write to the prop; that only ever upgrades
14
+ a prop needlessly, never drops a real write.
15
+ */
16
+ export function assignmentTargetNames(source: ts.SourceFile, into: Set<string>): void {
17
+ const rootIdentifier = (node: ts.Expression): string | undefined => {
18
+ let current: ts.Expression = node
19
+ /* Peel member/element access and parens down to the base object identifier — every
20
+ one of these node kinds exposes the inner expression as `.expression`. */
21
+ while (
22
+ ts.isPropertyAccessExpression(current) ||
23
+ ts.isElementAccessExpression(current) ||
24
+ ts.isParenthesizedExpression(current) ||
25
+ ts.isNonNullExpression(current)
26
+ ) {
27
+ current = current.expression
28
+ }
29
+ return ts.isIdentifier(current) ? current.text : undefined
30
+ }
31
+ const visit = (node: ts.Node): void => {
32
+ if (
33
+ ts.isBinaryExpression(node) &&
34
+ node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
35
+ node.operatorToken.kind <= ts.SyntaxKind.LastAssignment
36
+ ) {
37
+ const name = rootIdentifier(node.left)
38
+ if (name !== undefined) {
39
+ into.add(name)
40
+ }
41
+ } else if (
42
+ (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) &&
43
+ (node.operator === ts.SyntaxKind.PlusPlusToken ||
44
+ node.operator === ts.SyntaxKind.MinusMinusToken)
45
+ ) {
46
+ const name = rootIdentifier(node.operand as ts.Expression)
47
+ if (name !== undefined) {
48
+ into.add(name)
49
+ }
50
+ }
51
+ ts.forEachChild(node, visit)
52
+ }
53
+ visit(source)
54
+ }
@@ -0,0 +1,152 @@
1
+ import { attrLiftPosition } from './attrLiftPosition.ts'
2
+ import type { AsyncInterpolationField } from './types/AsyncInterpolationField.ts'
3
+ import type { TemplateNode } from './types/TemplateNode.ts'
4
+
5
+ /* No control-flow subject to record — the writeback for a content/attribute field. */
6
+ const NO_OP = (): void => {}
7
+
8
+ /*
9
+ Enumerates the async-liftable interpolation fields of ONE template node — content text parts,
10
+ value-position attributes (`attrLiftPosition`), and the control-flow subjects — as the single shared
11
+ reading both compile front-ends lower from. It mirrors `ifPlan`/`awaitPlan` (a per-node structural
12
+ plan the two back-ends read), applied to interpolations: the runtime lifts each field to an injected
13
+ `computed` cell and `write`s the rewritten `__vN` reference back — marking `setAsyncSubject` when a
14
+ whole subject collapsed to one bare cell — while the shadow reads the same `code`/`loc`/`position` to
15
+ peek-wrap its value-typed projection. Neither can disagree on WHICH interpolations are async or their
16
+ position.
17
+
18
+ Fields are yielded in source order (text parts / attributes before the control-flow subject) so the
19
+ runtime's global `__vN` cell numbering stays deterministic. The node's children are NOT descended —
20
+ each front-end recurses on its own. A `{#for await}` source is the sanctioned `AsyncIterable`, drained
21
+ unchanged, so it is never lifted (excluded); `await` / `component` / `snippet` / `try` carry no lifted
22
+ field (an `await`'s promise and a component's props route through their own lowerings).
23
+ */
24
+ export function asyncInterpolationFields(node: TemplateNode): AsyncInterpolationField[] {
25
+ const fields: AsyncInterpolationField[] = []
26
+ switch (node.kind) {
27
+ case 'text':
28
+ for (const part of node.parts) {
29
+ if (part.kind === 'expression' && part.loc !== undefined) {
30
+ fields.push({
31
+ code: part.code,
32
+ loc: part.loc,
33
+ position: 'content',
34
+ subject: false,
35
+ write: (code) => {
36
+ part.code = code
37
+ },
38
+ setAsyncSubject: NO_OP,
39
+ })
40
+ }
41
+ }
42
+ return fields
43
+ case 'element':
44
+ for (const attr of node.attrs) {
45
+ if (attr.kind === 'interpolated') {
46
+ for (const part of attr.parts) {
47
+ if (part.kind === 'expression' && part.loc !== undefined) {
48
+ fields.push({
49
+ code: part.code,
50
+ loc: part.loc,
51
+ position: 'attribute',
52
+ subject: false,
53
+ write: (code) => {
54
+ part.code = code
55
+ },
56
+ setAsyncSubject: NO_OP,
57
+ })
58
+ }
59
+ }
60
+ continue
61
+ }
62
+ /* Only `expression` attributes lift (directives evaluate to a handler/lvalue, not a
63
+ rendered value) — `attrLiftPosition` is the shared gate. */
64
+ const position = attrLiftPosition(attr)
65
+ if (
66
+ position !== undefined &&
67
+ attr.kind === 'expression' &&
68
+ attr.loc !== undefined
69
+ ) {
70
+ fields.push({
71
+ code: attr.code,
72
+ loc: attr.loc,
73
+ position,
74
+ subject: false,
75
+ write: (code) => {
76
+ attr.code = code
77
+ },
78
+ setAsyncSubject: NO_OP,
79
+ })
80
+ }
81
+ }
82
+ return fields
83
+ case 'if':
84
+ if (node.loc !== undefined) {
85
+ fields.push({
86
+ code: node.condition,
87
+ loc: node.loc,
88
+ position: 'if',
89
+ subject: true,
90
+ write: (code) => {
91
+ node.condition = code
92
+ },
93
+ setAsyncSubject: (asyncSubject) => {
94
+ node.asyncSubject = asyncSubject
95
+ },
96
+ })
97
+ }
98
+ return fields
99
+ case 'switch':
100
+ if (node.loc !== undefined) {
101
+ fields.push({
102
+ code: node.subject,
103
+ loc: node.loc,
104
+ position: 'switch',
105
+ subject: true,
106
+ write: (code) => {
107
+ node.subject = code
108
+ },
109
+ setAsyncSubject: (asyncSubject) => {
110
+ node.asyncSubject = asyncSubject
111
+ },
112
+ })
113
+ }
114
+ return fields
115
+ case 'case':
116
+ /* An `{:elseif}` condition — a truthy-tested control-flow subject, lifted like an
117
+ `{#if}` head so a bare async `{:elseif}` holds the cond-chain while it loads. */
118
+ if (node.condition !== undefined && node.loc !== undefined) {
119
+ fields.push({
120
+ code: node.condition,
121
+ loc: node.loc,
122
+ position: 'if',
123
+ subject: true,
124
+ write: (code) => {
125
+ node.condition = code
126
+ },
127
+ setAsyncSubject: (asyncSubject) => {
128
+ node.asyncSubject = asyncSubject
129
+ },
130
+ })
131
+ }
132
+ return fields
133
+ case 'each':
134
+ /* A plain `{#for}` source lifts (a promise-of-iterable → empty while pending); a
135
+ `{#for await}` source is the sanctioned `AsyncIterable`, never lifted. */
136
+ if (node.loc !== undefined && !node.async) {
137
+ fields.push({
138
+ code: node.items,
139
+ loc: node.loc,
140
+ position: 'each',
141
+ subject: false,
142
+ write: (code) => {
143
+ node.items = code
144
+ },
145
+ setAsyncSubject: NO_OP,
146
+ })
147
+ }
148
+ return fields
149
+ default:
150
+ return fields
151
+ }
152
+ }
@@ -0,0 +1,21 @@
1
+ import type { InterpolationKind } from './types/InterpolationKind.ts'
2
+ import type { ValuePositionInterpolation } from './types/ValuePositionInterpolation.ts'
3
+
4
+ /*
5
+ The narrowed value-position guard (ADR-0032): a promise/`AsyncIterable` (sub)expression now LIFTS
6
+ to a peek-cell in every position — an attribute, an `{#if}`/`{#switch}` subject, a plain `{#for}`
7
+ source — reading `undefined` while pending. The ONE remaining error is a raw `AsyncIterable`
8
+ driving a PLAIN `{#for}`: a frame is not a collection, so iterate its frames with `{#for await}`
9
+ (the `for await` position is exempt). Everything else (promises everywhere, streams in attribute/
10
+ `{#if}`/`{#switch}`/content) is allowed. Shared by the build front-end (`liftAsyncSubExpressions`
11
+ throws the same message) and `abide check` (`collectAbideDiagnostics`).
12
+ */
13
+ export function asyncValuePositionError(
14
+ kind: InterpolationKind,
15
+ position: ValuePositionInterpolation['position'],
16
+ ): string | undefined {
17
+ if (kind !== 'asyncIterable' || position !== 'each') {
18
+ return undefined
19
+ }
20
+ return "[abide] an `AsyncIterable` can't drive a plain `{#for}` — iterate its frames with `{#for await}`."
21
+ }
@@ -0,0 +1,64 @@
1
+ import type { TemplateNode } from './types/TemplateNode.ts'
2
+ import type { ValuePositionInterpolation } from './types/ValuePositionInterpolation.ts'
3
+
4
+ /*
5
+ Collects every interpolation sitting in a NON-content value position across a parsed
6
+ template — the positions Stage E guards (ADR-0019): an attribute value, an `{#if}` /
7
+ `{#switch}` head, and a `{#each}`/`{#for}` iterable. Text-child positions are NOT
8
+ collected: promise/asyncIterable text interpolations lower to a streaming await /
9
+ stream cell (Stages C+D), so they render fine. A `{#for await}` iterable is included
10
+ but tagged `for await` so the caller allows an `AsyncIterable` there.
11
+
12
+ Shared by both guard sites: the build front-end (`lowerAsyncInterpolations`, which
13
+ classifies each and throws) and `abide check` (`collectAbideDiagnostics`, which
14
+ classifies each and pushes a diagnostic). Only positions the parser tracked an offset
15
+ for are collected — an untracked span (`loc === undefined`) has no classifier key.
16
+ */
17
+ export function asyncValuePositionInterpolations(
18
+ nodes: TemplateNode[],
19
+ ): ValuePositionInterpolation[] {
20
+ const found: ValuePositionInterpolation[] = []
21
+ collect(nodes, found)
22
+ return found
23
+ }
24
+
25
+ /* Recursive walk: pushes each node's value-position interpolations, then descends
26
+ into its children so nested elements and blocks are reached. */
27
+ function collect(nodes: TemplateNode[], found: ValuePositionInterpolation[]): void {
28
+ for (const node of nodes) {
29
+ if (node === undefined) {
30
+ continue
31
+ }
32
+ if (node.kind === 'element') {
33
+ for (const attr of node.attrs) {
34
+ /* `name={expr}` — a plain reactive value bind. */
35
+ if (attr.kind === 'expression' && attr.loc !== undefined) {
36
+ found.push({ loc: attr.loc, code: attr.code, position: 'attribute' })
37
+ continue
38
+ }
39
+ /* `name="… {expr} …"` — each interpolated part stringifies into the value. */
40
+ if (attr.kind === 'interpolated') {
41
+ for (const part of attr.parts) {
42
+ if (part.kind === 'expression' && part.loc !== undefined) {
43
+ found.push({ loc: part.loc, code: part.code, position: 'attribute' })
44
+ }
45
+ }
46
+ }
47
+ }
48
+ } else if (node.kind === 'if' && node.loc !== undefined) {
49
+ found.push({ loc: node.loc, code: node.condition, position: 'if' })
50
+ } else if (node.kind === 'switch' && node.loc !== undefined) {
51
+ found.push({ loc: node.loc, code: node.subject, position: 'switch' })
52
+ } else if (node.kind === 'each' && node.loc !== undefined) {
53
+ /* `{#for await}` is the sanctioned async iterable; a sync `{#each}` is not. */
54
+ found.push({
55
+ loc: node.loc,
56
+ code: node.items,
57
+ position: node.async ? 'for await' : 'each',
58
+ })
59
+ }
60
+ if ('children' in node) {
61
+ collect(node.children, found)
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,18 @@
1
+ import type { LiftPosition } from './liftAsyncSubExpressions.ts'
2
+ import type { TemplateAttr } from './types/TemplateAttr.ts'
3
+
4
+ /* The value position an attribute's authored expression occupies for async lowering, or undefined
5
+ when the attribute carries no async-liftable expression. Only `name={code}` (`expression`) and
6
+ `name="… {code} …"` (`interpolated`) bind a rendered value a promise/stream sub-expression may sit
7
+ in and lift to a peek-cell — every element value position is `'attribute'`. The directive
8
+ attributes (`on*` event, `bind:`, `class:`, `style:`, `attach`, `{...}` spread) evaluate to a
9
+ handler / lvalue / attachment / spread object, not a rendered value, so their expressions are NOT
10
+ lifted; a `static` literal carries no expression at all. Both compile front-ends read this so the
11
+ runtime's injected cells and the shadow's peek-wrap cover the SAME attribute set — the two can
12
+ never silently disagree on which attribute expressions are async. */
13
+ export function attrLiftPosition(attr: TemplateAttr): LiftPosition | undefined {
14
+ if (attr.kind === 'expression' || attr.kind === 'interpolated') {
15
+ return 'attribute'
16
+ }
17
+ return undefined
18
+ }
@@ -2,18 +2,17 @@
2
2
  The DOM event a generic two-way `bind:<property>` listens on to write the bound
3
3
  path back. Most form fields report edits via `input`, but a few properties are
4
4
  driven by their own event and never fire `input` — `<details open>` fires
5
- `toggle`, a checkbox/radio `checked` fires `change`, and a `<select>` settles its
6
- `value` on `change`. Picking the wrong event silently breaks the write-back.
5
+ `toggle` and a checkbox/radio `checked` fires `change`. `<select value>` is NOT
6
+ handled here: generateBuild intercepts it before this function and routes it to
7
+ `bindSelectValue` (a MutationObserver-based helper that re-applies on late
8
+ options), so this function never sees that case.
7
9
  */
8
- export function bindListenEvent(property: string, tag: string): string {
10
+ export function bindListenEvent(property: string): string {
9
11
  if (property === 'open') {
10
12
  return 'toggle'
11
13
  }
12
14
  if (property === 'checked') {
13
15
  return 'change'
14
16
  }
15
- if (property === 'value' && tag === 'select') {
16
- return 'change'
17
- }
18
17
  return 'input'
19
18
  }
@@ -0,0 +1,40 @@
1
+ import ts from 'typescript'
2
+
3
+ /*
4
+ One parsed `ts.SourceFile` per (fileName, exact text, options signature), shared across
5
+ every `ts.Program` this process builds. `ts.createProgram` re-parses all ~90 default
6
+ `lib.*.d.ts` files (~3MB) plus every resolved dependency on each call — negligible for the
7
+ one-shot `abide check` CLI, but the LSP rebuilds a program per edit and the test suite builds
8
+ dozens over identical inputs. A `SourceFile` is a pure function of its text and the
9
+ parse/bind-affecting compiler options, so reuse is safe — the same reuse TS performs for the
10
+ source files it carries forward from an incremental `oldProgram`. Keying on the EXACT text
11
+ means any content change re-parses (never a stale AST); the `signature` keeps two projects
12
+ with different targets/libs from sharing a file; one entry per fileName bounds the cache to a
13
+ single program's footprint.
14
+ */
15
+
16
+ type CacheEntry = { text: string; signature: string; sourceFile: ts.SourceFile }
17
+
18
+ const CACHE = new Map<string, CacheEntry>()
19
+
20
+ export function cachedSourceFile(
21
+ fileName: string,
22
+ text: string,
23
+ languageVersionOrOptions: ts.ScriptTarget | ts.CreateSourceFileOptions,
24
+ signature: string,
25
+ scriptKind?: ts.ScriptKind,
26
+ ): ts.SourceFile {
27
+ const existing = CACHE.get(fileName)
28
+ if (existing && existing.text === text && existing.signature === signature) {
29
+ return existing.sourceFile
30
+ }
31
+ const sourceFile = ts.createSourceFile(
32
+ fileName,
33
+ text,
34
+ languageVersionOrOptions,
35
+ true,
36
+ scriptKind,
37
+ )
38
+ CACHE.set(fileName, { text, signature, sourceFile })
39
+ return sourceFile
40
+ }
@@ -1,8 +1,10 @@
1
1
  import type { Binding } from './types/Binding.ts'
2
2
 
3
- /* A block's `catch` branch binding — the error name bound `plain`, or none when the block
4
- has no catch branch. Shared by the await/each/try plans so the one catch-binding shape
5
- stays single-source. */
6
- export function catchBinding(catchAs: string, hasCatch: boolean): Binding[] {
7
- return hasCatch ? [{ name: catchAs, classification: 'plain' }] : []
3
+ /* A block's `catch` branch binding — the error name, or none when the block has no catch
4
+ branch. Shared by the await/each/try plans so the one catch-binding shape stays
5
+ single-source. `reactive` binds the error as a `.value` cell the runtime can update in
6
+ place (the reactive `{#try}` — a catch→catch error swaps in place, no rebuild); the
7
+ await/each catches leave it `plain` (they pass the error by value). */
8
+ export function catchBinding(catchAs: string, hasCatch: boolean, reactive = false): Binding[] {
9
+ return hasCatch ? [{ name: catchAs, classification: reactive ? 'reactive' : 'plain' }] : []
8
10
  }