@abide/abide 0.49.0 → 0.50.1

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 (325) hide show
  1. package/AGENTS.md +564 -437
  2. package/CHANGELOG.md +183 -0
  3. package/README.md +165 -202
  4. package/package.json +14 -5
  5. package/src/abideLsp.ts +5 -6
  6. package/src/abideResolverPlugin.ts +197 -169
  7. package/src/build.ts +79 -56
  8. package/src/buildDisconnected.ts +0 -2
  9. package/src/checkAbide.ts +12 -2
  10. package/src/devEntry.ts +113 -28
  11. package/src/discoveryEntry.ts +3 -2
  12. package/src/lib/bundle/disconnected.abide +69 -73
  13. package/src/lib/bundle/infoPlist.ts +13 -7
  14. package/src/lib/bundle/spawnEmbeddedServer.ts +13 -4
  15. package/src/lib/bundle/waitForServer.ts +6 -1
  16. package/src/lib/mcp/mcpTools.ts +15 -6
  17. package/src/lib/server/error.ts +6 -6
  18. package/src/lib/server/json.ts +17 -3
  19. package/src/lib/server/rpc/defineRpc.ts +58 -24
  20. package/src/lib/server/rpc/parseArgs.ts +98 -13
  21. package/src/lib/server/rpc/resolveRpcJsonSchema.ts +28 -0
  22. package/src/lib/server/rpc/types/RpcHelper.ts +75 -105
  23. package/src/lib/server/rpc/types/RpcRegistryEntry.ts +24 -9
  24. package/src/lib/server/rpc/validationError.ts +1 -1
  25. package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +0 -15
  26. package/src/lib/server/runtime/buildCacheSnapshot.ts +10 -9
  27. package/src/lib/server/runtime/buildInspectorSurface.ts +6 -4
  28. package/src/lib/server/runtime/buildOpenApiSpec.ts +55 -11
  29. package/src/lib/server/runtime/buildPreloadManifest.ts +12 -10
  30. package/src/lib/server/runtime/createAppAssetServer.ts +18 -15
  31. package/src/lib/server/runtime/createAppRouteResolver.ts +145 -0
  32. package/src/lib/server/runtime/createPlumbingRouter.ts +212 -0
  33. package/src/lib/server/runtime/createRouteDispatcher.ts +4 -10
  34. package/src/lib/server/runtime/createServer.ts +120 -324
  35. package/src/lib/server/runtime/createUiPageRenderer.ts +137 -26
  36. package/src/lib/server/runtime/devClientFingerprint.ts +19 -34
  37. package/src/lib/server/runtime/installAmbientScopeStore.ts +30 -9
  38. package/src/lib/server/runtime/logExposedSurfaces.ts +8 -7
  39. package/src/lib/server/runtime/pathStore.ts +15 -0
  40. package/src/lib/server/runtime/registryManifests.ts +1 -1
  41. package/src/lib/server/runtime/renderCellBarrierStore.ts +15 -0
  42. package/src/lib/server/runtime/runWithRequestScope.ts +3 -0
  43. package/src/lib/server/runtime/serializeCacheSnapshot.ts +6 -4
  44. package/src/lib/server/runtime/snapshotEntryFromCache.ts +14 -14
  45. package/src/lib/server/runtime/streamCacheResolutions.ts +14 -7
  46. package/src/lib/server/runtime/streamFromIterator.ts +30 -2
  47. package/src/lib/server/runtime/textResponse.ts +23 -0
  48. package/src/lib/server/runtime/types/DevReloadStamp.ts +6 -10
  49. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
  50. package/src/lib/server/runtime/types/RequestStore.ts +27 -0
  51. package/src/lib/server/runtime/warnUnguardedMcp.ts +15 -5
  52. package/src/lib/server/sockets/createSocketDispatcher.ts +9 -11
  53. package/src/lib/server/sockets/defineSocket.ts +5 -4
  54. package/src/lib/server/sse.ts +5 -1
  55. package/src/lib/shared/ASYNC_CELL.ts +5 -0
  56. package/src/lib/shared/DEV_REBUILD_PATH.ts +6 -0
  57. package/src/lib/shared/HTTP_METHODS.ts +6 -0
  58. package/src/lib/shared/HttpError.ts +1 -5
  59. package/src/lib/shared/MCP_PATH.ts +6 -0
  60. package/src/lib/shared/PROXIED_SERVER_SUBDIRS.ts +15 -0
  61. package/src/lib/shared/REF_JSON_TAGS.ts +13 -1
  62. package/src/lib/shared/RPC_SHIM_GLOBALS.ts +9 -0
  63. package/src/lib/shared/activeCacheStore.ts +1 -0
  64. package/src/lib/shared/activePage.ts +1 -0
  65. package/src/lib/shared/buildArtifact.ts +4 -1
  66. package/src/lib/shared/buildSocketOverChannel.ts +4 -3
  67. package/src/lib/shared/bundleGraphFromMetafile.ts +68 -0
  68. package/src/lib/shared/cache.ts +156 -131
  69. package/src/lib/shared/changeAffectsClient.ts +12 -7
  70. package/src/lib/shared/createCacheStore.ts +31 -8
  71. package/src/lib/shared/createLifecycleChannel.ts +7 -1
  72. package/src/lib/shared/createReachable.ts +47 -78
  73. package/src/lib/shared/createRemoteFunction.ts +52 -30
  74. package/src/lib/shared/createRpcServerProgram.ts +820 -0
  75. package/src/lib/shared/decodeRefJson.ts +4 -4
  76. package/src/lib/shared/decodeResponse.ts +2 -2
  77. package/src/lib/shared/decodeWireBody.ts +35 -0
  78. package/src/lib/shared/detectRpcMethod.ts +8 -1
  79. package/src/lib/shared/done.ts +8 -2
  80. package/src/lib/shared/encodeRefJson.ts +4 -4
  81. package/src/lib/shared/encodeWireBody.ts +18 -0
  82. package/src/lib/{server/rpc → shared}/fieldErrorsFromIssues.ts +5 -1
  83. package/src/lib/shared/generateDeclarations.ts +72 -0
  84. package/src/lib/shared/hasSeedableRequest.ts +25 -0
  85. package/src/lib/shared/hydrationWindow.ts +53 -0
  86. package/src/lib/shared/isAsyncCell.ts +11 -0
  87. package/src/lib/shared/isAsyncIterable.ts +8 -0
  88. package/src/lib/shared/isSubscribable.ts +3 -3
  89. package/src/lib/shared/isThenable.ts +9 -0
  90. package/src/lib/shared/jsonSchemaForType.ts +258 -0
  91. package/src/lib/shared/lenientDecode.ts +15 -0
  92. package/src/lib/shared/loadProjectTsConfig.ts +28 -0
  93. package/src/lib/shared/markFrameworkSourcesIgnored.ts +1 -1
  94. package/src/lib/shared/matchRoute.ts +4 -14
  95. package/src/lib/shared/peek.ts +14 -0
  96. package/src/lib/shared/pending.ts +9 -6
  97. package/src/lib/shared/pendingAsyncCellsSlot.ts +13 -0
  98. package/src/lib/shared/prepareRpcModule.ts +91 -93
  99. package/src/lib/shared/prepareSocketModule.ts +3 -2
  100. package/src/lib/shared/probeRegistries.ts +5 -18
  101. package/src/lib/shared/reachable.ts +7 -10
  102. package/src/lib/shared/refresh.ts +12 -2
  103. package/src/lib/shared/refreshing.ts +8 -2
  104. package/src/lib/shared/resolvedCellsSlot.ts +13 -0
  105. package/src/lib/shared/reviveWireField.ts +49 -0
  106. package/src/lib/shared/reviveWireOutput.ts +36 -0
  107. package/src/lib/shared/rpcServerForRoot.ts +25 -0
  108. package/src/lib/shared/scanPages.ts +25 -0
  109. package/src/lib/shared/snapshotShippable.ts +8 -7
  110. package/src/lib/shared/streamedCellsSlot.ts +14 -0
  111. package/src/lib/shared/subscribableFromResponse.ts +4 -4
  112. package/src/lib/shared/subscribableProbes.ts +1 -1
  113. package/src/lib/shared/tailProbeSlot.ts +1 -1
  114. package/src/lib/shared/types/AsyncComputed.ts +20 -0
  115. package/src/lib/shared/types/AsyncState.ts +13 -0
  116. package/src/lib/shared/types/CacheEntry.ts +7 -7
  117. package/src/lib/shared/types/CacheOptions.ts +23 -37
  118. package/src/lib/shared/types/CachePolicy.ts +25 -0
  119. package/src/lib/shared/types/CacheSnapshotEntry.ts +6 -5
  120. package/src/lib/shared/types/ErrorJsonSchemas.ts +8 -0
  121. package/src/lib/shared/types/HttpMethod.ts +3 -1
  122. package/src/lib/shared/types/InputCoercion.ts +17 -0
  123. package/src/lib/shared/types/{Subscribable.ts → NamedAsyncIterable.ts} +1 -1
  124. package/src/lib/shared/types/OutputWirePlan.ts +17 -0
  125. package/src/lib/shared/types/PagesScan.ts +7 -0
  126. package/src/lib/shared/types/PendingAsyncCells.ts +10 -0
  127. package/src/lib/shared/types/RawRemoteFunction.ts +6 -0
  128. package/src/lib/shared/types/RemoteCallable.ts +8 -7
  129. package/src/lib/shared/types/RemoteFunction.ts +16 -15
  130. package/src/lib/shared/types/ResolvedCells.ts +11 -0
  131. package/src/lib/shared/types/ReturnBody.ts +15 -0
  132. package/src/lib/shared/types/RpcBuildStamps.ts +23 -0
  133. package/src/lib/shared/types/RpcErrorGuard.ts +3 -8
  134. package/src/lib/shared/types/Socket.ts +4 -4
  135. package/src/lib/shared/types/SsrPayload.ts +5 -1
  136. package/src/lib/shared/types/StreamPolicy.ts +11 -0
  137. package/src/lib/shared/types/StreamedCells.ts +19 -0
  138. package/src/lib/shared/types/StreamedResolution.ts +24 -6
  139. package/src/lib/shared/types/TailHooks.ts +1 -1
  140. package/src/lib/shared/types/WireKind.ts +11 -0
  141. package/src/lib/shared/validationHttpError.ts +33 -0
  142. package/src/lib/shared/warmSeedKey.ts +16 -0
  143. package/src/lib/shared/wireJsonReplacer.ts +30 -0
  144. package/src/lib/shared/writeRpcDts.ts +11 -1
  145. package/src/lib/shared/writeTestSocketsDts.ts +1 -1
  146. package/src/lib/test/createTestApp.ts +19 -1
  147. package/src/lib/ui/README.md +1 -1
  148. package/src/lib/ui/activePendingCells.ts +14 -0
  149. package/src/lib/ui/compile/BLOCK_KEYWORDS.ts +21 -0
  150. package/src/lib/ui/compile/SSR_ESCAPE.ts +4 -1
  151. package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +35 -8
  152. package/src/lib/ui/compile/abideUiPlugin.ts +19 -2
  153. package/src/lib/ui/compile/analyzeComponent.ts +89 -6
  154. package/src/lib/ui/compile/assignmentTargetNames.ts +54 -0
  155. package/src/lib/ui/compile/asyncInterpolationFields.ts +152 -0
  156. package/src/lib/ui/compile/asyncValuePositionError.ts +21 -0
  157. package/src/lib/ui/compile/asyncValuePositionInterpolations.ts +64 -0
  158. package/src/lib/ui/compile/attrLiftPosition.ts +18 -0
  159. package/src/lib/ui/compile/cachedSourceFile.ts +40 -0
  160. package/src/lib/ui/compile/catchBinding.ts +7 -5
  161. package/src/lib/ui/compile/classifyInterpolationType.ts +46 -0
  162. package/src/lib/ui/compile/collectAbideDiagnostics.ts +96 -0
  163. package/src/lib/ui/compile/compileComponent.ts +19 -3
  164. package/src/lib/ui/compile/compileModule.ts +27 -58
  165. package/src/lib/ui/compile/compileSSR.ts +51 -12
  166. package/src/lib/ui/compile/compileShadow.ts +145 -18
  167. package/src/lib/ui/compile/composeProps.ts +12 -2
  168. package/src/lib/ui/compile/createShadowLanguageService.ts +116 -68
  169. package/src/lib/ui/compile/createShadowProgram.ts +35 -6
  170. package/src/lib/ui/compile/declaredNames.ts +36 -0
  171. package/src/lib/ui/compile/desugarSignals.ts +418 -36
  172. package/src/lib/ui/compile/expressionIsPrefixEvaluable.ts +19 -0
  173. package/src/lib/ui/compile/generateBuild.ts +52 -9
  174. package/src/lib/ui/compile/generateSSR.ts +242 -43
  175. package/src/lib/ui/compile/hoistableAwaits.ts +193 -0
  176. package/src/lib/ui/compile/hoistableChildRenders.ts +112 -0
  177. package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +3 -1
  178. package/src/lib/ui/compile/interpolationClassifierForRoot.ts +37 -0
  179. package/src/lib/ui/compile/isSpuriousAsyncReadDiagnostic.ts +174 -0
  180. package/src/lib/ui/compile/liftAsyncSubExpressions.ts +166 -0
  181. package/src/lib/ui/compile/lowerAsyncInterpolations.ts +92 -0
  182. package/src/lib/ui/compile/lowerContext.ts +10 -0
  183. package/src/lib/ui/compile/lowerDocAccess.ts +26 -23
  184. package/src/lib/ui/compile/lowerScript.ts +40 -4
  185. package/src/lib/ui/compile/nodeAtShadowOffset.ts +28 -0
  186. package/src/lib/ui/compile/parseTemplate.ts +18 -1092
  187. package/src/lib/ui/compile/parseTemplateRecovering.ts +1385 -0
  188. package/src/lib/ui/compile/referencedIdentifiers.ts +35 -0
  189. package/src/lib/ui/compile/renameSignalRefs.ts +21 -22
  190. package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +65 -0
  191. package/src/lib/ui/compile/shadowInterpolationClassifier.ts +47 -0
  192. package/src/lib/ui/compile/sourceFileOptionsSignature.ts +20 -0
  193. package/src/lib/ui/compile/structuralHeadTokens.ts +107 -0
  194. package/src/lib/ui/compile/templateSemanticTokens.ts +21 -0
  195. package/src/lib/ui/compile/templateStartOffset.ts +15 -0
  196. package/src/lib/ui/compile/tryPlan.ts +4 -3
  197. package/src/lib/ui/compile/types/AnalyzedComponent.ts +4 -0
  198. package/src/lib/ui/compile/types/AsyncInterpolationField.ts +19 -0
  199. package/src/lib/ui/compile/types/InterpolationClassifier.ts +12 -0
  200. package/src/lib/ui/compile/types/InterpolationKind.ts +7 -0
  201. package/src/lib/ui/compile/types/ParseDiagnostic.ts +8 -0
  202. package/src/lib/ui/compile/types/SeedTypeClassifier.ts +15 -0
  203. package/src/lib/ui/compile/types/TemplateNode.ts +36 -4
  204. package/src/lib/ui/compile/types/ValuePositionInterpolation.ts +13 -0
  205. package/src/lib/ui/compile/writtenTemplateNames.ts +84 -0
  206. package/src/lib/ui/computed.ts +28 -1
  207. package/src/lib/ui/createScope.ts +35 -68
  208. package/src/lib/ui/dom/anchoredBranch.ts +142 -0
  209. package/src/lib/ui/dom/appendText.ts +8 -3
  210. package/src/lib/ui/dom/awaitBlock.ts +43 -94
  211. package/src/lib/ui/dom/bindProp.ts +22 -0
  212. package/src/lib/ui/dom/bindableProp.ts +47 -0
  213. package/src/lib/ui/dom/cellPending.ts +24 -0
  214. package/src/lib/ui/dom/disposeRange.ts +2 -1
  215. package/src/lib/ui/dom/each.ts +26 -4
  216. package/src/lib/ui/dom/eachAsync.ts +39 -3
  217. package/src/lib/ui/dom/fillBoundary.ts +2 -3
  218. package/src/lib/ui/dom/fillRange.ts +4 -4
  219. package/src/lib/ui/dom/hydrate.ts +6 -12
  220. package/src/lib/ui/dom/isComment.ts +1 -1
  221. package/src/lib/ui/dom/matchingRangeClose.ts +29 -0
  222. package/src/lib/ui/dom/mount.ts +1 -2
  223. package/src/lib/ui/dom/mountChild.ts +9 -40
  224. package/src/lib/ui/dom/mountRange.ts +2 -3
  225. package/src/lib/ui/dom/mountStreamedChild.ts +84 -0
  226. package/src/lib/ui/dom/mountSwappableRange.ts +46 -4
  227. package/src/lib/ui/dom/mutateDocContainer.ts +65 -0
  228. package/src/lib/ui/dom/on.ts +2 -2
  229. package/src/lib/ui/dom/readCell.ts +40 -0
  230. package/src/lib/ui/dom/switchBlock.ts +30 -7
  231. package/src/lib/ui/dom/tryBlock.ts +230 -66
  232. package/src/lib/ui/dom/types/SwitchCase.ts +5 -1
  233. package/src/lib/ui/dom/when.ts +12 -2
  234. package/src/lib/ui/dom/withScope.ts +2 -2
  235. package/src/lib/ui/finalizeStreamedChildren.ts +89 -0
  236. package/src/lib/ui/flight.ts +56 -0
  237. package/src/lib/ui/html.ts +12 -1
  238. package/src/lib/ui/isolateCellBarrier.ts +17 -0
  239. package/src/lib/ui/linked.ts +48 -2
  240. package/src/lib/ui/remoteProxy.ts +84 -159
  241. package/src/lib/ui/renderChain.ts +49 -13
  242. package/src/lib/ui/renderToStream.ts +22 -18
  243. package/src/lib/ui/resumeSeedScript.ts +1 -1
  244. package/src/lib/ui/router.ts +63 -36
  245. package/src/lib/ui/runtime/AsyncCellError.ts +20 -0
  246. package/src/lib/ui/runtime/CELL_SEED.ts +14 -0
  247. package/src/lib/ui/runtime/CURRENT_BOUNDARY.ts +11 -0
  248. package/src/lib/ui/runtime/CURRENT_PATH.ts +29 -0
  249. package/src/lib/ui/runtime/RENDER.ts +10 -7
  250. package/src/lib/ui/runtime/RESUME.ts +4 -4
  251. package/src/lib/ui/runtime/STREAMED_CELLS.ts +57 -0
  252. package/src/lib/ui/runtime/ambientPathBacking.ts +32 -0
  253. package/src/lib/ui/runtime/applyPatchToTree.ts +1 -1
  254. package/src/lib/ui/runtime/blockId.ts +31 -0
  255. package/src/lib/ui/runtime/boundaryFor.ts +11 -0
  256. package/src/lib/ui/runtime/cellBarrierBacking.ts +30 -0
  257. package/src/lib/ui/runtime/createAsyncCell.ts +300 -0
  258. package/src/lib/ui/runtime/createDoc.ts +3 -42
  259. package/src/lib/ui/runtime/createEffectNode.ts +9 -0
  260. package/src/lib/ui/runtime/enterRenderPass.ts +5 -4
  261. package/src/lib/ui/runtime/flushEffects.ts +16 -6
  262. package/src/lib/ui/runtime/isAsyncFunction.ts +14 -0
  263. package/src/lib/ui/runtime/nextBlockId.ts +8 -7
  264. package/src/lib/ui/runtime/renderPath.ts +16 -0
  265. package/src/lib/ui/runtime/types/Boundary.ts +9 -0
  266. package/src/lib/ui/runtime/types/RenderContext.ts +9 -7
  267. package/src/lib/ui/runtime/types/SsrRender.ts +9 -2
  268. package/src/lib/ui/runtime/types/UiComponent.ts +1 -5
  269. package/src/lib/ui/runtime/withOptionalPath.ts +8 -0
  270. package/src/lib/ui/runtime/withPath.ts +16 -0
  271. package/src/lib/ui/runtime/withPathFrom.ts +20 -0
  272. package/src/lib/ui/seedStreamedResolution.ts +31 -6
  273. package/src/lib/ui/settleAsyncCells.ts +24 -0
  274. package/src/lib/ui/socketProxy.ts +1 -1
  275. package/src/lib/ui/startClient.ts +16 -31
  276. package/src/lib/ui/trackedComputed.ts +68 -0
  277. package/src/lib/ui/types/Scope.ts +8 -24
  278. package/src/lib/ui/watch.ts +3 -3
  279. package/src/serverEntry.ts +14 -0
  280. package/template/src/server/rpc/getHello.ts +15 -13
  281. package/template/test/app.test.ts +1 -1
  282. package/src/lib/server/runtime/devHotModuleResponse.ts +0 -41
  283. package/src/lib/shared/DEV_HOT_PREFIX.ts +0 -7
  284. package/src/lib/shared/UNREACHABLE_STATUSES.ts +0 -13
  285. package/src/lib/shared/hasReplayableRequest.ts +0 -17
  286. package/src/lib/shared/hydratingSlot.ts +0 -12
  287. package/src/lib/shared/outboxProbeSlot.ts +0 -20
  288. package/src/lib/shared/types/Outbox.ts +0 -9
  289. package/src/lib/shared/types/OutboxEntry.ts +0 -27
  290. package/src/lib/shared/types/SmartReadOptions.ts +0 -36
  291. package/src/lib/shared/wakeHydrationPeeks.ts +0 -20
  292. package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +0 -5
  293. package/src/lib/ui/compile/REACTIVE_CALLEES.ts +0 -11
  294. package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +0 -80
  295. package/src/lib/ui/compile/markupTokens.ts +0 -313
  296. package/src/lib/ui/compile/structuralBlockTokens.ts +0 -100
  297. package/src/lib/ui/dom/applyResolved.ts +0 -87
  298. package/src/lib/ui/dom/mutateDocArray.ts +0 -38
  299. package/src/lib/ui/dom/scopeLabel.ts +0 -21
  300. package/src/lib/ui/dom/text.ts +0 -20
  301. package/src/lib/ui/history.ts +0 -108
  302. package/src/lib/ui/installHotBridge.ts +0 -95
  303. package/src/lib/ui/installInspectorBridge.ts +0 -140
  304. package/src/lib/ui/outbox.ts +0 -35
  305. package/src/lib/ui/persist.ts +0 -115
  306. package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +0 -281
  307. package/src/lib/ui/rpcOutbox/outboxRegistry.ts +0 -69
  308. package/src/lib/ui/runtime/PATCH_BUS.ts +0 -28
  309. package/src/lib/ui/runtime/captureModelDoc.ts +0 -43
  310. package/src/lib/ui/runtime/hotInstances.ts +0 -10
  311. package/src/lib/ui/runtime/hotReloadEnabled.ts +0 -8
  312. package/src/lib/ui/runtime/hotReplace.ts +0 -38
  313. package/src/lib/ui/runtime/liveScopes.ts +0 -15
  314. package/src/lib/ui/runtime/localStoragePersistence.ts +0 -47
  315. package/src/lib/ui/runtime/registerHotInstance.ts +0 -23
  316. package/src/lib/ui/runtime/seedModelDoc.ts +0 -26
  317. package/src/lib/ui/runtime/types/HotInstance.ts +0 -22
  318. package/src/lib/ui/runtime/types/PatchEvent.ts +0 -16
  319. package/src/lib/ui/seedResolved.ts +0 -28
  320. package/src/lib/ui/sync.ts +0 -48
  321. package/src/lib/ui/types/History.ts +0 -14
  322. package/src/lib/ui/types/PersistHandle.ts +0 -11
  323. package/src/lib/ui/types/PersistenceStore.ts +0 -12
  324. package/src/lib/ui/types/ResolvedFrame.ts +0 -15
  325. package/src/lib/ui/types/SyncTransport.ts +0 -13
