@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,20 @@
1
+ import type { AsyncComputed } from '../../shared/types/AsyncComputed.ts'
2
+
3
+ /*
4
+ The throw a compiled async-cell read (`$$readCell`) raises when the cell is in error
5
+ with no retained value — routed to the nearest reactive `{#try}`. It carries the
6
+ originating cell so the boundary can subscribe to that cell's lifecycle and re-arm
7
+ the guarded content when the cell recovers (error→value via a `refresh()` or a
8
+ dependency change) — "keep watching what threw" (ADR-0019 D3.3). A plain `peek()` in
9
+ JS never throws; the throw is a template-lowering behaviour only, so the cause is
10
+ carried through unchanged for an author's `{:catch err}` binding.
11
+ */
12
+ export class AsyncCellError extends Error {
13
+ readonly cell: AsyncComputed<unknown>
14
+
15
+ constructor(cell: AsyncComputed<unknown>, cause: unknown) {
16
+ super(cause instanceof Error ? cause.message : String(cause), { cause })
17
+ this.name = 'AsyncCellError'
18
+ this.cell = cell
19
+ }
20
+ }
@@ -0,0 +1,14 @@
1
+ /* The async-cell warm-seed manifest: an SSR-resolved cell value, keyed by the cell's
2
+ serialization-stable render-path id (`${scope.id}:${index}`), as a ref-json-encoded STRING
3
+ (decoded lazily at the read site in `createAsyncCell`, so an in-process graph with cycles /
4
+ shared back-references survives where JSON would drop it — the same codec `RESUME` uses).
5
+ `startClient` drains `__SSR__.cells` into here before mount; a hydrating cell reads its key to
6
+ seed its value warm instead of re-running the seed (no refetch, no pending flash).
7
+
8
+ Backed by `globalThis.__abideCells` so an inline pre-bundle script and the framework share one
9
+ store — whoever runs first creates it, the other adopts the same reference (mirrors RESUME). */
10
+ const globalScope = globalThis as { __abideCells?: Record<string, string> }
11
+ globalScope.__abideCells ??= {}
12
+
13
+ // @documentation plumbing
14
+ export const CELL_SEED: Record<string, string> = globalScope.__abideCells
@@ -0,0 +1,11 @@
1
+ import type { Boundary } from './types/Boundary.ts'
2
+
3
+ /*
4
+ The ambient reactive error boundary — the `{#try}` mirror of `CURRENT_SCOPE`. A `tryBlock`
5
+ sets `current` to its boundary around the guarded build so every effect created inside
6
+ associates with it (`boundaryFor`), then restores the previous value (boundaries nest, the
7
+ innermost wins). Undefined outside any `{#try}`; a plain mutable holder is enough — the
8
+ value is only read on the cold path where an effect is created inside a boundary, never on
9
+ the reactive hot path.
10
+ */
11
+ export const CURRENT_BOUNDARY: { current: Boundary | undefined } = { current: undefined }
@@ -0,0 +1,29 @@
1
+ import { ambientPathBacking } from './ambientPathBacking.ts'
2
+
3
+ /*
4
+ The ambient RENDER-PATH — a serialization-stable lexical id for "where in the render tree
5
+ am I", composed top-down as the tree builds. Each nesting site pushes a segment for the
6
+ duration of its subtree (route → layout layer → slot → `{#each}` row key → `{#if}`/`{#switch}`
7
+ branch → child-component ordinal), and a lexical scope (`createScope`) snapshots the current
8
+ value as its id (replacing the old process-local `scope-${n}` counter, which restarted every
9
+ run and drifted on any SSR/client divergence). Because the path is built from compiler-stamped
10
+ positions and runtime data keys — NOT a creation-order counter — it is order-independent
11
+ (streaming can't shift it), and it self-handles SSR/client branch divergence: different branches
12
+ compose different paths, so a value keyed by one never mis-adopts under the other.
13
+
14
+ Segments are joined with `/` and each is `escapeKey`-escaped (RFC 6901), so a segment holding
15
+ a `/` (a URL-shaped row key) stays one segment. `current` is `''` outside any render (a detached
16
+ `scope()` then falls back to the counter for in-run uniqueness).
17
+
18
+ `current` reads through a SWAPPABLE backing (`ambientPathBacking`) rather than a raw field, and is
19
+ READ-ONLY: a segment is established only by `withPath`/`withPathFrom` calling the backing's `run`
20
+ (ADR-0033 D1), never by assigning `current`. The default backing is a module variable (correct on
21
+ the client, one render tree), but the server installs an AsyncLocalStorage-backed `run` so the
22
+ composed path survives the inline `await`s an SSR render suspends on (the cell barrier, child
23
+ renders) and stays isolated across concurrent requests. Mirrors `CURRENT_SCOPE`/`ambientScopeBacking`.
24
+ */
25
+ export const CURRENT_PATH: { readonly current: string } = {
26
+ get current(): string {
27
+ return ambientPathBacking.active.get()
28
+ },
29
+ }
@@ -5,11 +5,14 @@ the next server-rendered child node to claim — a node pointer (not an index) s
5
5
  survives nodes a block inserts (anchors) mid-hydration. Helpers claim in build
