@abide/abide 0.48.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (360) hide show
  1. package/AGENTS.md +85 -59
  2. package/CHANGELOG.md +216 -0
  3. package/README.md +21 -10
  4. package/package.json +15 -4
  5. package/src/abideLsp.ts +5 -6
  6. package/src/abideResolverPlugin.ts +197 -169
  7. package/src/build.ts +79 -56
  8. package/src/buildCli.ts +3 -5
  9. package/src/buildDisconnected.ts +2 -5
  10. package/src/bundleApp.ts +2 -3
  11. package/src/checkAbide.ts +12 -2
  12. package/src/compile.ts +2 -4
  13. package/src/devEntry.ts +113 -28
  14. package/src/discoveryEntry.ts +3 -2
  15. package/src/lib/bundle/disconnected.abide +69 -73
  16. package/src/lib/bundle/infoPlist.ts +13 -7
  17. package/src/lib/bundle/installDownloads.ts +13 -3
  18. package/src/lib/bundle/installMacMenu.ts +13 -3
  19. package/src/lib/bundle/spawnEmbeddedServer.ts +13 -4
  20. package/src/lib/bundle/waitForServer.ts +6 -1
  21. package/src/lib/cli/parseArgvForRpc.ts +36 -9
  22. package/src/lib/cli/tokenizeArgvFlags.ts +4 -2
  23. package/src/lib/mcp/createMcpResourceServer.ts +13 -2
  24. package/src/lib/mcp/mcpTools.ts +15 -6
  25. package/src/lib/mcp/toolResultFromResponse.ts +40 -12
  26. package/src/lib/server/error.ts +6 -6
  27. package/src/lib/server/json.ts +17 -3
  28. package/src/lib/server/rpc/defineRpc.ts +58 -24
  29. package/src/lib/server/rpc/parseArgs.ts +113 -14
  30. package/src/lib/server/rpc/resolveRpcJsonSchema.ts +28 -0
  31. package/src/lib/server/rpc/runWithRpcTimeout.ts +12 -1
  32. package/src/lib/server/rpc/types/RpcHelper.ts +75 -105
  33. package/src/lib/server/rpc/types/RpcRegistryEntry.ts +24 -9
  34. package/src/lib/server/rpc/validationError.ts +1 -1
  35. package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +0 -15
  36. package/src/lib/server/runtime/buildCacheSnapshot.ts +10 -9
  37. package/src/lib/server/runtime/buildInspectorSurface.ts +6 -4
  38. package/src/lib/server/runtime/buildOpenApiSpec.ts +55 -11
  39. package/src/lib/server/runtime/buildPreloadManifest.ts +12 -10
  40. package/src/lib/server/runtime/createAppAssetServer.ts +18 -15
  41. package/src/lib/server/runtime/createAppRouteResolver.ts +145 -0
  42. package/src/lib/server/runtime/createPlumbingRouter.ts +212 -0
  43. package/src/lib/server/runtime/createRouteDispatcher.ts +4 -10
  44. package/src/lib/server/runtime/createServer.ts +120 -324
  45. package/src/lib/server/runtime/createUiPageRenderer.ts +137 -26
  46. package/src/lib/server/runtime/devClientFingerprint.ts +19 -34
  47. package/src/lib/server/runtime/finalizeResponse.ts +11 -1
  48. package/src/lib/server/runtime/installAmbientScopeStore.ts +30 -9
  49. package/src/lib/server/runtime/logExposedSurfaces.ts +8 -7
  50. package/src/lib/server/runtime/pathStore.ts +15 -0
  51. package/src/lib/server/runtime/registryManifests.ts +1 -1
  52. package/src/lib/server/runtime/renderCellBarrierStore.ts +15 -0
  53. package/src/lib/server/runtime/runWithRequestScope.ts +3 -0
  54. package/src/lib/server/runtime/serializeCacheSnapshot.ts +6 -4
  55. package/src/lib/server/runtime/snapshotEntryFromCache.ts +24 -16
  56. package/src/lib/server/runtime/streamCacheResolutions.ts +14 -7
  57. package/src/lib/server/runtime/streamFromIterator.ts +30 -2
  58. package/src/lib/server/runtime/textResponse.ts +23 -0
  59. package/src/lib/server/runtime/types/DevReloadStamp.ts +6 -10
  60. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
  61. package/src/lib/server/runtime/types/RequestStore.ts +27 -0
  62. package/src/lib/server/runtime/warnUnguardedMcp.ts +15 -5
  63. package/src/lib/server/sockets/createSocketDispatcher.ts +9 -11
  64. package/src/lib/server/sockets/defineSocket.ts +5 -4
  65. package/src/lib/server/sse.ts +5 -1
  66. package/src/lib/shared/ASYNC_CELL.ts +5 -0
  67. package/src/lib/shared/DEV_REBUILD_PATH.ts +6 -0
  68. package/src/lib/shared/HTTP_METHODS.ts +6 -0
  69. package/src/lib/shared/HttpError.ts +1 -5
  70. package/src/lib/shared/MCP_PATH.ts +6 -0
  71. package/src/lib/shared/PROXIED_SERVER_SUBDIRS.ts +15 -0
  72. package/src/lib/shared/REF_JSON_TAGS.ts +13 -1
  73. package/src/lib/shared/RPC_SHIM_GLOBALS.ts +9 -0
  74. package/src/lib/shared/activeCacheStore.ts +1 -0
  75. package/src/lib/shared/activePage.ts +1 -0
  76. package/src/lib/shared/buildArtifact.ts +20 -0
  77. package/src/lib/shared/buildRpcRequest.ts +6 -2
  78. package/src/lib/shared/buildSocketOverChannel.ts +4 -3
  79. package/src/lib/shared/bundleGraphFromMetafile.ts +68 -0
  80. package/src/lib/shared/cache.ts +156 -131
  81. package/src/lib/shared/canonicalJson.ts +24 -1
  82. package/src/lib/shared/changeAffectsClient.ts +12 -7
  83. package/src/lib/shared/createCacheStore.ts +31 -8
  84. package/src/lib/shared/createChannelLog.ts +24 -0
  85. package/src/lib/shared/createLifecycleChannel.ts +7 -1
  86. package/src/lib/shared/createReachable.ts +47 -78
  87. package/src/lib/shared/createRemoteFunction.ts +52 -30
  88. package/src/lib/shared/createRpcServerProgram.ts +820 -0
  89. package/src/lib/shared/decodeRefJson.ts +4 -4
  90. package/src/lib/shared/decodeResponse.ts +2 -2
  91. package/src/lib/shared/decodeWireBody.ts +35 -0
  92. package/src/lib/shared/detectRpcMethod.ts +8 -1
  93. package/src/lib/shared/done.ts +8 -2
  94. package/src/lib/shared/encodeRefJson.ts +4 -4
  95. package/src/lib/shared/encodeWireBody.ts +18 -0
  96. package/src/lib/shared/exitOnBuildFailure.ts +4 -3
  97. package/src/lib/{server/rpc → shared}/fieldErrorsFromIssues.ts +5 -1
  98. package/src/lib/shared/generateDeclarations.ts +72 -0
  99. package/src/lib/shared/hasSeedableRequest.ts +25 -0
  100. package/src/lib/shared/hydrationWindow.ts +53 -0
  101. package/src/lib/shared/isAsyncCell.ts +11 -0
  102. package/src/lib/shared/isAsyncIterable.ts +8 -0
  103. package/src/lib/shared/isSubscribable.ts +3 -3
  104. package/src/lib/shared/isThenable.ts +9 -0
  105. package/src/lib/shared/jsonSchemaForType.ts +258 -0
  106. package/src/lib/shared/lenientDecode.ts +15 -0
  107. package/src/lib/shared/loadProjectTsConfig.ts +28 -0
  108. package/src/lib/shared/markFrameworkSourcesIgnored.ts +1 -1
  109. package/src/lib/shared/matchRoute.ts +4 -14
  110. package/src/lib/shared/parseEnv.ts +17 -6
  111. package/src/lib/shared/peek.ts +14 -0
  112. package/src/lib/shared/pending.ts +9 -6
  113. package/src/lib/shared/pendingAsyncCellsSlot.ts +13 -0
  114. package/src/lib/shared/prepareRpcModule.ts +91 -93
  115. package/src/lib/shared/prepareSocketModule.ts +3 -2
  116. package/src/lib/shared/probeRegistries.ts +5 -18
  117. package/src/lib/shared/reachable.ts +7 -10
  118. package/src/lib/shared/refresh.ts +12 -2
  119. package/src/lib/shared/refreshing.ts +8 -2
  120. package/src/lib/shared/resolvedCellsSlot.ts +13 -0
  121. package/src/lib/shared/reviveWireField.ts +49 -0
  122. package/src/lib/shared/reviveWireOutput.ts +36 -0
  123. package/src/lib/shared/rpcServerForRoot.ts +25 -0
  124. package/src/lib/shared/scanPages.ts +25 -0
  125. package/src/lib/shared/serializeEnv.ts +18 -6
  126. package/src/lib/shared/snapshotShippable.ts +8 -7
  127. package/src/lib/shared/snippet.ts +11 -6
  128. package/src/lib/shared/streamedCellsSlot.ts +14 -0
  129. package/src/lib/shared/subscribableFromResponse.ts +4 -4
  130. package/src/lib/shared/subscribableProbes.ts +1 -1
  131. package/src/lib/shared/tailProbeSlot.ts +1 -1
  132. package/src/lib/shared/types/AsyncComputed.ts +20 -0
  133. package/src/lib/shared/types/AsyncState.ts +13 -0
  134. package/src/lib/shared/types/CacheEntry.ts +7 -7
  135. package/src/lib/shared/types/CacheOptions.ts +23 -37
  136. package/src/lib/shared/types/CachePolicy.ts +25 -0
  137. package/src/lib/shared/types/CacheSnapshotEntry.ts +6 -5
  138. package/src/lib/shared/types/ErrorJsonSchemas.ts +8 -0
  139. package/src/lib/shared/types/HttpMethod.ts +3 -1
  140. package/src/lib/shared/types/InputCoercion.ts +17 -0
  141. package/src/lib/shared/types/{Subscribable.ts → NamedAsyncIterable.ts} +1 -1
  142. package/src/lib/shared/types/OutputWirePlan.ts +17 -0
  143. package/src/lib/shared/types/PagesScan.ts +7 -0
  144. package/src/lib/shared/types/PendingAsyncCells.ts +10 -0
  145. package/src/lib/shared/types/RawRemoteFunction.ts +6 -0
  146. package/src/lib/shared/types/RemoteCallable.ts +8 -7
  147. package/src/lib/shared/types/RemoteFunction.ts +16 -15
  148. package/src/lib/shared/types/ResolvedCells.ts +11 -0
  149. package/src/lib/shared/types/ReturnBody.ts +15 -0
  150. package/src/lib/shared/types/RpcBuildStamps.ts +23 -0
  151. package/src/lib/shared/types/RpcErrorGuard.ts +3 -8
  152. package/src/lib/shared/types/Socket.ts +4 -4
  153. package/src/lib/shared/types/SsrPayload.ts +5 -1
  154. package/src/lib/shared/types/StreamPolicy.ts +11 -0
  155. package/src/lib/shared/types/StreamedCells.ts +19 -0
  156. package/src/lib/shared/types/StreamedResolution.ts +24 -6
  157. package/src/lib/shared/types/TailHooks.ts +1 -1
  158. package/src/lib/shared/types/WireKind.ts +11 -0
  159. package/src/lib/shared/url.ts +5 -0
  160. package/src/lib/shared/validationHttpError.ts +33 -0
  161. package/src/lib/shared/warmSeedKey.ts +16 -0
  162. package/src/lib/shared/wireJsonReplacer.ts +30 -0
  163. package/src/lib/shared/writeRpcDts.ts +11 -1
  164. package/src/lib/shared/writeTestSocketsDts.ts +1 -1
  165. package/src/lib/test/createTestApp.ts +19 -1
  166. package/src/lib/ui/README.md +1 -1
  167. package/src/lib/ui/activePendingCells.ts +14 -0
  168. package/src/lib/ui/compile/BLOCK_KEYWORDS.ts +21 -0
  169. package/src/lib/ui/compile/SSR_ESCAPE.ts +4 -1
  170. package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +35 -7
  171. package/src/lib/ui/compile/abideUiPlugin.ts +19 -2
  172. package/src/lib/ui/compile/analyzeComponent.ts +89 -6
  173. package/src/lib/ui/compile/assignmentTargetNames.ts +54 -0
  174. package/src/lib/ui/compile/asyncInterpolationFields.ts +152 -0
  175. package/src/lib/ui/compile/asyncValuePositionError.ts +21 -0
  176. package/src/lib/ui/compile/asyncValuePositionInterpolations.ts +64 -0
  177. package/src/lib/ui/compile/attrLiftPosition.ts +18 -0
  178. package/src/lib/ui/compile/bindListenEvent.ts +5 -6
  179. package/src/lib/ui/compile/cachedSourceFile.ts +40 -0
  180. package/src/lib/ui/compile/catchBinding.ts +7 -5
  181. package/src/lib/ui/compile/classifyInterpolationType.ts +46 -0
  182. package/src/lib/ui/compile/collectAbideDiagnostics.ts +96 -0
  183. package/src/lib/ui/compile/compileComponent.ts +19 -3
  184. package/src/lib/ui/compile/compileModule.ts +27 -58
  185. package/src/lib/ui/compile/compileSSR.ts +52 -13
  186. package/src/lib/ui/compile/compileShadow.ts +265 -42
  187. package/src/lib/ui/compile/composeProps.ts +15 -4
  188. package/src/lib/ui/compile/createShadowLanguageService.ts +116 -68
  189. package/src/lib/ui/compile/createShadowProgram.ts +35 -6
  190. package/src/lib/ui/compile/declaredNames.ts +36 -0
  191. package/src/lib/ui/compile/desugarSignals.ts +419 -37
  192. package/src/lib/ui/compile/expressionIsPrefixEvaluable.ts +19 -0
  193. package/src/lib/ui/compile/generateBuild.ts +74 -17
  194. package/src/lib/ui/compile/generateSSR.ts +286 -75
  195. package/src/lib/ui/compile/hoistableAwaits.ts +193 -0
  196. package/src/lib/ui/compile/hoistableChildRenders.ts +112 -0
  197. package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +3 -1
  198. package/src/lib/ui/compile/interpolationClassifierForRoot.ts +37 -0
  199. package/src/lib/ui/compile/isSpuriousAsyncReadDiagnostic.ts +174 -0
  200. package/src/lib/ui/compile/isWhitespaceText.ts +10 -2
  201. package/src/lib/ui/compile/liftAsyncSubExpressions.ts +166 -0
  202. package/src/lib/ui/compile/lowerAsyncInterpolations.ts +92 -0
  203. package/src/lib/ui/compile/lowerContext.ts +10 -0
  204. package/src/lib/ui/compile/lowerDocAccess.ts +51 -10
  205. package/src/lib/ui/compile/lowerScript.ts +40 -4
  206. package/src/lib/ui/compile/nodeAtShadowOffset.ts +28 -0
  207. package/src/lib/ui/compile/parseTemplate.ts +18 -1090
  208. package/src/lib/ui/compile/parseTemplateRecovering.ts +1385 -0
  209. package/src/lib/ui/compile/referencedIdentifiers.ts +35 -0
  210. package/src/lib/ui/compile/renameSignalRefs.ts +26 -40
  211. package/src/lib/ui/compile/resolveReactiveExport.ts +4 -7
  212. package/src/lib/ui/compile/scopeCss.ts +27 -4
  213. package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +65 -0
  214. package/src/lib/ui/compile/shadowInterpolationClassifier.ts +47 -0
  215. package/src/lib/ui/compile/sourceFileOptionsSignature.ts +20 -0
  216. package/src/lib/ui/compile/structuralHeadTokens.ts +107 -0
  217. package/src/lib/ui/compile/templateSemanticTokens.ts +21 -0
  218. package/src/lib/ui/compile/templateStartOffset.ts +15 -0
  219. package/src/lib/ui/compile/tryPlan.ts +4 -3
  220. package/src/lib/ui/compile/types/AnalyzedComponent.ts +4 -0
  221. package/src/lib/ui/compile/types/AsyncInterpolationField.ts +19 -0
  222. package/src/lib/ui/compile/types/InterpolationClassifier.ts +12 -0
  223. package/src/lib/ui/compile/types/InterpolationKind.ts +7 -0
  224. package/src/lib/ui/compile/types/ParseDiagnostic.ts +8 -0
  225. package/src/lib/ui/compile/types/SeedTypeClassifier.ts +15 -0
  226. package/src/lib/ui/compile/types/TemplateNode.ts +36 -4
  227. package/src/lib/ui/compile/types/ValuePositionInterpolation.ts +13 -0
  228. package/src/lib/ui/compile/writtenTemplateNames.ts +84 -0
  229. package/src/lib/ui/computed.ts +28 -1
  230. package/src/lib/ui/createScope.ts +35 -68
  231. package/src/lib/ui/dom/anchoredBranch.ts +142 -0
  232. package/src/lib/ui/dom/appendText.ts +8 -3
  233. package/src/lib/ui/dom/awaitBlock.ts +62 -75
  234. package/src/lib/ui/dom/bindProp.ts +22 -0
  235. package/src/lib/ui/dom/bindableProp.ts +47 -0
  236. package/src/lib/ui/dom/cellPending.ts +24 -0
  237. package/src/lib/ui/dom/discardBoundary.ts +24 -6
  238. package/src/lib/ui/dom/disposeRange.ts +2 -1
  239. package/src/lib/ui/dom/each.ts +41 -12
  240. package/src/lib/ui/dom/eachAsync.ts +39 -3
  241. package/src/lib/ui/dom/fillBefore.ts +6 -7
  242. package/src/lib/ui/dom/fillBoundary.ts +2 -3
  243. package/src/lib/ui/dom/fillRange.ts +4 -4
  244. package/src/lib/ui/dom/hydrate.ts +6 -12
  245. package/src/lib/ui/dom/isComment.ts +1 -1
  246. package/src/lib/ui/dom/matchingRangeClose.ts +29 -0
  247. package/src/lib/ui/dom/mergeProps.ts +1 -1
  248. package/src/lib/ui/dom/mount.ts +1 -2
  249. package/src/lib/ui/dom/mountChild.ts +9 -40
  250. package/src/lib/ui/dom/mountRange.ts +2 -3
  251. package/src/lib/ui/dom/mountSlot.ts +1 -1
  252. package/src/lib/ui/dom/mountStreamedChild.ts +84 -0
  253. package/src/lib/ui/dom/mountSwappableRange.ts +46 -4
  254. package/src/lib/ui/dom/mutateDocContainer.ts +65 -0
  255. package/src/lib/ui/dom/on.ts +2 -2
  256. package/src/lib/ui/dom/readCell.ts +40 -0
  257. package/src/lib/ui/dom/restProps.ts +2 -2
  258. package/src/lib/ui/dom/spreadProps.ts +3 -4
  259. package/src/lib/ui/dom/switchBlock.ts +30 -7
  260. package/src/lib/ui/dom/tryBlock.ts +233 -70
  261. package/src/lib/ui/dom/types/SwitchCase.ts +5 -1
  262. package/src/lib/ui/dom/when.ts +12 -2
  263. package/src/lib/ui/dom/withScope.ts +9 -2
  264. package/src/lib/ui/finalizeStreamedChildren.ts +89 -0
  265. package/src/lib/ui/flight.ts +56 -0
  266. package/src/lib/ui/html.ts +12 -1
  267. package/src/lib/ui/isolateCellBarrier.ts +17 -0
  268. package/src/lib/ui/linked.ts +48 -2
  269. package/src/lib/ui/props.ts +17 -0
  270. package/src/lib/ui/remoteProxy.ts +84 -159
  271. package/src/lib/ui/renderChain.ts +53 -13
  272. package/src/lib/ui/renderToStream.ts +22 -18
  273. package/src/lib/ui/resumeSeedScript.ts +1 -1
  274. package/src/lib/ui/router.ts +86 -53
  275. package/src/lib/ui/runtime/AsyncCellError.ts +20 -0
  276. package/src/lib/ui/runtime/CELL_SEED.ts +14 -0
  277. package/src/lib/ui/runtime/CHILD_PRESENT.ts +2 -2
  278. package/src/lib/ui/runtime/CURRENT_BOUNDARY.ts +11 -0
  279. package/src/lib/ui/runtime/CURRENT_PATH.ts +29 -0
  280. package/src/lib/ui/runtime/RENDER.ts +10 -7
  281. package/src/lib/ui/runtime/RESUME.ts +4 -4
  282. package/src/lib/ui/runtime/STREAMED_CELLS.ts +57 -0
  283. package/src/lib/ui/runtime/ambientPathBacking.ts +32 -0
  284. package/src/lib/ui/runtime/applyPatchToTree.ts +17 -4
  285. package/src/lib/ui/runtime/blockId.ts +31 -0
  286. package/src/lib/ui/runtime/boundaryFor.ts +11 -0
  287. package/src/lib/ui/runtime/cellBarrierBacking.ts +30 -0
  288. package/src/lib/ui/runtime/createAsyncCell.ts +300 -0
  289. package/src/lib/ui/runtime/createDoc.ts +31 -54
  290. package/src/lib/ui/runtime/createEffectNode.ts +9 -0
  291. package/src/lib/ui/runtime/enterRenderPass.ts +5 -4
  292. package/src/lib/ui/runtime/flushEffects.ts +33 -1
  293. package/src/lib/ui/runtime/isAsyncFunction.ts +14 -0
  294. package/src/lib/ui/runtime/nextBlockId.ts +8 -7
  295. package/src/lib/ui/runtime/renderPath.ts +16 -0
  296. package/src/lib/ui/runtime/scope.ts +11 -0
  297. package/src/lib/ui/runtime/toTeardown.ts +10 -3
  298. package/src/lib/ui/runtime/types/Boundary.ts +9 -0
  299. package/src/lib/ui/runtime/types/RenderContext.ts +9 -7
  300. package/src/lib/ui/runtime/types/SsrRender.ts +9 -2
  301. package/src/lib/ui/runtime/types/UiComponent.ts +1 -5
  302. package/src/lib/ui/runtime/types/UiProps.ts +6 -5
  303. package/src/lib/ui/runtime/withOptionalPath.ts +8 -0
  304. package/src/lib/ui/runtime/withPath.ts +16 -0
  305. package/src/lib/ui/runtime/withPathFrom.ts +20 -0
  306. package/src/lib/ui/runtime/withoutHydration.ts +22 -0
  307. package/src/lib/ui/seedStreamedResolution.ts +31 -6
  308. package/src/lib/ui/settleAsyncCells.ts +24 -0
  309. package/src/lib/ui/socketProxy.ts +1 -1
  310. package/src/lib/ui/startClient.ts +16 -31
  311. package/src/lib/ui/state.ts +9 -3
  312. package/src/lib/ui/trackedComputed.ts +68 -0
  313. package/src/lib/ui/types/Scope.ts +8 -24
  314. package/src/lib/ui/watch.ts +3 -3
  315. package/src/serverEntry.ts +14 -0
  316. package/template/src/server/rpc/getHello.ts +15 -13
  317. package/template/test/app.test.ts +1 -1
  318. package/src/lib/server/runtime/devHotModuleResponse.ts +0 -41
  319. package/src/lib/shared/DEV_HOT_PREFIX.ts +0 -7
  320. package/src/lib/shared/UNREACHABLE_STATUSES.ts +0 -13
  321. package/src/lib/shared/hasReplayableRequest.ts +0 -17
  322. package/src/lib/shared/hydratingSlot.ts +0 -12
  323. package/src/lib/shared/outboxProbeSlot.ts +0 -20
  324. package/src/lib/shared/types/Outbox.ts +0 -9
  325. package/src/lib/shared/types/OutboxEntry.ts +0 -27
  326. package/src/lib/shared/types/SmartReadOptions.ts +0 -36
  327. package/src/lib/shared/wakeHydrationPeeks.ts +0 -20
  328. package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +0 -5
  329. package/src/lib/ui/compile/REACTIVE_CALLEES.ts +0 -11
  330. package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +0 -80
  331. package/src/lib/ui/compile/markupTokens.ts +0 -313
  332. package/src/lib/ui/compile/structuralBlockTokens.ts +0 -100
  333. package/src/lib/ui/dom/applyResolved.ts +0 -87
  334. package/src/lib/ui/dom/scopeLabel.ts +0 -21
  335. package/src/lib/ui/dom/text.ts +0 -20
  336. package/src/lib/ui/history.ts +0 -101
  337. package/src/lib/ui/installHotBridge.ts +0 -93
  338. package/src/lib/ui/installInspectorBridge.ts +0 -140
  339. package/src/lib/ui/outbox.ts +0 -35
  340. package/src/lib/ui/persist.ts +0 -115
  341. package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +0 -281
  342. package/src/lib/ui/rpcOutbox/outboxRegistry.ts +0 -69
  343. package/src/lib/ui/runtime/PATCH_BUS.ts +0 -28
  344. package/src/lib/ui/runtime/captureModelDoc.ts +0 -28
  345. package/src/lib/ui/runtime/hotInstances.ts +0 -10
  346. package/src/lib/ui/runtime/hotReloadEnabled.ts +0 -8
  347. package/src/lib/ui/runtime/hotReplace.ts +0 -38
  348. package/src/lib/ui/runtime/liveScopes.ts +0 -15
  349. package/src/lib/ui/runtime/localStoragePersistence.ts +0 -47
  350. package/src/lib/ui/runtime/registerHotInstance.ts +0 -23
  351. package/src/lib/ui/runtime/seedModelDoc.ts +0 -26
  352. package/src/lib/ui/runtime/types/HotInstance.ts +0 -22
  353. package/src/lib/ui/runtime/types/PatchEvent.ts +0 -16
  354. package/src/lib/ui/seedResolved.ts +0 -28
  355. package/src/lib/ui/sync.ts +0 -48
  356. package/src/lib/ui/types/History.ts +0 -14
  357. package/src/lib/ui/types/PersistHandle.ts +0 -11
  358. package/src/lib/ui/types/PersistenceStore.ts +0 -12
  359. package/src/lib/ui/types/ResolvedFrame.ts +0 -15
  360. package/src/lib/ui/types/SyncTransport.ts +0 -13