@@ -0,0 +1,23 @@
1
+ import type { ErrorJsonSchemas } from './ErrorJsonSchemas.ts'
2
+ import type { InputCoercion } from './InputCoercion.ts'
3
+ import type { OutputWirePlan } from './OutputWirePlan.ts'
4
+
5
+ /*
6
+ The build-time facts the warm rpc server program resolves for one `$rpc/**` handler, gathered into
7
+ a single named payload that `prepareRpcModule` destructures (each rewrite direction picks the fields
8
+ it needs). Replaces a positional argument list where ordering was load-bearing — a new build stamp
9
+ is now one field on this shape rather than a new positional slot every caller must thread. Every
10
+ field is optional: an absent field means the warm program resolved no verdict (no program built, an
11
+ unresolvable node), and the rewrite falls open to its char-scan / today's behaviour exactly as
12
+ before. `streaming` overrides the char-scan streaming verdict; the rest are stamped as server opts
13
+ (`coercion`→`coerce`, `inputSchema`→`inputJsonSchema`, `outputSchema`→`outputJsonSchema`,
14
+ `errorSchemas`→`errorJsonSchemas`) or the client opt (`outputWirePlan`).
15
+ */
16
+ export type RpcBuildStamps = {
17
+ streaming?: boolean
18
+ coercion?: InputCoercion
19
+ inputSchema?: Record<string, unknown>
20
+ outputSchema?: Record<string, unknown>
21
+ errorSchemas?: ErrorJsonSchemas
22
+ outputWirePlan?: OutputWirePlan
23
+ }
@@ -1,6 +1,5 @@
1
1
  import type { HttpError } from '../HttpError.ts'