6
6
  order, which matches the SSR order, advancing the pointer to the next sibling.
7
7
 
8
- `blockId`/`depth` drive the render-pass block-id counter: every `await`/`try` block
9
- draws an id from `blockId` in document order, shared across a component and the
10
- child components it inlines, so ids are globally unique within one render pass (the
11
- SSR stream and client hydration agree on them `RESUME` is keyed by id). `depth`
12
- tracks nesting so the OUTERMOST render/mount resets the counter and a child render/
8
+ `blockCounters`/`depth` drive the render-pass block-id counters: every `await`/`try`
9
+ block draws a path-namespaced id via `nextBlockId` (ADR-0037) `blockCounters` maps
10
+ each render-path to its own 0,1,2… sequence, counted in document order within that
11
+ path. Namespacing by path (not one flat counter shared across a component and the
12
+ children it inlines) is what keeps ids congruent SSR↔client even when sibling child
13
+ renders run concurrently on the server; the client mounts each child synchronously
14
+ under the same path, so both sides compose the same ids (`RESUME` is keyed by them).
15
+ `depth` tracks nesting so the OUTERMOST render/mount clears the map and a child render/
13
16
  mount continues it. See `enterRenderPass`/`nextBlockId`.
14
17
 
15
18
  `namespace` is the ambient foreign-content namespace (SVG/MathML) a control-flow block