@@ -1,7 +1,6 @@
1
- import { hydratingSlot } from '../shared/hydratingSlot.ts'
1
+ import { hydrationWindow } from '../shared/hydrationWindow.ts'
2
2
  import { layoutChainForRoute } from '../shared/layoutChainForRoute.ts'
3
3
  import { matchRoute } from '../shared/matchRoute.ts'
4
- import { wakeHydrationPeeks } from '../shared/wakeHydrationPeeks.ts'
5
4
  import { fillBoundary } from './dom/fillBoundary.ts'
6
5
  import { outlet } from './dom/outlet.ts'
7
6
  import { effect } from './effect.ts'
@@ -20,6 +19,7 @@ import type { Route } from './runtime/types/Route.ts'
20
19
  import type { RouteLoader } from './runtime/types/RouteLoader.ts'
21
20
  import type { UiProps } from './runtime/types/UiProps.ts'
22
21
  import { untrack } from './runtime/untrack.ts'
22
+ import { withPath } from './runtime/withPath.ts'
23
23
 
24
24
  /* An outlet boundary — the `<!--abide:outlet-->`…`<!--/abide:outlet-->` marker pair a
25
25
  layer's content lives between (a layout's `<slot/>`, or the router's root boundary in
@@ -200,17 +200,19 @@ export function router(
200
200
  /* Route params as reactive thunks: reading `clientPage.value.params` inside the
201
201
  thunk tracks the page signal, so an in-place same-route hop (params change, the
202
202
  page stays mounted) re-runs each `props()` derive. The key set is stable across
203
- same-route hops, so the bag need not rebuild. A layout also gets `$children`
203
+ same-route hops, so the bag need not rebuild. A layout also gets `children`
204
204
  set to `CHILD_PRESENT` when a child layer exists below it, read by
205
205
  `{#if children}` (a layout's `{children()}` lowers to its `outlet()` boundary,
206
- so it ignores this value). */
206
+ so it ignores this value). Every bag entry is a thunk — `props()` destructure
207
+ lowers to `$props["key"]?.()` — so the sentinel must be wrapped too, or calling
208
+ it would invoke `CHILD_PRESENT` and yield `undefined` instead of the sentinel. */
207
209
  const propsBag = (hasChild: boolean): UiProps => {
208
210
  const bag: UiProps = {}
209
211
  for (const key of Object.keys(params)) {
210
212
  bag[key] = () => clientPage.value.params[key]
211
213
  }
212
214
  if (hasChild) {
213
- bag.$children = CHILD_PRESENT
215
+ bag.children = () => CHILD_PRESENT
214
216
  }
215
217
  return bag
216
218
  }
