@abide/abide 0.49.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/AGENTS.md +70 -52
  2. package/CHANGELOG.md +177 -0
  3. package/README.md +12 -7
  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
@@ -2,7 +2,6 @@ import { applyPatchToTree } from './applyPatchToTree.ts'
2
2
  import { batch } from './batch.ts'
3
3
  import { createComputedNode } from './createComputedNode.ts'
4
4
  import { createSignalNode } from './createSignalNode.ts'
5
- import { PATCH_BUS } from './PATCH_BUS.ts'
6
5
  import { pathSegments } from './pathSegments.ts'
7
6
  import { readNode } from './readNode.ts'
8
7
  import { trigger } from './trigger.ts'
@@ -59,12 +58,9 @@ export function createDoc(initial: unknown): Doc {
59
58
  truth. Held apart from `nodes` so the structural wake/eviction never touches
60
59
  them; their dirtiness is driven entirely by the deps they read (the signal
61
60
  graph), not by tree mutations. They are not in `tree`, so `snapshot` omits
62
- them, and they never pass through `apply`, so they never hit the patch bus —
63
- a recompute is a downstream reaction, not a change to journal/persist/sync. */
61
+ them, and they never pass through `apply` a recompute is a downstream reaction
62
+ of the signal graph, not a tree change. */
64
63
  const computed = new Map<string, ReactiveNode>()
65
- /* Set to the returned document before any apply runs, so a PATCH_BUS event can
66
- name the document it came from (reference identity, the undo/persistence key). */
67
- let self: Doc
68
64
 
69
65
  /* Links `path` into the trie under its parent and continues up the ancestor chain.
70
66
  Stops at the first ancestor whose parent set already lists it — that link, and
@@ -227,10 +223,6 @@ export function createDoc(initial: unknown): Doc {
227
223
  /* Segments index the tree, so they carry the REAL keys (unescaped); the path
228
224
  strings (parentPath, node-map keys) stay escaped, re-walked through walkPath. */
229
225
  const segments = patch.path === '' ? [] : pathSegments(patch.path)
230
- /* Capture the pre-image only when a consumer is listening (the inverse's only
231
- cost): a replace/remove inverts to the value it overwrote, an add to a
232
- remove (computed post-apply, below, to resolve an array append's index). */
233
- const before = PATCH_BUS.active ? walkPath(tree, patch.path) : undefined
234
226
  tree = applyPatchToTree(tree, patch, segments)
235
227
  const parentPath = parentPathOf(patch.path)
236
228
  const parentValue = walkPath(tree, parentPath).value
@@ -270,40 +262,9 @@ export function createDoc(initial: unknown): Doc {
270
262
  } else {
271
263
  wakeSubtree(parentPath, true, true)
272
264
  }
273
- /* Announce the change before effects flush, so a patch an effect emits in
274
- reaction lands AFTER this one on the bus — the journal stays chronological.
275
- Emitting inside the batch keeps it ahead of the depth-0 flush on batch exit. */
276
- if (PATCH_BUS.active) {
277
- PATCH_BUS.emit({ doc: self, patch, inverse: inverseOf(patch, before) })
278
- }
279
265
  })
280
266
  }
281
267
 