@@ -19,12 +22,12 @@ undefined outside foreign content. See `enterNamespace`/`effectiveChildNamespace
19
22
  */
20
23
  export const RENDER: {
21
24
  hydration: { next: Map<Node, Node | null> } | undefined
22
- blockId: number
25
+ blockCounters: Map<string, number>
23
26
  depth: number
24
27
  namespace: string | undefined
25
28
  } = {
26
29
  hydration: undefined,
27
- blockId: 0,
30
+ blockCounters: new Map(),
28
31
  depth: 0,
29
32
  namespace: undefined,
30
33
  }
@@ -1,7 +1,7 @@
1
1
  /* The await-resume manifest: the resolved value (or error) of each streamed
2
2
  `await` block, keyed by its boundary id. The SSR stream serializes each entry
3
- alongside its fragment; the client registers it (via `applyResolved` or the
4
- inline swap script), and hydration reads it so an `await` block adopts the
3
+ alongside its fragment; the client registers it (via the inline swap script),
4
+ and hydration reads it so an `await` block adopts the
5
5
  resolved branch with the real value instead of re-running the promise.
6
6
 
7
7
  Each value is the ref-json-encoded ResumeEntry STRING, not the decoded object:
@@ -16,8 +16,8 @@
16
16
  same reference. */
17
17
  export type ResumeEntry = { ok: true; value: unknown } | { ok: false; error: unknown }
18
18
 
19
- const globalScope = globalThis as { __abideResume?: Record<number, string> }
19
+ const globalScope = globalThis as { __abideResume?: Record<string, string> }
20
20
  globalScope.__abideResume ??= {}
21
21
 
22
22
  // @documentation plumbing
23
- export const RESUME: Record<number, string> = globalScope.__abideResume
23
+ export const RESUME: Record<string, string> = globalScope.__abideResume
@@ -0,0 +1,57 @@
1
+ /*
2
+ Post-mount sink for STREAMING-cell resolutions (ADR-0035). Unlike `CELL_SEED` — the pre-mount
3
+ warm-seed a blocking cell reads at construction — a streaming cell ships pending and its value
4
+ arrives LATER (streamed after the shell, `__abideResolve({ cellKey, value })` →
5
+ `seedStreamedResolution`). So a mounted streaming cell registers an `apply` fn keyed by its
6
+ render-path id, and the streamed value is routed to it — the render-path analogue of the cache
7
+ path's `markLifecycle` wake. A value that arrives before its cell registers is buffered and applied
8
+ on registration (the two orderings race as the shell hydrates while chunks stream in).
9
+
10
+ Backed by `globalThis.__abideStreamedCells` so an inline pre-bundle script and the framework share
11
+ one store, mirroring `CELL_SEED`/`RESUME`.
12
+ */
13
+ const globalScope = globalThis as {
14
+ __abideStreamedCells?: {
15
+ apply: Map<string, (value: unknown) => void>
16
+ buffer: Map<string, unknown>
17
+ }
18
+ }
19
+ globalScope.__abideStreamedCells ??= { apply: new Map(), buffer: new Map() }
20
+
21
+ const store = globalScope.__abideStreamedCells
22
+
23
+ /* Apply a streamed value to its cell as a POST-hydration reactive update. Deferred to a microtask
24
+ so it can NEVER run inside the synchronous hydration mount: the streamed `__abideResolve` chunk
25
+ parses (buffering the value) BEFORE the client mounts, so a synchronous apply at registration
26
+ would set the cell to the resolved value while the SSR DOM still shows the pending text — an
27
+ `assertClaimedText` desync (ADR-0033). The microtask runs after the whole synchronous mount
28
+ tree unwinds, so hydration claims the pending markup first and the value lands as a plain
29
+ reactive update (pending → resolved), congruent with a fresh mount. */
30
+ function deferApply(apply: (value: unknown) => void, value: unknown): void {
31
+ queueMicrotask(() => apply(value))
32
+ }
33
+
34
+ /* A mounted streaming cell offers where to deliver its streamed value. If the value already
35
+ arrived (buffered), schedule it; otherwise hold the apply fn for when it streams in. */
36
+ export function registerStreamedCell(key: string, apply: (value: unknown) => void): void {
37
+ if (store.buffer.has(key)) {
38
+ const value = store.buffer.get(key)
39
+ store.buffer.delete(key)
40
+ deferApply(apply, value)
41
+ return
42
+ }
43
+ store.apply.set(key, apply)
44
+ }
45
+
46
+ /* A streamed value landed for `key`. Deliver it to the registered cell (scheduled post-mount), or
47
+ buffer it until the cell registers. One-shot: the apply fn is consumed (a cell adopts a streamed
48
+ value once; later reactivity is its own reseed). */
49
+ export function receiveStreamedCell(key: string, value: unknown): void {
50
+ const apply = store.apply.get(key)
51
+ if (apply !== undefined) {
52
+ store.apply.delete(key)
53
+ deferApply(apply, value)
54
+ return
55
+ }
56
+ store.buffer.set(key, value)
57
+ }
@@ -0,0 +1,32 @@
1
+ /*
2
+ The swappable backing behind `CURRENT_PATH.current` — the render-path sibling of
3
+ `ambientScopeBacking`. The default is a module variable with a synchronous save/restore `run`
4
+ (correct on the client, whose mount is synchronous — the whole subtree builds and restores before
5
+ control returns, so a slot never has to survive an `await`). The server swaps in an
6
+ AsyncLocalStorage-backed `run` at boot (`installAmbientScopeStore`, ADR-0033 D1), because SSR
7
+ composes the path while `await`ing inline (the Tier-2 cell barrier, child renders) and the pushed
8
+ segment must be inherited by the render's own post-await continuation — a synchronously-restored slot
9
+ cannot follow an async continuation. `run(composed, build)` establishes the composed path for
10
+ `build`; `get()` reads the active path (`''` outside any push). Mirrors `ambientScopeBacking`.
11
+ */
12
+ type PathBacking = {
13
+ run: <T>(composedPath: string, build: () => T) => T
14
+ get: () => string
15
+ }
16
+
17
+ let modulePath = ''
18
+
19
+ export const ambientPathBacking: { active: PathBacking } = {
20
+ active: {
21
+ run: <T>(composedPath: string, build: () => T): T => {
22
+ const previous = modulePath
23
+ modulePath = composedPath
24
+ try {
25
+ return build()
26
+ } finally {
27
+ modulePath = previous
28
+ }
29
+ },
30
+ get: () => modulePath,
31
+ },
32
+ }
@@ -7,7 +7,7 @@ value only when the root itself is replaced). In-place is the deliberate choice:
7
7
  the patch path already names exactly what changed, so we never need copy-on-write
8
8
  to *detect* a change — and cloning the spine made every leaf write O(width) of
9
9
  the widest ancestor (a 5k-item list copied 50k times in the bench). Mutating is
10
- O(depth). History/undo is served by journalling patches (the change is a value),
10
+ O(depth). Change detection is served by announcing patches (the change is a value),
11
11
  not by retaining old roots — which is cheaper anyway. The wake step in createDoc
12
12
  force-notifies ancestor readers from the path, since their container keeps its
13
13
  identity now.
@@ -0,0 +1,31 @@
1
+ import { CURRENT_PATH } from './CURRENT_PATH.ts'
2
+
3
+ /*
4
+ Allocates an await/try block id NAMESPACED by the ambient render-path (ADR-0037). The id is
5
+ `${CURRENT_PATH.current}:${n}` where `n` is a per-path counter incremented in document order —
6
+ so blocks WITHIN one component/branch/row (one path) number 0,1,2… deterministically, while a
7
+ child component (its own path segment) gets its own independent sequence. This replaces the old
8
+ flat monotonic counter shared across a component and the children it inlines.
9
+
10
+ Path-namespacing is what lets sibling child renders run CONCURRENTLY during SSR: their ids no
11
+ longer depend on a globally-sequential draw order (which parallel renders would interleave), only
12
+ on document order within each child's own path — and the server render-path is ALS-backed
13
+ (pathStore), so each child's continuations keep their own path across awaits. The client mounts
14
+ each child synchronously under the same path (mountChild → withPath), so both sides compose
15
+ byte-identical ids and the resume manifest stays keyed congruently.
16
+
17
+ `counters` is the per-render-pass map: the request-local `$ctx` threaded through the SSR render
18
+ tree, or `RENDER.blockCounters` on the client (see nextBlockId). Concurrent sibling renders write
19
+ DIFFERENT path keys, so a shared map needs no locking — each get/set is atomic and per-path.
20
+ */
21
+ // @documentation plumbing
22
+ export function blockId(counters: Map<string, number>): string {
23
+ const path = CURRENT_PATH.current
24
+ const next = counters.get(path) ?? 0
25
+ counters.set(path, next + 1)
26
+ /* Bare (pathless) render — a top-level page whose route key is `''`, or a component rendered
27
+ outside any route in a test — keeps the plain `0,1,2…` form; only a non-empty path qualifies
28
+ the id. Both sides compute the same path, so the branch is congruent, and the common bare case
29
+ stays identical to the pre-ADR-0037 flat counter. */
30
+ return path === '' ? String(next) : `${path}:${next}`
31
+ }
@@ -0,0 +1,11 @@
1
+ import type { Boundary } from './types/Boundary.ts'
2
+ import type { ReactiveNode } from './types/ReactiveNode.ts'
3
+
4
+ /*
5
+ Associates an effect node with the reactive `{#try}` boundary it was created inside. Kept
6
+ OFF the `ReactiveNode` shape — a WeakMap populated only for effects built while
7
+ `CURRENT_BOUNDARY.current` is set — so the node stays monomorphic for signals/computeds/
8
+ effects on the read/write hot path, and this map is consulted only on the cold throw path
9
+ (`flushEffects.drain`'s catch), to route a guarded effect's later-run throw to its boundary.
10
+ */
11
+ export const boundaryFor = new WeakMap<ReactiveNode, Boundary>()
@@ -0,0 +1,30 @@
1
+ import type { PendingAsyncCells } from '../../shared/types/PendingAsyncCells.ts'
2
+
3
+ /*
4
+ The swappable backing for the PER-RENDER async-cell barrier override (ADR-0037 Phase 2). Mirrors
5
+ `ambientPathBacking`: the default is an inert passthrough (no override — the barrier list resolves
6
+ through the per-request `pendingAsyncCellsSlot` as before), and the server installs an
7
+ AsyncLocalStorage-backed variant (`renderCellBarrierStore`) so a hoisted child render can run its
8
+ async cells under its OWN list.
9
+
10
+ Why a per-render override at all: sibling child renders that run CONCURRENTLY (the parallel-SSR
11
+ hoist) each register cells on the request-scoped pending list, and `settleAsyncCells` drains it with
12
+ `splice(0)` — so one child's barrier would drain a sibling's cells, letting that sibling's template
13
+ peek a still-pending value. Running each hoisted child under `isolateCellBarrier` gives it a fresh
14
+ list (via `run`), so `activePendingCells()` returns that list for the child's registrations and its
15
+ own barrier drain — isolated from every concurrent sibling and from the page. `current()` returns
16
+ `undefined` outside any isolated render, so the page's own cells fall through to the request list
17
+ unchanged. Kept out of `node:async_hooks` on the client: the default backing is pure JS, and the ALS
18
+ variant is installed only server-side.
19
+ */
20
+ export const cellBarrierBacking: {
21
+ active: {
22
+ current: () => PendingAsyncCells | undefined
23
+ run: <T>(list: PendingAsyncCells, render: () => T) => T
24
+ }
25
+ } = {
26
+ active: {
27
+ current: () => undefined,
28
+ run: (_list, render) => render(),
29
+ },
30
+ }
@@ -0,0 +1,300 @@
1
+ import { ASYNC_CELL } from '../../shared/ASYNC_CELL.ts'
2
+ import { decodeRefJson } from '../../shared/decodeRefJson.ts'
3
+ import { isAsyncIterable } from '../../shared/isAsyncIterable.ts'
4
+ import { isThenable } from '../../shared/isThenable.ts'
5
+ import { resolvedCellsSlot } from '../../shared/resolvedCellsSlot.ts'
6
+ import { streamedCellsSlot } from '../../shared/streamedCellsSlot.ts'
7
+ import type { AsyncComputed } from '../../shared/types/AsyncComputed.ts'
8
+ import type { AsyncState } from '../../shared/types/AsyncState.ts'
9
+ import type { NamedAsyncIterable } from '../../shared/types/NamedAsyncIterable.ts'
10
+ import { warmSeedKey } from '../../shared/warmSeedKey.ts'
11
+ import { activePendingCells } from '../activePendingCells.ts'
12
+ import type { Scope } from '../types/Scope.ts'
13
+ import { CELL_SEED } from './CELL_SEED.ts'
14
+ import { CURRENT_SCOPE } from './CURRENT_SCOPE.ts'
15
+ import { createEffectNode } from './createEffectNode.ts'
16
+ import { createSignalNode } from './createSignalNode.ts'
17
+ import { readNode } from './readNode.ts'
18
+ import { registerStreamedCell } from './STREAMED_CELLS.ts'
19
+ import { writeNode } from './writeNode.ts'
20
+
21
+ /* The seed's transform gate (mirrors `state`/`linked`): coerces every value entering
22
+ the store, whether from a settling promise, a frame, or a `set()`. */
23
+ type Transform = (next: unknown, previous: unknown) => unknown
24
+
25
+ type AsyncCellOptions = {
26
+ writable: boolean
27
+ transform?: Transform
28
+ /* ADR-0032: a STREAMING cell must NOT join the SSR blocking barrier. A no-`await` async
29
+ value/content position lowers to a cell that ships pending-`undefined` in the shell and
30
+ resolves on the client, rather than blocking the first flush inline (that is the `await`
31
+ tier). Absent/false → today's blocking registration (`await` tier / a keyed bare read).
32
+ A stream (`isAsyncIterable`) never registers regardless — this only gates a promise seed. */
33
+ streaming?: boolean
34
+ }
35
+
36
+ /*
37
+ The source-agnostic async cell — the runtime behind `computed`/`linked` when the seed
38
+ tracks a promise or a NamedAsyncIterable (ADR-0019 D1). It is **eager**: a reactive
39
+ effect runs the seed once at construction (capturing its synchronous dependencies) and
40
+ re-runs — a *reseed* — whenever they change, so independent cells load in parallel and a
41
+ dependent cell re-loads when its dependency settles. The seed's result drives the cell:
42
+
43
+ - a **promise** (an `async () => await …` thunk) → unwrap; resolve → value, reject → error;
44
+ - a **NamedAsyncIterable** (socket / streaming rpc) → `for await` its frames, each frame
45
+ updating the value; the terminal error/close land on `error()` / clear in-flight;
46
+ - a **synchronous value** (a seed that produced one directly) → settle immediately.
47
+
48
+ Read through the probe family only — there is no `.value`. `pending()` is "no value yet",
49
+ `refreshing()` is "a held value while a fresher source is in flight" (a reseed or a
50
+ `refresh()`), `peek()` is the retained value/latest frame (stale-while-revalidate),
51
+ `error()` the last rejection. Every out-of-order settle is guarded by a run id, so a slow
52
+ prior flight never overwrites a newer one. `refresh()` re-invokes the seed keeping the
53
+ value visible; a writable cell adds `set()`, which latches until the next reseed.
54
+ */
55
+ export function createAsyncCell(
56
+ seed: () => unknown,
57
+ options: AsyncCellOptions,
58
+ ): AsyncComputed<unknown> | AsyncState<unknown> {
59
+ const transform = options.transform
60
+ /* Reactive state, each its own signal so a reader subscribes only to the facets it
61
+ touches (peeking the value does not re-run on a pending flip, and vice versa). */
62
+ const valueNode = createSignalNode(undefined)
63
+ const errorNode = createSignalNode(undefined)
64
+ const inFlightNode = createSignalNode(true)
65
+ const hasValueNode = createSignalNode(false)
66
+
67
+ /* This cell's serialization-stable warm-seed key: its scope's render-path id + a per-scope
68
+ index, drawn at construction (in declaration order) so SSR and client agree on it. Minted
69
+ through the shared `warmSeedKey` so BOTH sides of the handoff (this same isomorphic call,
70
+ server-side keying `resolvedCells`/`streamedCells` and client-side reading `CELL_SEED`) form
71
+ the identical string from one definition. Undefined for a detached cell (no scope) — such a
72
+ cell never crosses SSR→client, so never warm-seeds. */
73
+ const scope = CURRENT_SCOPE.current as (Scope & { nextCellIndex: () => number }) | undefined
74
+ const warmKey = scope !== undefined ? warmSeedKey(scope.id, scope.nextCellIndex()) : undefined
75
+
76
+ /* `linked` write latch: a local `set()` holds the cell until the next reseed, so an
77
+ arriving frame / settling promise never clobbers an in-progress edit. Cleared on reseed. */
78
+ let written = false
79
+ /* Supersedes out-of-order settles: only the latest run's promise/stream may write. */
80
+ let runId = 0
81
+ /* The current run's in-flight promise (undefined once settled, or for a stream/sync seed).
82
+ Surfaced through `settled()` for the SSR barrier's structural read; a stream never sets it. */
83
+ let inFlight: Promise<unknown> | undefined
84
+ /* Cancels the active stream subscription (a reseed, refresh, or dispose supersedes it). */
85
+ let cancelStream: (() => void) | undefined
86
+
87
+ /* Store a produced value, honouring the write latch and the transform gate. */
88
+ const acceptValue = (value: unknown): void => {
89
+ if (written) {
90
+ return
91
+ }
92
+ writeNode(valueNode, transform === undefined ? value : transform(value, valueNode.value))
93
+ writeNode(hasValueNode, true)
94
+ }
95
+
96
+ const settleValue = (myRun: number, value: unknown): void => {
97
+ if (myRun !== runId) {
98
+ return
99
+ }
100
+ inFlight = undefined
101
+ acceptValue(value)
102
+ writeNode(errorNode, undefined)
103
+ writeNode(inFlightNode, false)
104
+ /* Server-only: record the resolved value keyed by this cell's render-path id (the `window`
105
+ guard keeps client settles from ever recording). BLOCKING cells → `resolvedCells`: the
106
+ barrier awaits them before the template peeks, so the value bakes into the SSR HTML and
107
+ the page renderer stamps it into the PRE-mount `__SSR__.cells` warm-seed — a warm client
108
+ value MATCHES it (no flash, no duplication). STREAMING cells → `streamedCells` (ADR-0035):
109
+ they ship pending in the shell, so their value can't ride the pre-mount seed (it would
110
+ diverge from the pending markup); it's streamed AFTER the shell as an `__abideResolve`
111
+ chunk the client adopts POST-hydration. Recording a VALUE (not awaiting a promise) means a
112
+ cell that never settles this request is simply never recorded — no hang. */
113
+ if (warmKey !== undefined && typeof window === 'undefined') {
114
+ if (options.streaming === true) {
115
+ streamedCellsSlot.get()?.entries.push({ key: warmKey, value })
116
+ } else {
117
+ resolvedCellsSlot.get()?.entries.push({ key: warmKey, value })
118
+ }
119
+ }
120
+ }
121
+
122
+ /* An error retains the value (SWR): a failed background refresh keeps the stale value
123
+ visible and surfaces the rejection through `error()` — the read decides what to do. */
124
+ const settleError = (myRun: number, error: unknown): void => {
125
+ if (myRun !== runId) {
126
+ return
127
+ }
128
+ inFlight = undefined
129
+ writeNode(errorNode, error)
130
+ writeNode(inFlightNode, false)
131
+ }
132
+
133
+ /* Drive a NamedAsyncIterable: first frame clears pending, each later frame updates the
134
+ value (unless written), a throw lands in `error()`, and completion clears in-flight. */
135
+ const consumeStream = (myRun: number, source: NamedAsyncIterable<unknown>): void => {
136
+ const iterator = source[Symbol.asyncIterator]()
137
+ let live = true
138
+ cancelStream = (): void => {
139
+ live = false
140
+ void iterator.return?.(undefined)
141
+ }
142
+ const pump = async (): Promise<void> => {
143
+ try {
144
+ while (live && myRun === runId) {
145
+ const step = await iterator.next()
146
+ if (!live || myRun !== runId) {
147
+ return
148
+ }
149
+ if (step.done === true) {
150
+ writeNode(inFlightNode, false)
151
+ return
152
+ }
153
+ acceptValue(step.value)
154
+ writeNode(errorNode, undefined)
155
+ writeNode(inFlightNode, false)
156
+ }
157
+ } catch (error) {
158
+ settleError(myRun, error)
159
+ }
160
+ }
161
+ /* The pump never rejects (its body is fully try/caught), so the kickoff is contained
162
+ — no unhandled rejection, never Bun-fatal. */
163
+ void pump()
164
+ }
165
+
166
+ /* Run (or re-run) the seed. `reseed` marks a dependency-driven new source, which clears
167
+ the write latch; a `refresh()` passes false so a held write and value survive. */
168
+ const run = (reseed: boolean): void => {
169
+ runId += 1
170
+ const myRun = runId
171
+ if (cancelStream !== undefined) {
172
+ cancelStream()
173
+ cancelStream = undefined
174
+ }
175
+ if (reseed) {
176
+ written = false
177
+ }
178
+ writeNode(inFlightNode, true)
179
+
180
+ let produced: unknown
181
+ try {
182
+ produced = seed()
183
+ } catch (error) {
184
+ settleError(myRun, error)
185
+ return
186
+ }
187
+
188
+ if (isAsyncIterable(produced)) {
189
+ consumeStream(myRun, produced as NamedAsyncIterable<unknown>)
190
+ return
191
+ }
192
+ if (isThenable(produced)) {
193
+ inFlight = produced as Promise<unknown>
194
+ /* Server-only: register the in-flight promise on the request-scoped pending list so
195
+ the SSR barrier (`$$settleAsyncCells`) awaits it before the template peeks the
196
+ cell — baking the resolved value into the first-pass HTML (ADR-0019 Tier-2). A
197
+ stream (the `isAsyncIterable` branch above) never registers: it never settles. The
198
+ `window` guard keeps client construction from ever registering. A `streaming`
199
+ cell (ADR-0032, a no-`await` position) opts OUT: it ships pending and resolves on
200
+ the client instead of blocking the flush. */
201
+ if (typeof window === 'undefined' && options.streaming !== true) {
202
+ activePendingCells()?.promises.push(inFlight)
203
+ }
204
+ /* `.then(onValue, onError)` handles the rejection inline — contained in `error()`,
205
+ never an unhandled rejection. A STREAMING cell's resolved value is recorded in
206
+ `settleValue` (ADR-0035), not here — awaiting the promise could hang if it never
207
+ settles this request. */
208
+ ;(produced as PromiseLike<unknown>).then(
209
+ (value) => settleValue(myRun, value),
210
+ (error) => settleError(myRun, error),
211
+ )
212
+ return
213
+ }
214
+ settleValue(myRun, produced)
215
+ }
216
+
217
+ /* Warm hydrate: if the server shipped this cell's resolved value (keyed by its render-path
218
+ id), adopt it NOW — before the eager run — so `hasValue` is already true. The cell then reads
219
+ as REFRESHING (not pending) through the eager run below, so the value shows instantly with no
220
+ flash and matches the SSR-rendered branch. The run still fires (revalidate + subscribe the
221
+ seed's deps for reactivity, SWR), and its settle supersedes via the `runId` guard. Client-
222
+ only in effect (the store is only ever populated by `startClient`, so it's empty on the
223
+ server — no `window` sniff needed); a decode failure falls through to a cold run. */
224
+ if (warmKey !== undefined) {
225
+ const seeded = CELL_SEED[warmKey]
226
+ if (seeded !== undefined) {
227
+ /* One-shot: consume the seed so a LATER fresh mount at the same render-path can't
228
+ warm-adopt this boot-time snapshot. `scope.id` is the route PATTERN (params-
229
+ independent), so an SPA navigation `/products/42`→`/products/99` — or a back-nav
230
+ that rebuilds the SSR page — recomputes the identical warmKey; without deleting,
231
+ the new cell would render `/products/42`'s stale value until revalidation. The seed
232
+ only ever hydrates the initial SSR render, where each cell is constructed once. */
233
+ delete CELL_SEED[warmKey]
234
+ try {
235
+ acceptValue(decodeRefJson(seeded))
236
+ } catch {
237
+ /* Unserializable/corrupt seed → cold run renders it. */
238
+ }
239
+ }
240
+ }
241
+
242
+ /* The eager first-run + reactive reseed: the effect tracks the seed's synchronous reads,
243
+ so a dependency change re-runs it. Its cleanup cancels any live stream on reseed/dispose. */
244
+ createEffectNode(() => {
245
+ run(true)
246
+ return (): void => {
247
+ if (cancelStream !== undefined) {
248
+ cancelStream()
249
+ cancelStream = undefined
250
+ }
251
+ }
252
+ })
253
+
254
+ /* Client-only, STREAMING cells (ADR-0035): a streaming cell ships pending and its value streams
255
+ in AFTER the shell. Register — AFTER the eager run above, so its `inFlight = true` is already
256
+ set — to receive that value by render-path id; when it lands, adopt it as a reactive update:
257
+ the value shows immediately (no `loading…` flash) and the in-flight cold seed run re-settles
258
+ the same value, superseded by the write latch. The streamed value only ever seeds the initial
259
+ render; a later dep-driven reseed is authoritative and runs the seed as usual. */
260
+ if (warmKey !== undefined && options.streaming === true && typeof window !== 'undefined') {
261
+ registerStreamedCell(warmKey, (value) => {
262
+ acceptValue(value)
263
+ writeNode(errorNode, undefined)
264
+ writeNode(inFlightNode, false)
265
+ })
266
+ }
267
+
268
+ /* The shared read surface, identical for read-only and writable cells. */
269
+ const readOnly: AsyncComputed<unknown> = {
270
+ [ASYNC_CELL]: true,
271
+ peek: () => readNode(valueNode),
272
+ /* No value yet: in flight and nothing retained. */
273
+ pending: () => readNode(inFlightNode) === true && readNode(hasValueNode) !== true,
274
+ /* A held value being superseded: in flight but a value is retained. */
275
+ refreshing: () => readNode(inFlightNode) === true && readNode(hasValueNode) === true,
276
+ error: () => readNode(errorNode),
277
+ /* Re-invoke the seed keeping the value visible (SWR); not a reseed, so a write holds. */
278
+ refresh: () => run(false),
279
+ /* The current in-flight promise (undefined once settled / not-a-promise) — an internal
280
+ runtime affordance the SSR barrier reads structurally; not on the public cell types. */
281
+ settled: () => inFlight,
282
+ } as AsyncComputed<unknown>
283
+ if (!options.writable) {
284
+ return readOnly
285
+ }
286
+ /* A writable cell adds `set()`: latches until the next reseed. `settled` rides along from
287
+ the `readOnly` spread (a runtime affordance not on the public type). */
288
+ const writable: AsyncState<unknown> = {
289
+ ...readOnly,
290
+ set: (value: unknown): void => {
291
+ written = true
292
+ writeNode(
293
+ valueNode,
294
+ transform === undefined ? value : transform(value, valueNode.value),
295
+ )
296
+ writeNode(hasValueNode, true)
297
+ },
298
+ }
299
+ return writable
300
+ }