2
2
  import type { ErrorSpec } from './ErrorSpec.ts'
3
- import type { OutboxEntry } from './OutboxEntry.ts'
4
3
  import type { StandardSchemaV1 } from './StandardSchemaV1.ts'
5
4
  import type { ValidationErrorData } from './ValidationErrorData.ts'
6
5
 
@@ -16,9 +15,9 @@ type DeclaredErrorData<
16
15
  /*
17
16
  A rpc's `isError` guard — branch a caught error on a kind THIS rpc knows, with the
18
17
  data type narrowed from the rpc's own `errors` spec. `rpc.isError(err, 'outOfStock')`
19
- narrows `.data` to that error's payload; the framework-reserved `'validation'` /
20
- `'queued'` to their fixed shapes; any other string narrows `.kind` only (`.data`
21
- stays `unknown` — it belongs to a different rpc, whose type isn't in scope here).
18
+ narrows `.data` to that error's payload; the framework-reserved `'validation'` to its
19
+ fixed shape; any other string narrows `.kind` only (`.data` stays `unknown` — it
20
+ belongs to a different rpc, whose type isn't in scope here).
22
21
 
23
22
  The declared-name overload comes first so an app error shadows the reserved ones on a
24
23
  name collision, and so the kind argument autocompletes to the rpc's declared names.
@@ -32,9 +31,5 @@ export interface RpcErrorGuard<Errors extends ErrorSpec> {
32
31
  error: unknown,
33
32
  kind: 'validation',
34
33
  ): error is HttpError & { kind: 'validation'; data: ValidationErrorData }
35
- (
36
- error: unknown,
37
- kind: 'queued',
38
- ): error is HttpError & { kind: 'queued'; data: OutboxEntry<unknown> }
39
34
  <Kind extends string>(error: unknown, kind: Kind): error is HttpError & { kind: Kind }
40
35
  }
@@ -9,8 +9,8 @@ fan-out and a client-side ws proxy by build target. Iterating the socket
9
9
  is the live stream — no replay. `.tail(count)` opens a subscription seeded
10
10
  with the last `count` frames of the retained tail (declared via
11
11
  `{ tail: n }`; no-arg = the whole retained tail) before going live — the
12
- optional Subscribable retention capability a reactive `watch(socket, …)`
13
- seeds from. `broadcast` is isomorphic: server code broadcasts in-process
12
+ optional NamedAsyncIterable retention capability a reactive `watch(socket, …)`
13
+ seeds from. `publish` is isomorphic: server code publishes in-process
14
14
  and fans out to remote subscribers; client code sends a `pub` frame the
15
15
  dispatcher validates against the topic's `clientPublish` flag. `clients`
16
16
  exposes which adapter surfaces (browser / mcp / cli) advertise this
@@ -21,8 +21,8 @@ export interface Socket<T> extends AsyncIterable<T> {
21
21
  readonly clients: ClientFlags
22
22
  /* Send a frame to every subscriber (server always; client only when `clientPublish` is
23
23
  set). Isomorphic: server code fans out in-process + to remote subscribers; client code
24
- sends a validated `pub` frame. */
25
- broadcast(message: T): void
24
+ sends a validated `pub` frame. Mirrors Bun's `server.publish()`. */
25
+ publish(message: T): void
26
26
  tail(count?: number, hooks?: TailHooks): AsyncIterable<T>