282
- /* The patch that undoes `patch`, from the pre-image `before` (a value the change
283
- overwrote/removed) and the now-mutated tree. An add inverts to removing the slot
284
- it created — resolving an array append (`-`) to the concrete last index it took.
285
- A replace/remove of a path that held nothing inverts to remove/nothing. */
286
- function inverseOf(
287
- patch: Patch,
288
- before: ReturnType<typeof walkPath> | undefined,
289
- ): Patch | undefined {
290
- if (patch.op === 'add') {
291
- const parentPath = parentPathOf(patch.path)
292
- const parent = walkPath(tree, parentPath).value
293
- const resolved =
294
- Array.isArray(parent) && patch.path.endsWith('/-')
295
- ? `${parentPath}/${parent.length - 1}`
296
- : patch.path
297
- return { op: 'remove', path: resolved }
298
- }
299
- if (patch.op === 'replace') {
300
- return before?.exists
301
- ? { op: 'replace', path: patch.path, value: before.value }
302
- : { op: 'remove', path: patch.path }
303
- }
304
- return before?.exists ? { op: 'add', path: patch.path, value: before.value } : undefined
305
- }
306
-
307
268
  /*
308
269
  A stable accessor bound to one scalar leaf — what the compiler emits so a hot
309
270
  loop has zero string work: the node, the parent container, and the leaf key
@@ -355,7 +316,7 @@ export function createDoc(initial: unknown): Doc {
355
316
  }
356
317
  }
357
318
 
358
- self = {
319
+ const self: Doc = {
359
320
  read,
360
321
  cell,
361
322
  derive,
@@ -1,3 +1,5 @@
1
+ import { boundaryFor } from './boundaryFor.ts'
2
+ import { CURRENT_BOUNDARY } from './CURRENT_BOUNDARY.ts'
1
3
  import { CURRENT_SCOPE } from './CURRENT_SCOPE.ts'
2
4
  import { inScope } from './inScope.ts'
3
5
  import { NODE_STATE } from './NODE_STATE.ts'
@@ -67,5 +69,12 @@ export function createEffectNode(fn: () => EffectResult): () => void {
67
69
  if (OWNER.current !== undefined) {
68
70
  OWNER.current.push(dispose)
69
71
  }
72
+ /* Effects built inside a reactive `{#try}` associate with its boundary so a LATER re-run
73
+ throw (an async cell that rejects post-mount, read via a throwing peek) routes there
74
+ rather than the collect-and-rethrow fallback. Populated only under a boundary — the
75
+ node shape stays untouched, the lookup is cold (only on the throw path). */
76
+ if (CURRENT_BOUNDARY.current !== undefined) {
77
+ boundaryFor.set(node, CURRENT_BOUNDARY.current)
78
+ }
70
79
  return dispose
71
80
  }
@@ -1,12 +1,13 @@
1
1
  import { RENDER } from './RENDER.ts'
2
2
 
3
- /* Marks entry into a render/mount. The OUTERMOST one (depth 0) resets the block-id
4
- counter so every render pass starts at 0; a child component's render/mount runs
5
- at depth > 0 and continues the parent's counter. Pair with `exitRenderPass`. */
3
+ /* Marks entry into a render/mount. The OUTERMOST one (depth 0) clears the per-path
4
+ block-id counters so every render pass starts each path at 0 (a re-navigation to the
5
+ same route must not continue a prior pass's counter); a child component's render/mount
6
+ runs at depth > 0 and continues the same map. Pair with `exitRenderPass`. */
6
7
  // @documentation plumbing
7
8
  export function enterRenderPass(): void {
8
9
  if (RENDER.depth === 0) {
9
- RENDER.blockId = 0
10
+ RENDER.blockCounters.clear()
10
11
  }
11
12
  RENDER.depth += 1
12
13
  }
@@ -1,3 +1,4 @@
1
+ import { boundaryFor } from './boundaryFor.ts'
1
2
  import { NODE_STATE } from './NODE_STATE.ts'
2
3
  import { REACTIVE_CONTEXT } from './REACTIVE_CONTEXT.ts'
3
4
  import type { ReactiveNode } from './types/ReactiveNode.ts'