@@ -218,15 +220,17 @@ export function router(
218
220
  for (let depth = index; depth < layoutViews.length; depth += 1) {
219
221
  const view = layoutViews[depth] as Route
220
222
  PENDING_OUTLET.current = undefined
221
- const { dispose } = fillBoundary(
222
- boundary.open,
223
- boundary.close,
224
- view.build,
225
- /* A layout always has a child below: a deeper layout, or the page. */
226
- propsBag(depth < layoutViews.length - 1 || pageView !== undefined),
227
- /* The layout's route key names its scope in the inspector's Reactive tab
228
- (no host element to read a tag from — see `scopeLabel`). */
229
- chainKeys[depth],
223
+ /* Root this layer's render-path at its route key (the directory URL — stable
224
+ across reloads and globally unique), so every scope/cell it builds gets a
225
+ serialization-stable id under it (`withPath`). */
226
+ const { dispose } = withPath(chainKeys[depth] as string, () =>
227
+ fillBoundary(
228
+ boundary.open,
229
+ boundary.close,
230
+ view.build,
231
+ /* A layout always has a child below: a deeper layout, or the page. */
232
+ propsBag(depth < layoutViews.length - 1 || pageView !== undefined),
233
+ ),
230
234
  )
231
235
  const slot = PENDING_OUTLET.current
232
236
  if (slot === undefined) {
@@ -239,34 +243,32 @@ export function router(
239
243
  mountedPageKey = undefined
240
244
  return
241
245
  }
242
- disposePage = fillBoundary(
243
- boundary.open,
244
- boundary.close,
245
- pageView.build,
246
- /* A page is a leaf — no child layer. */
247
- propsBag(false),
248
- /* The page's route key names its scope in the inspector (see above). */
249
- pageKey,
246
+ /* Root the page's render-path at its route key (stable, unique) — see the layout
247
+ layer above. */
248
+ disposePage = withPath(pageKey, () =>
249
+ fillBoundary(
250
+ boundary.open,
251
+ boundary.close,
252
+ pageView.build,
253
+ /* A page is a leaf — no child layer. */
254
+ propsBag(false),
255
+ ),
250
256
  ).dispose
251
257
  mountedPageKey = pageKey
252
258
  }
253
259
  if (hydrating) {
254
260
  const previous = RENDER.hydration
255
- const previousHydrating = hydratingSlot.active
256
261
  RENDER.hydration = { next: new Map() }
257
- hydratingSlot.active = true
262
+ hydrationWindow.enter()
258
263
  enterRenderPass()
259
264
  try {
260
265
  run()
261
266
  } finally {
262
267
  exitRenderPass()
263
268
  RENDER.hydration = previous
264
- hydratingSlot.active = previousHydrating
265
- /* Wake the peeks this pass withheld for SSR congruence, now that the pass is
266
- over and the warm value is congruent to show. Only on the outermost unwind. */
267
- if (!previousHydrating) {
268
- wakeHydrationPeeks()
269
- }
269
+ /* Outermost exit clears the window and wakes the peeks this pass withheld
270
+ for SSR congruence, now that the warm value is congruent to show. */
271
+ hydrationWindow.exit()
270
272
  }
271
273
  return
272
274
  }
@@ -379,6 +381,15 @@ export function router(
379
381
  would re-run the router and re-mount the page, dropping local state. */
380
382
  const path = runtimePath.value
381
383
  untrack(() => {
384
+ /* Capture whether THIS run is the first paint, then flip the shared flag
385
+ synchronously. Previously `first` was only cleared inside the async `.then`,
386
+ so a second navigation that began before the boot resolve settled (a
387
+ double-tapped back button, two quick `navigate()` calls) still read
388
+ `first === true` and skipped its auth/redirect probe. Each run's `.then`
389
+ closes over its own `isFirstRun`, so hydration still keys off the real first
390
+ paint. */
391
+ const isFirstRun = first
392
+ first = false
382
393
  /* The route matches on the pathname only; the query/hash ride along for
383
394
  the probe (so server gating sees them) and for clientPage.url. */
384
395
  const pathname = path.split(/[?#]/)[0] ?? path
@@ -390,7 +401,7 @@ export function router(
390
401
  const targetUrl = resolveUrl(path)
391
402
  const mountedUrl = clientPage.value.url
392
403
  if (
393
- !first &&
404
+ !isFirstRun &&
394
405
  mountedUrl.pathname === targetUrl.pathname &&
395
406
  mountedUrl.search === targetUrl.search &&
396
407
  mountedUrl.hash !== targetUrl.hash
@@ -422,13 +433,13 @@ export function router(
422
433
  Skipped on first paint — there is no page to leave. An instant SPA hop
423
434
  (chunk cached, no probe) flips it back within the same microtask, so the
424
435
  browser never paints the intermediate state — no flash. */
425
- if (!first && !clientPage.value.navigating) {
436
+ if (!isFirstRun && !clientPage.value.navigating) {
426
437
  clientPage.value = { ...clientPage.value, navigating: true }
427
438
  }
428
439
  /* First paint adopts a document the server already ran handle() on;
429
440
  only later navigations re-run it through the probe. */
430
441
  const verdict: Promise<NavVerdict> =
431
- first || probe === undefined ? Promise.resolve({ kind: 'mount' }) : probe(path)
442
+ isFirstRun || probe === undefined ? Promise.resolve({ kind: 'mount' }) : probe(path)
432
443
  /* Resolve the page chunk, its layout chunks, and the gate in parallel,
433
444
  keeping the current chain mounted until all land — no blank frame while
434
445
  imports are in flight or the probe is in the air. */
@@ -439,14 +450,10 @@ export function router(
439
450
  ])
440
451
  .then(([pageView, resolvedLayouts, decision]) => {
441
452
  if (token !== sequence || disposed) {
442
- /* Consume the first-paint flag even when superseded: the boot SSR DOM
443
- is valid only for THIS boot navigation. Leaving `first` true would
444
- let a navigation that started before this settled (to a different
445
- page) skip its auth probe and hydrate the boot URL's SSR DOM as its
446
- own — claimExpected then throws and commit swallows it, stranding a
447
- broken page. Cleared here → the superseding nav builds create-mode
448
- (clearing the stale SSR DOM) and runs its probe. */
449
- first = false
453
+ /* Superseded (or disposed) drop this build. The first-paint flag was
454
+ already consumed synchronously at run start (`first = false`), so a
455
+ navigation that started after this one already saw `first === false`,
456
+ ran its probe, and builds in create mode over the stale SSR DOM. */
450
457
  return
451
458
  }
452
459
  /* handle() redirected: go where it pointed, replacing the blocked
@@ -474,9 +481,8 @@ export function router(
474
481
  ) {
475
482
  divergence += 1
476
483
  }
477
- const firstPaint = first
478
- const hydrating = first && pageView?.hydratable === true
479
- first = false
484
+ const firstPaint = isFirstRun
485
+ const hydrating = isFirstRun && pageView?.hydratable === true
480
486
  /* Same page, same layout chain — only params/url differ (e.g. stepping
481
487
  between episodes on one detail page). The whole structure survives, so
482
488
  publish the new snapshot on the reactive `page` proxy and let the mounted
@@ -526,15 +532,42 @@ export function router(
526
532
  disposeFrom(divergence)
527
533
  const url = resolveUrl(path)
528
534
  clientPage.value = { route: chainRoute, params, url, navigating: false }
529
- buildFrom(
530
- divergence,
531
- chainKeys,
532
- layoutViews,
533
- pageView,
534
- key,
535
- params,
536
- hydrating,
537
- )
535
+ try {
536
+ buildFrom(
537
+ divergence,
538
+ chainKeys,
539
+ layoutViews,
540
+ pageView,
541
+ key,
542
+ params,
543
+ hydrating,
544
+ )
545
+ } catch (error) {
546
+ /* A cold mount owns no claim cursor, so the SSR-vs-client claim asserts
547
+ (`assertClaimedText`, `claimExpected`) can't fire there — a throw from
548
+ it is a genuine codegen/user-render defect. Rethrow to `commit`, which
549
+ surfaces it and stops (a reload would re-run the same failure). */
550
+ if (!hydrating) {
551
+ throw error
552
+ }
553
+ /* A HYDRATING first paint threw — a server↔client markup divergence (a
554
+ blocking cell whose resolved value the server couldn't serialize into
555
+ `__SSR__.cells`, so the client reads pending where SSR baked a value;
556
+ or any other SSR/client mismatch). A reload re-runs the same SSR, so
557
+ recover CLIENT-side: discard the server markup and mount the page fresh
558
+ (cold — pending reads render their empty state and fill in on settle,
559
+ congruent because nothing is being adopted). Reset to the pristine
560
+ pre-mount state so `run` re-establishes the root boundary and clears the
561
+ host, then rebuild the whole chain cold. */
562
+ console.warn(
563
+ `[abide] hydration mismatch at ${path} — discarding server markup and rendering on the client:`,
564
+ error,
565
+ )
566
+ disposeFrom(0)
567
+ rootBoundary = undefined
568
+ mountedPageKey = undefined
569
+ buildFrom(0, chainKeys, layoutViews, pageView, key, params, false)
570
+ }
538
571
  /* Reapply the destination entry's scroll once its DOM exists — a
539
572
  back/forward restores its offset, a fresh nav scrolls to the `#hash`
540
573
  anchor (now built) or the top. SKIPPED on a hydrating first paint:
@@ -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
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  A no-op truthiness marker the router (client) and `renderChain` (SSR) set as a layout's
3
- `$props.$children` when a child layer (a nested layout or the page) exists below it. Read
4
- only by `{#if children}` (→ `$props?.$children`); a layout's `{children()}` lowers to the
3
+ `$props.children` when a child layer (a nested layout or the page) exists below it. Read
4
+ only by `{#if children}` (→ `$props?.children`); a layout's `{children()}` lowers to the
5
5
  `outlet()` boundary the router fills, so this value is never invoked — it exists purely so
6
6
  `{#if children}` reads a uniform presence signal on both the client and the server.
7
7
  */
@@ -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
+ }
@@ -1,3 +1,4 @@
1
+ import { setOwnProperty } from '../../shared/setOwnProperty.ts'
1
2
  import type { Patch } from './types/Patch.ts'
2
3
 
3
4
  /*
@@ -6,7 +7,7 @@ value only when the root itself is replaced). In-place is the deliberate choice:
6
7
  the patch path already names exactly what changed, so we never need copy-on-write
7
8
  to *detect* a change — and cloning the spine made every leaf write O(width) of
8
9
  the widest ancestor (a 5k-item list copied 50k times in the bench). Mutating is
9
- 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),
10
11
  not by retaining old roots — which is cheaper anyway. The wake step in createDoc
11
12
  force-notifies ancestor readers from the path, since their container keeps its
12
13
  identity now.
@@ -21,16 +22,28 @@ export function applyPatchToTree(tree: unknown, patch: Patch, segments: string[]
21
22
  }
22
23
  let parent = tree as Record<string, unknown>
23
24
  for (const segment of segments.slice(0, -1)) {
24
- parent = parent[segment] as Record<string, unknown>
25
+ /* Only descend into an OWN property. A segment of `__proto__`/`constructor`/
26
+ `prototype` is not own on a plain data object/array, so this refuses to walk
27
+ into a shared prototype (or the constructor function) that a later write would
28
+ then pollute — the real vector, since `apply` is reachable from `sync()` with
29
+ unvalidated peer-controlled patch paths. A legit document key that happens to be
30
+ named `constructor` is an own data property, so `hasOwn` still traverses it. */
31
+ const next = Object.hasOwn(parent, segment) ? parent[segment] : undefined
32
+ if (next === null || typeof next !== 'object') {
33
+ throw new TypeError(
34
+ `abide: patch path segment "${segment}" does not address a container`,
35
+ )
36
+ }
37
+ parent = next as Record<string, unknown>
25
38
  }
26
39
  const key = segments[segments.length - 1] as string
27
40
  if (patch.op === 'replace') {
28
- parent[key] = patch.value
41
+ setOwnProperty(parent, key, patch.value)
29
42
  } else if (patch.op === 'add') {
30
43
  if (Array.isArray(parent)) {
31
44
  parent.splice(key === '-' ? parent.length : Number(key), 0, patch.value)
32
45
  } else {
33
- parent[key] = patch.value
46
+ setOwnProperty(parent, key, patch.value)
34
47
  }
35
48
  } else if (Array.isArray(parent)) {
36
49
  parent.splice(Number(key), 1)
@@ -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
+ }