27
27
  /* The latest retained frame, synchronously — `T | undefined` when none. The value
28
28
  member of the probe family for a stream; `peek(socket)` routes here. */
@@ -10,7 +10,8 @@ write-set and read-set can't drift apart on a matching-string-key handshake.
10
10
 
11
11
  Three partitions:
12
12
  - `SsrBootState` fields seed ambient slots via the exhaustive `seedBootState` map;
13
- - `cache` is the inline warm-seed partition, drained through `seedResolved`;
13
+ - `cache` / `cells` are the warm-seed partitions (settled `cache()` values / resolved async-cell
14
+ values), drained into their client stores before mount;
14
15
  - `route`/`params` are stamped for inspectability and are NOT read by the client
15
16
  (the router re-resolves the route from the URL).
16
17
  */
@@ -18,4 +19,7 @@ export type SsrPayload = SsrBootState & {
18
19
  route: string
19
20
  params: Record<string, string>
20
21
  cache?: CacheSnapshotEntry[]
22
+ /* Async-cell values that resolved during SSR, keyed by render-path id → ref-json string.
23
+ `startClient` seeds these into `CELL_SEED`; a hydrating cell reads its key to warm-hydrate. */
24
+ cells?: Record<string, string>
21
25
  }
@@ -0,0 +1,11 @@
1
+ /*
2
+ Endpoint stream policy, declared once on a streaming rpc definition (ADR-0020).
3
+ `n` is the replay depth — how many retained frames a fresh reader gets before
4
+ going live, pairing with the server-side `tail` retention. Endpoint-fixed: a
5
+ late joiner can't request deeper replay than a fresh view. No runtime consumer
6
+ today (subscribableFromResponse ignores it); threaded onto the definition for
7
+ completeness and future use.
8
+ */
9
+ export type StreamPolicy = {
10
+ n?: number
11
+ }
@@ -0,0 +1,19 @@
1
+ /*
2
+ The per-request list of STREAMING async-cell values that SETTLED during this SSR pass, each keyed by
3
+ the cell's serialization-stable render-path id (`${scope.id}:${index}`) (ADR-0035). A streaming cell
4
+ ships pending in the shell (excluded from the pre-mount `__SSR__.cells` warm-seed, which would
5
+ diverge from that pending markup), so instead `createAsyncCell.settleValue` records its resolved
6
+ VALUE here when it settles server-side. The page renderer reads this AFTER the shell — streaming an
7
+ `__abideResolve({ cellKey, value })` chunk per entry — so the client adopts the server-resolved
8
+ value post-hydration (no `loading…` flash) instead of only cold-re-running the seed.
9
+
10
+ Values (not promises) so the drain never AWAITS — a streaming cell may legitimately stay pending
11
+ through the whole request (the `{#if getFoo()}`-holds case), and awaiting it would hang the
12
+ response. Only cells that settled before the drain point are streamed; one still pending is simply
13
+ not (the client cold-runs it, unchanged). The streaming sibling of `ResolvedCells` (the BLOCKING
14
+ warm-seed, read at render-return and stamped into `__SSR__.cells`); this one is read later in the
15
+ stream and shipped out-of-order as chunks.
16
+ */
17
+ export type StreamedCells = {
18
+ entries: { key: string; value: unknown }[]
19
+ }
@@ -1,10 +1,28 @@
1
1
  import type { CacheSnapshotEntry } from './CacheSnapshotEntry.ts'