@@ -49,12 +50,21 @@ function drain(): void {
49
50
  try {
50
51
  updateIfNecessary(node)
51
52
  } catch (error) {
52
- /* One effect throwing must not strand the effects queued behind it they
53
- live in this same `batch`, which becomes unreachable the moment we swap
54
- `pendingEffects`. Reset the culprit to CLEAN so a later write to its
55
- dependencies can re-queue it (otherwise `mark`'s CLEAN→dirty gate leaves
56
- it permanently inert), then keep draining and surface the error(s) once
57
- the graph has settled rather than swallowing them. */
53
+ /* A node built inside a reactive `{#try}` routes its throw to that boundary
54
+ (swap the guarded content to the catch branch) instead of the rethrow
55
+ fallback this is the "later re-run throw" the sync boundary could not
56
+ catch. Reset it CLEAN either way so a later write to its deps can re-queue
57
+ it (otherwise `mark`'s CLEAN→dirty gate leaves it permanently inert). */
58
+ const boundary = boundaryFor.get(node)
59
+ if (boundary !== undefined) {
60
+ boundary.handle(error)
61
+ node.status = NODE_STATE.CLEAN
62
+ continue
63
+ }
64
+ /* No boundary — one effect throwing must not strand the effects queued behind
65
+ it (they live in this same `batch`, unreachable once we swap
66
+ `pendingEffects`); keep draining and surface the error(s) once the graph has
67
+ settled rather than swallowing them. */
58
68
  node.status = NODE_STATE.CLEAN
59
69
  if (errors === undefined) {
60
70
  errors = []
@@ -0,0 +1,14 @@
1
+ /*
2
+ True for an `async () => …` / `async function () {}`. This is the async cell's
3
+ `await` marker at runtime: the `computed`/`linked` transform lowers `computed(await p)`
4
+ to `computed(async () => await p)`, so an async-function seed means "unwrap the
5
+ promise and track it" — as opposed to a plain thunk returning a promise, which is held
6
+ opaque (`Computed<Promise<T>>`). The tag is the reliable signal (`.constructor.name`
7
+ survives minification less reliably; `Symbol.toStringTag` is spec-stable).
8
+ */
9
+ export function isAsyncFunction(value: unknown): boolean {
10
+ return (
11
+ typeof value === 'function' &&
12
+ (value as { [Symbol.toStringTag]?: unknown })[Symbol.toStringTag] === 'AsyncFunction'
13
+ )
14
+ }
@@ -1,11 +1,12 @@
1
+ import { blockId } from './blockId.ts'
1
2
  import { RENDER } from './RENDER.ts'
2
3
 
3
- /* The next block id in the current render pass. `await`/`try` blocks draw from it
4
- in document order shared across a component and the children it inlines — so a
5
- page id and a child component's id never collide in the global `RESUME` manifest. */
4
+ /* The next block id in the current render pass, namespaced by the ambient render-path
5
+ (ADR-0037). `await`/`try` blocks draw it in document order WITHIN their path; a page
6
+ id and a child component's id can never collide because they carry different paths, so
7
+ ids stay congruent SSR↔client even when the server renders sibling children concurrently.
8
+ Delegates to the shared `blockId` over the client's per-pass `RENDER.blockCounters` map. */
6
9
  // @documentation plumbing
7
- export function nextBlockId(): number {
8
- const id = RENDER.blockId
9
- RENDER.blockId += 1
10
- return id
10
+ export function nextBlockId(): string {
11
+ return blockId(RENDER.blockCounters)
11
12
  }
@@ -0,0 +1,16 @@
1
+ import { CURRENT_PATH } from './CURRENT_PATH.ts'
2
+ import { withPath } from './withPath.ts'
3
+
4
+ /*
5
+ The render-path a `<Child/>` mounts under — the boundary id of a STREAMED child component (ADR-0039).
6
+ Composes the child's ordinal segment onto the ambient path exactly as the client's `mountChild` /
7
+ `mountStreamedChild` does (`withPath(ordinal, …)`), so the server-written `abide:await:CHILDPATH`
8
+ boundary and the client adopter compute the SAME id with no counter to drift. It carries `/`
9
+ separators and never a `:`, so a child path (`products/0`) can't collide with an await-block id
10
+ (`products:0`). Server-emit-only (the streaming boundary is server codegen), so it tree-shakes out of
11
+ the client bundle like `$$flight`.
12
+ */
13
+ // @documentation plumbing
14
+ export function renderPath(segment: string | number): string {
15
+ return withPath(segment, () => CURRENT_PATH.current)
16
+ }
@@ -0,0 +1,9 @@
1
+ /*
2
+ A reactive error boundary — the runtime handle a `{#try}` installs so a LATER re-run
3
+ throw from a guarded effect (an async cell that rejects after mount, read via a throwing
4
+ peek) routes back to it. `handle(error)` swaps the guarded content to the catch branch;
5
+ the scheduler (`flushEffects.drain`) calls it when a node associated with this boundary
6
+ (`boundaryFor`) throws during a flush. Kept minimal so the boundary can install itself as
7
+ the ambient `CURRENT_BOUNDARY` around a guarded build without dragging DOM types here.
8
+ */
9
+ export type Boundary = { handle(error: unknown): void }
@@ -1,8 +1,10 @@
1
1
  /* The per-request SSR render context, threaded through a layout chain and every
2
- child component a page inlines. `next` is the block-id counter: each `await`/`try`
3
- block draws from it in depth-first document order the SAME order the client
4
- allocates ids during its synchronous hydration walk, so the streamed fragments and
5
- the `RESUME` manifest line up. Request-local (not a module global) because SSR
6
- render is async a blocking `await` yields, and a shared global counter would
7
- interleave across concurrent requests. */
8
- export type RenderContext = { next: number }
2
+ child component a page inlines. It is the block-id counter MAP (ADR-0037): each
3
+ `await`/`try` block draws a path-namespaced id via `blockId(ctx)`keyed by the
4
+ ambient render-path, counting 0,1,2… in document order WITHIN each path. Both sides
5
+ compose the same ids (the client mounts each child under the same path), so the
6
+ streamed fragments and the `RESUME` manifest line up. Request-local (not a module
7
+ global) because SSR render is async and concurrent but sibling child renders write
8
+ different path keys, so the shared map needs no locking and survives parallel renders
9
+ (which a single flat counter could not). */
10
+ export type RenderContext = Map<string, number>
@@ -9,10 +9,17 @@ import type { ResumeEntry } from '../RESUME.ts'
9
9
  on the tag — never land here: they render inline during the async render pass and
10
10
  seed `SsrRender.resume`.) */
11
11
  export type SsrAwait = {
12
- id: number
12
+ id: string
13
13
  promise: () => unknown
14
14
  then: (value: unknown) => Promise<string>
15
15
  catch?: (error: unknown) => Promise<string>
16
+ /* ADR-0039 (spike): a STANDALONE-UNIT boundary — a streamed child COMPONENT — carries no
17
+ resume value of its own (its `then` returns the child's rendered html and merges the child's
18
+ own awaits/resume for nested composition; the child re-mounts client-side rather than
19
+ adopting a RESUME[id] value). Marks `settle` to emit an html-only fragment with no resume seed
20
+ script, so the client never tries to decode a whole SsrRender as an await value. Absent for a
21
+ normal await block (which seeds its resolved value). */
22
+ htmlOnly?: boolean
16
23
  }
17
24
 
18
25
  /* The result of a component's server `render()`: the pending-shell HTML, the
@@ -23,5 +30,5 @@ export type SsrRender = {
23
30
  html: string
24
31
  state: unknown
25
32
  awaits: SsrAwait[]
26
- resume: Record<number, ResumeEntry>
33
+ resume: Record<string, ResumeEntry>
27
34
  }
@@ -18,11 +18,7 @@ export type UiComponent = ((host: Element, props?: UiProps) => () => void) & {
18
18
  hydrate?: (host: Element, props?: UiProps) => () => void
19
19
  /* The bare client build (`(host, props) => void`) — appends the component's nodes
20
20
  to `host`. A nested child mounts it into a marker range (`mountRange`/`mountChild`)
21
- instead of the wrapped `mount`, and `hotReplace` re-fills a range with the new
22
- module's `build` on edit. */
21
+ instead of the wrapped `mount`. */
23
22
  build: (host: Node, props?: UiProps) => void
24
23
  hydratable?: boolean
25
- /* Stable module id (project-relative source path) stamped by `compileModule`,
26
- keying the component in the hot-reload registry — see `mountChild`. */
27
- __abideId?: string
28
24
  }
@@ -0,0 +1,8 @@
1
+ import { withPath } from './withPath.ts'
2
+
3
+ /* Runs `build` under the render-path segment for a compiler `ordinal`, or plainly when `ordinal`
4
+ is undefined (a non-compiled caller that supplied no site id). Shared by the child-mount paths so
5
+ the "push a path segment iff an ordinal is present" convention lives in one place. */
6
+ export function withOptionalPath<T>(ordinal: number | undefined, build: () => T): T {
7
+ return ordinal === undefined ? build() : withPath(ordinal, build)
8
+ }
@@ -0,0 +1,16 @@
1
+ import { CURRENT_PATH } from './CURRENT_PATH.ts'
2
+ import { withPathFrom } from './withPathFrom.ts'
3
+
4
+ /*
5
+ Pushes one render-path segment for the duration of `build`, RELATIVE to the live ambient path,
6
+ restoring after (synchronous build, so the save/restore is exact and nesting is a strict stack —
7
+ the same shape `withScope`/`enterRenderPass` use). The segment is `escapeKey`-escaped and joined
8
+ with `/`, so a segment carrying a `/` (a URL-shaped `{#each}` key) survives as one element. Used
9
+ at sites that run DURING the initial render walk (a layout layer, a `<Child/>` mount) where the
10
+ ambient path is the correct base. A control-flow block that rebuilds reactively after mount uses
11
+ `withPathFrom` with a captured base instead.
12
+ */
13
+ // @documentation plumbing
14
+ export function withPath<T>(segment: string | number, build: () => T): T {
15
+ return withPathFrom(CURRENT_PATH.current, segment, build)
16
+ }
@@ -0,0 +1,20 @@
1
+ import { ambientPathBacking } from './ambientPathBacking.ts'
2
+ import { escapeKey } from './escapeKey.ts'
3
+
4
+ /*
5
+ Runs `build` with the render path set to `base` + one escaped `segment` (ADR-0033 D1). Unlike
6
+ `withPath` (which pushes relative to the LIVE ambient), this composes from an EXPLICIT base — for
7
+ a control-flow block that rebuilds content reactively AFTER mount, when the ambient path is no
8
+ longer on the stack. The block captures `CURRENT_PATH.current` at construction (its render-time
9
+ ancestry) and re-establishes `base/segment` for every branch/row build — initial and every later
10
+ swap — so a component or cell built on a swap still gets its full, stable id, not a bare segment.
11
+
12
+ Pushes through the backing's `run` (not a set-then-restore slot): the server's AsyncLocalStorage
13
+ backing makes the composed path survive an `await` inside `build`, so a render body resuming after
14
+ a cell barrier or child render reads its own path; the client's synchronous module-var `run` is a
15
+ plain save/restore. `run` returns `build()`'s value (a value on the client, a promise on the server).
16
+ */
17
+ export function withPathFrom<T>(base: string, segment: string | number, build: () => T): T {
18
+ const composedPath = base === '' ? escapeKey(segment) : `${base}/${escapeKey(segment)}`
19
+ return ambientPathBacking.active.run(composedPath, build)
20
+ }
@@ -1,6 +1,9 @@
1
1
  import { activeCacheStore } from '../shared/activeCacheStore.ts'
2
2
  import { cacheEntryFromSnapshot } from '../shared/cacheEntryFromSnapshot.ts'
3
+ import { decodeRefJson } from '../shared/decodeRefJson.ts'
3
4
  import type { StreamedResolution } from '../shared/types/StreamedResolution.ts'
5
+ import { CELL_SEED } from './runtime/CELL_SEED.ts'
6
+ import { receiveStreamedCell } from './runtime/STREAMED_CELLS.ts'
4
7
 
5
8
  /*
6
9
  Seeds one streamed cache resolution into the active store — the single sink for the
@@ -9,12 +12,27 @@ so a `cache()` read resolves synchronously (no wire round-trip) and `<template a
9
12
  adopts without a refetch; a `{ key, miss }` marker — a body the server couldn't snapshot
10
13
  (binary / rejected / evicted) — is a no-op, so that read falls back to a live fetch.
11
14
 
12
- Shared by startClient's boot drain, the live `window.__abideResolve`, and applyResolved,
13
- so no resolved-frame consumer can swap DOM while silently dropping the cache channel —
14
- the asymmetry that made streamed reads cold-miss to the network.
15
+ Shared by startClient's boot drain and the live `window.__abideResolve`, so no
16
+ resolved-frame consumer can swap DOM while silently dropping the cache channel — the
17
+ asymmetry that made streamed reads cold-miss to the network.
15
18
  */
16
19
  // @documentation plumbing
17
20
  export function seedStreamedResolution(resolution: StreamedResolution): void {
21
+ /* ADR-0039: a STREAMED CHILD's blocking async-cell value that resolved after the head snapshot.
22
+ Seed it into `CELL_SEED` (the same warm partition `__SSR__.cells` fills, storing the raw
23
+ ref-json string decoded at cell read) so the child's cell constructs RESOLVED when its
24
+ deferred mount runs — no flash, no re-run. Checked before `cellKey` (a distinct arm). */
25
+ if ('cellSeed' in resolution) {
26
+ CELL_SEED[resolution.cellSeed] = resolution.value
27
+ return
28
+ }
29
+ /* ADR-0035: a streaming CELL's server-resolved value, keyed by render-path id. Route it to the
30
+ mounted cell (or buffer until it registers) — decoded through the same ref-json codec the
31
+ server encoded it with, so a Set/Map/Date/bigint/cyclic value survives. */
32
+ if ('cellKey' in resolution) {
33
+ receiveStreamedCell(resolution.cellKey, decodeRefJson(resolution.value))
34
+ return
35
+ }
18
36
  if ('miss' in resolution) {
19
37
  return
20
38
  }
@@ -22,10 +40,17 @@ export function seedStreamedResolution(resolution: StreamedResolution): void {
22
40
  an unconsumed hydrated seed (`hydrated === true`, cleared by the first cache()
23
41
  read). A live/settled non-hydrated entry is authoritative; clobbering it with a
24
42
  stale snapshot would drop a fresher value (e.g. one a live fetch already wrote). */
25
- const { entries } = activeCacheStore()
26
- const existing = entries.get(resolution.key)
43
+ const store = activeCacheStore()
44
+ const existing = store.entries.get(resolution.key)
27
45
  if (existing !== undefined && existing.hydrated !== true) {
28
46
  return
29
47
  }
30
- entries.set(resolution.key, cacheEntryFromSnapshot(resolution))
48
+ store.entries.set(resolution.key, cacheEntryFromSnapshot(resolution))
49
+ /* Wake any reader subscribed to this key's lifecycle channel. Seeding used to rely on
50
+ seed-before-mount ordering (valid for the await path, whose subscription reads the
51
+ resume manifest, not the cache); an auto-streamed BARE read (ADR-0024) has already
52
+ mounted and its throwing-peek subscribed the key, so without an explicit dispatch the
53
+ peek that read `undefined` never re-runs on the streamed value. markLifecycle re-derives
54
+ the peek scope — the same wake materializeRetained relies on for a late-landing value. */
55
+ store.markLifecycle(resolution.key)
31
56
  }
@@ -0,0 +1,24 @@
1
+ import { activePendingCells } from './activePendingCells.ts'
2
+
3
+ /*
4
+ The SSR await-barrier the compiler lowers to `$$settleAsyncCells()` between a
5
+ component's cell declarations and its template (ADR-0019 Tier-2). It DRAINS the
6
+ request-scoped pending list (`splice(0)`) so each barrier awaits only the cells
7
+ declared since the last one — `renderChain` renders layout layers sequentially,
8
+ each with its own barrier, and a drain keeps a later layer from re-awaiting an
9
+ earlier settled layer's cells. `Promise.allSettled` never rejects the render: a
10
+ rejected cell settles into its `error()` state, surfaced at the read site (via
11
+ `$$readCell`), not here. On the client the list is always empty (cells register
12
+ server-side only), so the barrier is a no-op await.
13
+ */
14
+ // @documentation plumbing
15
+ export async function settleAsyncCells(): Promise<void> {
16
+ const pending = activePendingCells()
17
+ if (pending === undefined) {
18
+ return
19
+ }
20
+ const promises = pending.promises.splice(0)
21
+ if (promises.length > 0) {
22
+ await Promise.allSettled(promises)
23
+ }
24
+ }
@@ -11,7 +11,7 @@ over the multiplexed ws channel). Both paths produce identical Socket
11
11
  shapes so user code reads the same on either side.
12
12
 
13
13
  The Socket surface — bare iteration as the live stream, `.tail(n)` seeded
14
- from the retained tail, `.broadcast` sending a server-validated `pub` frame —
14
+ from the retained tail, `.publish` sending a server-validated `pub` frame —
15
15
  is built by buildSocketOverChannel over the page's lazily-opened singleton
16
16
  channel; this module only binds that builder to the browser channel so the
17
17
  test harness can reuse the identical surface over its own channel.
@@ -5,18 +5,12 @@ import { sharedCacheStoreSlot } from '../shared/sharedCacheStoreSlot.ts'
5
5
  import type { SsrPayload } from '../shared/types/SsrPayload.ts'
6
6
  import type { StreamedResolution } from '../shared/types/StreamedResolution.ts'
7
7
  import { probeNavigation } from './probeNavigation.ts'
8
- import { seedBootState } from './seedBootState.ts'
9
-
10
- /* Build-time flag the production client defines false (see build.ts `define`) so the dev-only
11
- hot bridge — and the entire DOM runtime it statically pulls in for re-builds — is dead-code-
12
- eliminated, not even emitted as a chunk. Dev defines it true; the test preload sets it on
13
- globalThis so the bare reference resolves there. */
14
- declare const __ABIDE_DEV__: boolean
15
-
16
8
  import { router } from './router.ts'
9
+ import { CELL_SEED } from './runtime/CELL_SEED.ts'
17
10
  import { clientPage } from './runtime/clientPage.ts'
18
11
  import type { RouteLoader } from './runtime/types/RouteLoader.ts'
19
- import { seedResolved } from './seedResolved.ts'
12
+ import { seedBootState } from './seedBootState.ts'
13
+ import { seedStreamedResolution } from './seedStreamedResolution.ts'
20
14
 
21
15
  /*
22
16
  The official abide-ui client entry. Reads the server's `window.__SSR__` payload,
@@ -40,21 +34,6 @@ export function startClient(
40
34
  if (target === null) {
41
35
  throw new Error('[abide] startClient: missing #app target')
42
36
  }
43
- /* Dev only: the live-reload script sets `__abideDev` before this module runs, so the
44
- runtime bridge is in place before any component mounts and records its instances
45
- (mountChild) for hot replacement. Lazy-imported and `__ABIDE_DEV__`-gated so the
46
- bridge (and the DOM runtime it drags in) is fully dead-code-eliminated in production
47
- rather than shipped behind a runtime flag the minifier can't prove false. */
48
- if (__ABIDE_DEV__ && (globalThis as { __abideDev?: boolean }).__abideDev) {
49
- import('./installHotBridge.ts').then((module) => module.installHotBridge())
50
- }
51
- /* Inspector only: the server injects `__abideInspect` when ABIDE_ENABLE_INSPECTOR is on,
52
- so the scope/router bridge arms before the router builds any scope. Inspector can be
53
- enabled in production, so this stays a lazy chunk (not `__ABIDE_DEV__`-gated) — emitted
54
- but fetched only when the flag is set, never weighing down the default client load. */
55
- if ((globalThis as { __abideInspect?: boolean }).__abideInspect) {
56
- import('./installInspectorBridge.ts').then((module) => module.installInspectorBridge())
57
- }
58
37
  const ssr = (globalThis as { __SSR__?: Partial<SsrPayload> }).__SSR__ ?? {}
59
38
  /* Seed the per-page __SSR__ stamps into their shared slots before mount: the mount
60
39
  base, app name (default log channel), health payload (so health()'s first probe is
@@ -78,16 +57,22 @@ export function startClient(
78
57
  const streamed =
79
58
  (globalThis as { __abideResumeCache?: StreamedResolution[] }).__abideResumeCache ?? []
80
59
  for (const resolution of [...(ssr.cache ?? []), ...streamed]) {
81
- seedResolved({ kind: 'cache', resolution })
60
+ seedStreamedResolution(resolution)
61
+ }
62
+ /* Seed the async-cell warm partition into `CELL_SEED` before mount: a hydrating
63
+ `createAsyncCell` reads its render-path key here to adopt the SSR-resolved value warm
64
+ instead of re-running its seed cold (Object.assign so an inline pre-bundle script that
65
+ already populated `__abideCells` isn't clobbered). */
66
+ if (ssr.cells !== undefined) {
67
+ Object.assign(CELL_SEED, ssr.cells)
82
68
  }
83
69
  /* Keep the cache channel live past boot: replace the head's buffering collector with
84
- the store-connected sink so a post-load resolution — streaming SPA navigation or a
85
- socket-delivered SSR frame, both routed through applyResolved seeds the store
86
- directly instead of pushing to a buffer nothing drains again. The inline doc-stream
87
- script only ever hands this a cache `StreamedResolution`, so wrap it as a cache frame
88
- through the one intake seam. */
70
+ the store-connected sink so a post-boot resolution — the inline doc-stream cache
71
+ script seeds the store through `seedStreamedResolution` directly instead of pushing
72
+ to a buffer nothing drains again. The inline doc-stream script only ever hands this a
73
+ cache `StreamedResolution`. */
89
74
  ;(globalThis as { __abideResolve?: (resolution: StreamedResolution) => void }).__abideResolve =
90
- (resolution) => seedResolved({ kind: 'cache', resolution })
75
+ seedStreamedResolution
91
76
 
92
77
  return router(target, routes, layoutRoutes, probeNavigation)
93
78
  }
@@ -0,0 +1,68 @@
1
+ import { isAsyncIterable } from '../shared/isAsyncIterable.ts'
2
+ import type { AsyncComputed } from '../shared/types/AsyncComputed.ts'
3
+ import type { NamedAsyncIterable } from '../shared/types/NamedAsyncIterable.ts'
4
+ import { createAsyncCell } from './runtime/createAsyncCell.ts'
5
+ import { createComputedNode } from './runtime/createComputedNode.ts'
6
+ import { isAsyncFunction } from './runtime/isAsyncFunction.ts'
7
+ import { OWNER } from './runtime/OWNER.ts'
8
+ import { readNode } from './runtime/readNode.ts'
9
+ import type { Computed } from './runtime/types/Computed.ts'
10
+ import { unlinkDeps } from './runtime/unlinkDeps.ts'
11
+ import { untrack } from './runtime/untrack.ts'
12
+
13
+ /*
14
+ The eager, stream-classifying read-only computed the compiler routes a bare-call /
15
+ identifier seed to — `computed(getStream())` / `computed(ref)` (ADR-0019 D1). Unlike the
16
+ lazy `computed` primitive it PROBES its seed once to self-identify the source: a
17
+ `NamedAsyncIterable` (socket / streaming rpc) auto-tracks its frames as an `AsyncComputed`,
18
+ an `async () => await …` seed unwraps its promise, and anything else — a sync value, a bare
19
+ opaque promise — falls back to the lazy `Computed`, so laziness is preserved on the miss and
20
+ only a real stream/promise producer becomes a cell. It lives apart from the `computed`
21
+ primitive so a DIRECT `computed(() => …)` call stays lazy (the primitive never probes); the
22
+ eager probe is reached only through this compiler-emitted entry, matching how `linked`
23
+ already classifies its seed. The probe runs untracked — a throwaway read must not subscribe
24
+ the enclosing build.
25
+ */
26
+ export function trackedComputed<T>(
27
+ compute: () => T | Promise<T> | NamedAsyncIterable<T>,
28
+ /* ADR-0032: when the compiler lifts a no-`await` async (sub)expression to a cell, it passes
29
+ `streaming: true` so the resolved promise does not join the SSR blocking barrier (the shell
30
+ ships pending, the client resolves). Default false = today's blocking registration; a stream
31
+ seed never registers regardless, so it only matters for a promise-unwrapping seed. */
32
+ streaming = false,
33
+ ): Computed<T> | AsyncComputed<T> {
34
+ /* `await` marker: an async-function seed unwraps its promise into a read-only async cell. */
35
+ if (isAsyncFunction(compute)) {
36
+ return createAsyncCell(compute as () => unknown, {
37
+ writable: false,
38
+ streaming,
39
+ }) as AsyncComputed<T>
40
+ }
41
+ /* Probe the seed once (untracked) to detect a self-identifying stream. A discarded stream
42
+ iterable costs nothing — a streaming rpc defers its fetch to the first pull, which never
43
+ happens here; a throw means a plain reactive compute, so fall through to the lazy path
44
+ (it throws at read, unchanged). */
45
+ let probe: unknown
46
+ let threw = false
47
+ try {
48
+ probe = untrack(compute)
49
+ } catch {
50
+ threw = true
51
+ }
52
+ if (!threw && isAsyncIterable(probe)) {
53
+ return createAsyncCell(compute as () => unknown, {
54
+ writable: false,
55
+ streaming,
56
+ }) as AsyncComputed<T>
57
+ }
58
+ /* Sync value or a bare (opaque) promise: the lazy computed, identical to the primitive. */
59
+ const node = createComputedNode(compute as () => unknown)
60
+ if (OWNER.current !== undefined) {
61
+ OWNER.current.push(() => unlinkDeps(node))
62
+ }
63
+ return {
64
+ get value(): T {
65
+ return readNode(node) as T
66
+ },
67
+ }
68
+ }