2
2
 
3
3
  /*
4
- Payload of one streamed `window.__abideResolve(...)` call. A full
5
- `CacheSnapshotEntry` settles the placeholder with warm data; a `{ key, miss }`
6
- marker means the server couldn't snapshot that body (binary, rejected, evicted)
7
- so the client settles the placeholder with a live re-fetch instead. Discriminate
8
- on the `miss` field.
4
+ Payload of one streamed `window.__abideResolve(...)` call. Three arms:
5
+
6
+ - a full `CacheSnapshotEntry` (has `key` + body) settles a cache placeholder with warm data;
7
+ - a `{ key, miss }` marker means the server couldn't snapshot that cache body (binary, rejected,
8
+ evicted) so the client settles the placeholder with a live re-fetch instead;
9
+ - a `{ cellKey, value }` (ADR-0035) carries a STREAMING CELL's server-resolved value, keyed by its
10
+ render-path warm-key and `encodeRefJson`-encoded, applied post-hydration as a reactive update so
11
+ a non-cache peek stops flashing `loading…` on hydrate.
12
+ - a `{ cellSeed, value }` (ADR-0039) carries a STREAMED CHILD's blocking async-cell value that
13
+ resolved AFTER the head `__SSR__.cells` snapshot (its render was deferred to the drain), seeded
14
+ PRE-mount into `CELL_SEED` — the same warm partition as `__SSR__.cells` — so the child's cell
15
+ constructs resolved on hydrate rather than re-running.
16
+
17
+ Discriminate on `cellSeed` then `cellKey` (cell) then `miss` (cache miss) then default (cache entry).
9
18
  */
10
- export type StreamedResolution = CacheSnapshotEntry | { key: string; miss: true }
19
+
20
+ /* The cache arms — a warm snapshot entry or a `{ key, miss }` marker. `streamCacheResolutions`
21
+ yields exactly these (never the cell arm), so its element type is `CacheResolution`, keeping a
22
+ `.key` access on its results well-typed. */
23
+ export type CacheResolution = CacheSnapshotEntry | { key: string; miss: true }
24
+
25
+ export type StreamedResolution =
26
+ | CacheResolution
27
+ | { cellKey: string; value: string }
28
+ | { cellSeed: string; value: string }
@@ -1,5 +1,5 @@
1
1
  /*
2
- Hooks a `tail(count, hooks)` caller passes to a Subscribable's retention
2
+ Hooks a `tail(count, hooks)` caller passes to a NamedAsyncIterable's retention
3
3
  capability.
4
4
  `replayed` must be signalled in-band exactly once per iteration — after the
5
5
  last replayed frame, before any live frame, and even when nothing was
@@ -0,0 +1,11 @@
1
+ /*
2
+ The wire codec kind for a single top-level Args field (ADR-0029). Generalizes ADR-0028's scalar
3
+ `number`/`boolean` query coercion to the structured value kinds the warm server program can name
4
+ through the type graph — a `Date` / `Set` / `Map` by symbol identity, a `bigint` by type flag.
5
+ parseArgs revives a field's plain-JSON wire form into the runtime value the handler's declared
6
+ type expects: an ISO string → `Date`, a numeric string → `bigint`, a JSON array → `Set`, a JSON
7
+ entries array (or object) → `Map`. Top-level fields only in this increment — a structured value
8
+ NESTED inside another value is not descended into. Reviving is fail-open: an unrevivable value is
9
+ left as its JSON form, never thrown, so the plain-JSON / OpenAPI contract still works.
10
+ */
11
+ export type WireKind = 'number' | 'boolean' | 'date' | 'bigint' | 'set' | 'map'
@@ -0,0 +1,33 @@
1
+ import { fieldErrorsFromIssues } from './fieldErrorsFromIssues.ts'
2
+ import { HttpError } from './HttpError.ts'
3
+ import type { StandardSchemaV1 } from './types/StandardSchemaV1.ts'
4
+ import type { ValidationErrorData } from './types/ValidationErrorData.ts'
5
+
6
+ /*
7
+ Builds the HttpError a client-side pre-flight validation failure throws (ADR-0026 D2),
8
+ shaped IDENTICALLY to the one a caller gets when the server rejects the same input with a
9
+ 422: `kind: 'validation'`, `status: 422`, `data: ValidationErrorData` (`{ issues, fields }`),
10
+ so an `error instanceof HttpError && error.kind === 'validation'` branch — and the
11
+ `error.data.fields` form-error map — read the same whether the rejection came from the
12
+ client pre-flight or the server.
13
+
14
+ This is a UX optimization ONLY, never a trust boundary: the server's unconditional
15
+ `inputSchema['~standard'].validate` → `validationError` 422 (defineRpc.ts) stays
16
+ authoritative. A client that skips or fakes this check is still fully validated on the
17
+ server.
18
+
19
+ The `data` mapping (`fieldErrorsFromIssues`) is the same isomorphic helper the server 422
20
+ uses; the response envelope (`{ $abideError, data }`, status 422, reason phrase) mirrors
21
+ `typedErrorResponse('validation', 422, …)` — reconstructed here with a plain web-standard
22
+ `Response` so the browser bundle never imports the server's Response builder. The status
23
+ text mirrors `STATUS_TEXT[422]`.
24
+ */
25
+ export function validationHttpError(issues: readonly StandardSchemaV1.Issue[]): HttpError {
26
+ const data: ValidationErrorData = { issues, fields: fieldErrorsFromIssues(issues) }
27
+ const response = new Response(JSON.stringify({ $abideError: 'validation', data }), {
28
+ status: 422,
29
+ statusText: 'Unprocessable Content',
30
+ headers: { 'content-type': 'application/json' },
31
+ })
32
+ return new HttpError(response, 'validation', data)
33
+ }
@@ -0,0 +1,16 @@
1
+ /*
2
+ The single source of the async-cell warm-seed key format: the `${scopeId}:${cellIndex}` string that
3
+ pairs a cell's serialization-stable render-path scope id with its per-scope declaration ordinal.
4
+
5
+ Both sides of the SSR→client handoff mint the key through THIS callable — the isomorphic
6
+ `createAsyncCell` runs it server-side to key its `resolvedCells`/`streamedCells` entries (which the
7
+ page renderer stamps into `__SSR__.cells` / streams as `{ cellKey }` / `{ cellSeed }` chunks), and
8
+ client-side to look the value up in `CELL_SEED`. Because the two sides now invoke one function, the
9
+ byte-for-byte agreement the warm-seed depends on is a shared definition rather than a template
10
+ literal re-typed at each call site: changing the delimiter here changes it for both sides at once,
11
+ so the SSR-text-≡-first-client-render mismatch class (ADR-0033) cannot silently reopen from a
12
+ one-sided edit. Pure so it stays trivially testable and inlinable.
13
+ */
14
+ export function warmSeedKey(scopeId: string, cellIndex: number): string {
15
+ return `${scopeId}:${cellIndex}`
16
+ }
@@ -0,0 +1,30 @@
1
+ /*
2
+ The value-directed wire ENCODE step for a rpc response body (ADR-0029 output path). A
3
+ `JSON.stringify` replacer that rewrites the structured runtime values plain JSON can't carry into
4
+ their honest-JSON wire form, so `json()` never drops a `Set`/`Map` to `{}` or throws on a `bigint`:
5
+
6
+ - `bigint` → its digit string (plain JSON.stringify throws on a bigint)
7
+ - `Set<T>` → a JSON array of its values (plain JSON.stringify emits `{}`)
8
+ - `Map<K,V>`→ a JSON array of `[K,V]` entries (uniform, carries non-string keys)
9
+
10
+ A `Date` needs no branch — its native `toJSON` already rewrote it to an ISO string before the
11
+ replacer runs. Value-directed (not type-directed), so it runs for EVERY client — a non-abide caller
12
+ (curl / an OpenAPI SDK) receives the same honest JSON the projected schema (`jsonSchemaForType`)
13
+ describes. It is naturally recursive: a returned array/object member surfaces through the same
14
+ replacer, so a `Set` nested inside an object still encodes. Never throws — an ordinary value falls
15
+ through untouched.
16
+ */
17
+ export function wireJsonReplacer(_key: string, value: unknown): unknown {
18
+ if (typeof value === 'bigint') {
19
+ return value.toString()
20
+ }
21
+ if (value instanceof Set) {
22
+ return [...value]
23
+ }
24
+ if (value instanceof Map) {
25
+ /* A Map iterates as `[key, value]` pairs, so the spread yields the entries array the
26
+ client output plan (and the projected schema) expects. */
27
+ return [...value]
28
+ }
29
+ return value
30
+ }
@@ -1,9 +1,11 @@
1
+ import { resolve } from 'node:path'
1
2
  import { augmentModule } from './augmentModule.ts'
2
3
  import { carriesBodyArgs } from './carriesBodyArgs.ts'
3
4
  import { detectRpcMethod } from './detectRpcMethod.ts'
4
5
  import { fileStem } from './fileStem.ts'
5
6
  import { RPC_ARGS_TYPE } from './RPC_ARGS_TYPE.ts'
6
7
  import { rpcUrlForFile } from './rpcUrlForFile.ts'
8
+ import type { HttpMethod } from './types/HttpMethod.ts'
7
9
  import { writeDts } from './writeDts.ts'
8
10
 
9
11
  /*
@@ -22,15 +24,23 @@ export async function writeRpcDts({
22
24
  rpcDir,
23
25
  rpcFiles,
24
26
  importName,
27
+ methodForModule,
25
28
  }: {
26
29
  cwd: string
27
30
  rpcDir: string
28
31
  rpcFiles: string[]
29
32
  importName: string
33
+ /* Warm server-program method resolver (ADR-0025 D2): reads the method off the export's
34
+ helper symbol so an aliased/re-exported helper resolves where `detectRpcMethod`'s
35
+ `RPC_EXPORT` regex misses it. Absent (no warm program) or undefined-per-file falls open to
36
+ the regex, byte-identical to before. */
37
+ methodForModule?: (modulePath: string) => HttpMethod | undefined
30
38
  }): Promise<void> {
31
39
  const pairs = await Promise.all(
32
40
  rpcFiles.map(async (file): Promise<[string, string] | undefined> => {
33
- const method = detectRpcMethod(await Bun.file(`${rpcDir}/${file}`).text())
41
+ const method =
42
+ methodForModule?.(resolve(rpcDir, file)) ??
43
+ detectRpcMethod(await Bun.file(`${rpcDir}/${file}`).text())
34
44
  // A body rpc's args can't ride a URL — leave it out of the url() rpc map.
35
45
  if (!method || carriesBodyArgs(method)) {
36
46
  return undefined
@@ -8,7 +8,7 @@ Emits a `.d.ts` that augments createTestApp's `SocketClient` interface with one
8
8
  entry per $socket, keyed by socket name (the same key `app.sockets.<name>`
9
9
  resolves at runtime). The `socket(...)` helper statically returns `Socket<T>`,
10
10
  so each entry is the export's own type directly — `app.sockets.chat` types as
11
- `Socket<ChatMessage>`, iterable for the live stream with `.tail`/`.broadcast`.
11
+ `Socket<ChatMessage>`, iterable for the live stream with `.tail`/`.publish`.
12
12
  Written to `src/.abide/testSockets.d.ts` like its rpc sibling.
13
13
  */
14
14
  export async function writeTestSocketsDts({
@@ -39,8 +39,11 @@ import { commandNameForUrl } from '../shared/commandNameForUrl.ts'
39
39
  import { createCacheStore } from '../shared/createCacheStore.ts'
40
40
  import { HEALTH_PATH } from '../shared/HEALTH_PATH.ts'
41
41
  import { pageSlot } from '../shared/pageSlot.ts'
42
+ import { pendingAsyncCellsSlot } from '../shared/pendingAsyncCellsSlot.ts'
43
+ import { resolvedCellsSlot } from '../shared/resolvedCellsSlot.ts'
42
44
  import { SOCKETS_PATH } from '../shared/SOCKETS_PATH.ts'
43
45
  import { sharedCacheStoreSlot } from '../shared/sharedCacheStoreSlot.ts'
46
+ import { streamedCellsSlot } from '../shared/streamedCellsSlot.ts'
44
47
  import type { RemoteFunction } from '../shared/types/RemoteFunction.ts'
45
48
  import type { Socket } from '../shared/types/Socket.ts'
46
49
  import { createTestSocketChannel } from './createTestSocketChannel.ts'
@@ -72,7 +75,7 @@ export type TestApp = {
72
75
  /* Rpc calls over HTTP, keyed by command name: `app.rpc.getProduct({ id })`. */
73
76
  rpc: RpcClient
74
77
  /* Sockets keyed by name: `app.sockets.ticker` is the Socket — iterate it for
75
- the live stream, `.tail(n)` to seed, `.broadcast(m)` to send. */
78
+ the live stream, `.tail(n)` to seed, `.publish(m)` to send. */
76
79
  sockets: SocketClient
77
80
  /* The /__abide/health payload, decoded. */
78
81
  health: () => Promise<unknown>
@@ -101,12 +104,24 @@ export async function createTestApp(): Promise<TestApp> {
101
104
  sharedResolver: sharedCacheStoreSlot.resolver,
102
105
  pageResolver: pageSlot.resolver,
103
106
  baseResolver: baseSlot.resolver,
107
+ pendingAsyncCellsResolver: pendingAsyncCellsSlot.resolver,
108
+ resolvedCellsResolver: resolvedCellsSlot.resolver,
109
+ streamedCellsResolver: streamedCellsSlot.resolver,
104
110
  activeServer: serverSlot.active,
105
111
  }
106
112
 
107
113
  const sharedStore = createCacheStore()
108
114
  sharedCacheStoreSlot.resolver = () => sharedStore
109
115
  cacheStoreSlot.resolver = () => requestContext.getStore()?.cache ?? sharedStore
116
+ const sharedPendingAsyncCells = { promises: [] }
117
+ pendingAsyncCellsSlot.resolver = () =>
118
+ requestContext.getStore()?.pendingAsyncCells ?? sharedPendingAsyncCells
119
+ const sharedResolvedCells = { entries: [] }
120
+ resolvedCellsSlot.resolver = () =>
121
+ requestContext.getStore()?.resolvedCells ?? sharedResolvedCells
122
+ const sharedStreamedCells = { entries: [] }
123
+ streamedCellsSlot.resolver = () =>
124
+ requestContext.getStore()?.streamedCells ?? sharedStreamedCells
110
125
  pageSlot.resolver = resolvePageSnapshot
111
126
 
112
127
  /* Eager env validation, exactly as serverEntry: a top-level env(schema) in
@@ -188,6 +203,9 @@ export async function createTestApp(): Promise<TestApp> {
188
203
  sharedCacheStoreSlot.resolver = previous.sharedResolver
189
204
  pageSlot.resolver = previous.pageResolver
190
205
  baseSlot.resolver = previous.baseResolver
206
+ pendingAsyncCellsSlot.resolver = previous.pendingAsyncCellsResolver
207
+ resolvedCellsSlot.resolver = previous.resolvedCellsResolver
208
+ streamedCellsSlot.resolver = previous.streamedCellsResolver
191
209
  serverSlot.active = previous.activeServer
192
210
  }
193
211
 
@@ -61,7 +61,7 @@ write-path microbench this runs ~20× faster than a deep-proxy signal baseline.
61
61
 
62
62
  - `compileSSR` renders to an HTML string (byte-identical to the client DOM).
63
63
  - `renderToStream` streams: pending shell first, then resolved `<template await>`
64
- fragments out of order as their promises settle; `applyResolved` swaps them in.
64
+ fragments out of order as their promises settle; the inline swap script swaps them in.
65
65
  - `hydrate` adopts the server DOM in place (no re-render) for static-structure
66
66
  components — claims existing nodes, splits merged text, wires effects/listeners.
67
67
 
@@ -0,0 +1,14 @@
1
+ import { pendingAsyncCellsSlot } from '../shared/pendingAsyncCellsSlot.ts'
2
+ import type { PendingAsyncCells } from '../shared/types/PendingAsyncCells.ts'
3
+ import { cellBarrierBacking } from './runtime/cellBarrierBacking.ts'
4
+
5
+ /*
6
+ The async-cell barrier list a registration (`createAsyncCell`) or a drain (`settleAsyncCells`) should
7
+ use RIGHT NOW: a per-render isolated list if one is active (a hoisted concurrent child render — see
8
+ `isolateCellBarrier`), else the per-request list from `pendingAsyncCellsSlot`. The single seam both
9
+ barrier consumers read, so an isolated render can't leak its in-flight cells into a sibling's or the
10
+ page's drain. Undefined only when neither a render override nor a request/fallback list is installed.
11
+ */
12
+ export function activePendingCells(): PendingAsyncCells | undefined {
13
+ return cellBarrierBacking.active.current() ?? pendingAsyncCellsSlot.get()
14
+ }
@@ -0,0 +1,21 @@
1
+ /*
2
+ The control-block vocabulary — the ONE source of truth shared by the parser
3
+ (`parseTemplate`'s dispatch/error) and the LSP highlighter (`structuralHeadTokens`, driven
4
+ by the parse walk), so the keywords the highlighter colors can't drift from the blocks the
5
+ parser accepts (the `{#snippet}` head once went uncolored because a hand-copy omitted it).
6
+
7
+ `BLOCK_OPENERS` follow `{#`, `BLOCK_CONNECTORS` follow `{:`. `for await` is the async-each
8
+ opener form the highlighter colors as one phrase (the parser reads the `await` inside a
9
+ `for` head); it is not a distinct opener.
10
+ */
11
+ export const BLOCK_OPENERS = ['if', 'for', 'await', 'switch', 'try', 'snippet'] as const
12
+
13
+ export const BLOCK_CONNECTORS = [
14
+ 'else if',
15
+ 'else',
16
+ 'then',
17
+ 'catch',
18
+ 'finally',
19
+ 'case',
20
+ 'default',
21
+ ] as const
@@ -17,7 +17,10 @@ across bundles. Emitted inline (not imported) so the generated render module is
17
17
  self-contained.
18
18
  */
19
19
  export const SSR_ESCAPE =
20
- 'const $esc = (v) => String(v).replace(/[&<>"\']/g, (c) => ' +
20
+ // Nullish escapes to empty text, never the literal `"undefined"` — a pending async
21
+ // read (ADR-0032 D3) renders nothing. `$attr` guards nullish before it calls `$esc`,
22
+ // so its whole-value undefined still drops the attribute.
23
+ 'const $esc = (v) => (v == null ? "" : String(v)).replace(/[&<>"\']/g, (c) => ' +
21
24
  "({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' })[c]);\n" +
22
25
  'const $attr = (n, v) => (v === false || v === null || v === undefined) ? "" ' +
23
26
  ': v === true ? (" " + n) : (" " + n + \'="\' + $esc(v) + \'"\');\n' +
@@ -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,15 +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' },
52
- { name: 'mutateDocArray', specifier: 'ui/dom/mutateDocArray', alias: '$$mutateDocArray' },
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
+ },
53
79
  { name: 'hydrate', specifier: 'ui/dom/hydrate', alias: '$$hydrate' },
54
80
  { name: 'escapeKey', specifier: 'ui/runtime/escapeKey', alias: '$$escapeKey' },
55
81
  { name: 'nextBlockId', specifier: 'ui/runtime/nextBlockId', alias: '$$nextBlockId' },
82
+ { name: 'blockId', specifier: 'ui/runtime/blockId', alias: '$$blockId' },
56
83
  {
57
84
  name: 'enterRenderPass',
58
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