@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
@@ -46,7 +46,17 @@ export type TemplateNode =
46
46
  keyLoc?: number
47
47
  indexLoc?: number
48
48
  }
49
- | { kind: 'if'; condition: string; children: TemplateNode[]; loc?: number }
49
+ | {
50
+ kind: 'if'
51
+ condition: string
52
+ children: TemplateNode[]
53
+ loc?: number
54
+ /* `condition` is a bare async subject — the WHOLE test was one promise/stream that
55
+ `lowerAsyncInterpolations` lifted to a single peek-cell (`{#if getX()}`). The binary
56
+ back-ends then read its pending facet so a still-loading subject renders NO branch,
57
+ not the falsy `{:else}`. Unset (or on an `{:elseif}` chain) → the plain truthy test. */
58
+ asyncSubject?: boolean
59
+ }
50
60
  | {
51
61
  kind: 'await'
52
62
  promise: string
@@ -77,11 +87,29 @@ export type TemplateNode =
77
87
  loc?: number
78
88
  /* Each authored attribute as a prop. A `spread` entry (`{...code}`) carries no
79
89
  `name`; its keys merge in at runtime (`mergeProps`/`spreadProps`). `nameLoc` is
80
- the prop name's source offset — the anchor for an excess-prop diagnostic. */
81
- props: { name: string; code: string; loc?: number; nameLoc?: number; spread?: boolean }[]
90
+ the prop name's source offset — the anchor for an excess-prop diagnostic. `bind`
91
+ marks a `bind:<name>={target}` two-way prop: `code` is the writable target (an
92
+ lvalue or a `{ get, set }` accessor), passed with a write-back channel so the
93
+ child can push changes upstream (`bindProp`/`bindableProp`). */
94
+ props: {
95
+ name: string
96
+ code: string
97
+ loc?: number
98
+ nameLoc?: number
99
+ spread?: boolean
100
+ bind?: true
101
+ }[]
82
102
  children: TemplateNode[]
83
103
  }
84
- | { kind: 'switch'; subject: string; children: TemplateNode[]; loc?: number }
104
+ | {
105
+ kind: 'switch'
106
+ subject: string
107
+ children: TemplateNode[]
108
+ loc?: number
109
+ /* `subject` is a bare async subject (the whole expression lifted to one peek-cell) —
110
+ a still-loading subject selects no case (not even the default) until it settles. */
111
+ asyncSubject?: boolean
112
+ }
85
113
  /* A branch of a `<template switch>` (`match` set) or `<template if>` chain. Inside an
86
114
  `if`, `<template elseif={c}>` sets `condition` (match-less, truthy-tested), and
87
115
  `<template else>` leaves both unset (the default). `loc` points at whichever
@@ -92,6 +120,10 @@ export type TemplateNode =
92
120
  condition?: string
93
121
  children: TemplateNode[]
94
122
  loc?: number
123
+ /* An `{:elseif}` whose `condition` is a bare async subject (the whole test lifted to one
124
+ peek-cell) — the cond-chain holds at this branch while the cell is pending, resuming the
125
+ top-down walk (skip it / match it / fall to the next branch) once it settles. */
126
+ asyncSubject?: boolean
95
127
  }
96
128
  /* A `{#snippet row(item)}` snippet: a named, scope-capturing builder declared
97
129
  once and called like a function (`{row(item)}`). `params` is the raw
@@ -0,0 +1,13 @@
1
+ /*
2
+ A template interpolation sitting in a NON-content value position — where a
3
+ `Promise`/`AsyncIterable` can't be rendered over time and would silently stringify
4
+ to `[object Promise]` (Stage E of type-directed lowering, ADR-0019). `loc`/`code`
5
+ are the expression's source offset and verbatim text (the classifier's key), and
6
+ `position` names the position for the diagnostic. `for await` is the sanctioned
7
+ `{#for await}` iterable — the one value position where an `AsyncIterable` is allowed.
8
+ */
9
+ export type ValuePositionInterpolation = {
10
+ loc: number
11
+ code: string
12
+ position: 'attribute' | 'if' | 'switch' | 'each' | 'for await'
13
+ }
@@ -0,0 +1,84 @@
1
+ import ts from 'typescript'
2
+ import { assignmentTargetNames } from './assignmentTargetNames.ts'
3
+ import { isPlainIdentifier } from './isPlainIdentifier.ts'
4
+ import type { TemplateNode } from './types/TemplateNode.ts'
5
+
6
+ /*
7
+ Every name the TEMPLATE writes — the counterpart to the leading script's own write
8
+ scan. Two ways a prop's name shows up written in markup:
9
+ • forwarded as a `bind:` target — `bind:value={value}` on a child element or component
10
+ passes the name as a two-way target, so it must be a writable cell (a bare identifier
11
+ only; a `{ get, set }` accessor carries its own write path and reads the prop plainly);
12
+ • assigned in a template expression — an event handler like `onclick={() => value += 1}`.
13
+
14
+ Collected across every JS fragment the markup carries (attribute/prop/event/text
15
+ expressions and nested `<script>` bodies), then intersected with the actual prop names by
16
+ the caller. Syntactic and conservative — see `assignmentTargetNames`.
17
+ */
18
+ export function writtenTemplateNames(nodes: TemplateNode[]): Set<string> {
19
+ const names = new Set<string>()
20
+ /* One synthetic source of every expression fragment, parsed once for its write targets. */
21
+ let fragments = ''
22
+ const addFragment = (code: string | undefined): void => {
23
+ if (code !== undefined && code.trim() !== '') {
24
+ fragments += `${code};\n`
25
+ }
26
+ }
27
+ /* A bare-identifier `bind:` target names a prop forwarded as a two-way channel. */
28
+ const addBindTarget = (code: string): void => {
29
+ if (isPlainIdentifier(code.trim())) {
30
+ names.add(code.trim())
31
+ }
32
+ }
33
+ const walk = (node: TemplateNode): void => {
34
+ if (node.kind === 'text') {
35
+ for (const part of node.parts) {
36
+ if (part.kind === 'expression') {
37
+ addFragment(part.code)
38
+ }
39
+ }
40
+ return
41
+ }
42
+ if (node.kind === 'script') {
43
+ addFragment(node.code)
44
+ return
45
+ }
46
+ if (node.kind === 'element') {
47
+ for (const attr of node.attrs) {
48
+ if (attr.kind === 'bind') {
49
+ addBindTarget(attr.code)
50
+ addFragment(attr.code)
51
+ } else if (attr.kind === 'interpolated') {
52
+ for (const part of attr.parts) {
53
+ if (part.kind === 'expression') {
54
+ addFragment(part.code)
55
+ }
56
+ }
57
+ } else if (attr.kind !== 'static') {
58
+ addFragment(attr.code)
59
+ }
60
+ }
61
+ }
62
+ if (node.kind === 'component') {
63
+ for (const prop of node.props) {
64
+ if (prop.bind === true) {
65
+ addBindTarget(prop.code)
66
+ }
67
+ addFragment(prop.code)
68
+ }
69
+ }
70
+ if ('children' in node) {
71
+ for (const child of node.children) {
72
+ walk(child)
73
+ }
74
+ }
75
+ }
76
+ for (const node of nodes) {
77
+ walk(node)
78
+ }
79
+ if (fragments.trim() !== '') {
80
+ const source = ts.createSourceFile('template.ts', fragments, ts.ScriptTarget.Latest, true)
81
+ assignmentTargetNames(source, names)
82
+ }
83
+ return names
84
+ }
@@ -1,4 +1,8 @@
1
+ import type { AsyncComputed } from '../shared/types/AsyncComputed.ts'
2
+ import type { NamedAsyncIterable } from '../shared/types/NamedAsyncIterable.ts'
3
+ import { createAsyncCell } from './runtime/createAsyncCell.ts'
1
4
  import { createComputedNode } from './runtime/createComputedNode.ts'
5
+ import { isAsyncFunction } from './runtime/isAsyncFunction.ts'
2
6
  import { OWNER } from './runtime/OWNER.ts'
3
7
  import { readNode } from './runtime/readNode.ts'
4
8
  import type { Computed } from './runtime/types/Computed.ts'
@@ -13,8 +17,31 @@ would be a write to its *sources*, which is expressed at the binding site instea
13
17
  (`bind:value={{ get, set }}`), where the write actually originates. Lazy: it
14
18
  recomputes on read only when a dependency changed, and never serializes (it is
15
19
  re-derived from its inputs on resume). Read via `.value`.
20
+
21
+ When the seed *tracks* an async source it becomes an async cell instead (ADR-0019
22
+ D1): an `async () => await …` thunk (the compiler's `await` lowering) unwraps the
23
+ promise, and a thunk producing a `NamedAsyncIterable` (socket / streaming rpc)
24
+ auto-tracks its frames. Both yield an `AsyncComputed<T>` — no `.value`, read through
25
+ the probe family (`peek`/`pending`/`refreshing`/`error`/`refresh`). A thunk that
26
+ returns a *bare* promise (no `await`) is held opaquely as a normal `Computed<Promise<T>>`
27
+ — render it with `{#await}`.
16
28
  */
17
- export function computed<T>(compute: () => T): Computed<T> {
29
+ export function computed<T>(compute: () => NamedAsyncIterable<T>): AsyncComputed<T>
30
+ export function computed<T>(compute: () => Promise<T>): AsyncComputed<T>
31
+ export function computed<T>(compute: () => T): Computed<T>
32
+ export function computed<T>(
33
+ compute: () => T | Promise<T> | NamedAsyncIterable<T>,
34
+ ): Computed<T> | AsyncComputed<T> {
35
+ /* `await` marker: an async-function thunk unwraps its promise into a tracked async cell.
36
+ This is the only classification the read-only primitive can make without running the
37
+ seed — a sync `computed` MUST stay lazy (it does not compute until first read). A stream
38
+ seed (`computed(getStream())`) can only be told from a sync seed by running it, which
39
+ laziness forbids here; the compiler routes that eager path to `createAsyncCell` directly.
40
+ A bare (un-awaited) promise stays a lazy `Computed<Promise<T>>` — render it with `{#await}`. */
41
+ if (isAsyncFunction(compute)) {
42
+ return createAsyncCell(compute as () => unknown, { writable: false }) as AsyncComputed<T>
43
+ }
44
+ /* Sync value or a bare (opaque) promise: the lazy computed, unchanged. */
18
45
  const node = createComputedNode(compute as () => unknown)
19
46
  /* Tear down with the enclosing scope, the way an effect does. A computed only
20
47
  unlinks from its sources when it re-runs (`runNode` re-tracking); one read
@@ -1,62 +1,68 @@
1
1
  import { computed } from './computed.ts'
2
2
  import { effect } from './effect.ts'
3
- import { history } from './history.ts'
4
3
  import { linked } from './linked.ts'
5
- import { persist as persistDoc } from './persist.ts'
4
+ import { CURRENT_PATH } from './runtime/CURRENT_PATH.ts'
6
5
  import { createDoc } from './runtime/createDoc.ts'
7
- import { liveScopes } from './runtime/liveScopes.ts'
8
6
  import type { Cell } from './runtime/types/Cell.ts'
9
7
  import type { Doc } from './runtime/types/Doc.ts'
10
8
  import { state } from './state.ts'
11
- import { sync } from './sync.ts'
12
- import type { History } from './types/History.ts'
13
- import type { PersistHandle } from './types/PersistHandle.ts'
9
+ import { trackedComputed } from './trackedComputed.ts'
14
10
  import type { Scope } from './types/Scope.ts'
15
11
 
16
- /* A process-stable counter id. The serialization-stable LEXICAL id (route +
17
- component + tree position) that `persist`/`broadcast` want across reloads/peers
18
- is stamped by the compiler later; until then a scope's id is unique within a
19
- run enough for in-session undo and the bus, and as an explicit key's fallback. */
12
+ /* The counter fallback for a DETACHED scope — one created outside any render (a bare
13
+ `scope()` on first use), where the ambient render-path is empty.
14
+ A rendered scope instead takes the serialization-stable render-path id (`CURRENT_PATH`,
15
+ route + tree position), so an async cell's warm-seed key (`${scope.id}:${index}`) is
16
+ stable across the SSR→client handoff; only the counter path remains merely run-unique. */
20
17
  let nextId = 0
21
18
 
22
19
  /*
23
20
  Builds a lexical scope. Its data is a document — created eagerly from `initial`,
24
21
  or (when `awaiting`) ADOPTED from the first `doc()` a component body creates under
25
22
  it, so a scope can wrap the component's own model without changing the data
26
- lowering. Data methods mirror `Doc` and delegate to that document; capabilities
27
- are lazy (`record`/`persist` attach `history`/`persist` to it on first call);
28
- `child` nests; `dispose` tears the subtree down children-first.
23
+ lowering. Data methods mirror `Doc` and delegate to that document; `child` nests;
24
+ `dispose` tears the subtree down children-first.
29
25
  */
30
26
  export function createScope(
31
27
  initial: unknown = {},
32
28
  parent: Scope | undefined = undefined,
33
29
  awaiting = false,
34
- label: string | undefined = undefined,
35
30
  ): Scope {
36
31
  /* Eager unless awaiting adoption; `data()` lazily mints an empty doc if a body
37
32
  never created one (a stateless component that still reaches for its scope). */
38
33
  let document: Doc | undefined = awaiting ? undefined : createDoc(initial)
39
34
  const data = (): Doc => (document ??= createDoc({}))
40
- const id = parent === undefined ? `scope-${nextId++}` : `${parent.id}.${nextId++}`
41
- const children: Scope[] = []
35
+ /* The scope's serialization-stable id: the ambient render-path when created inside a render
36
+ (route + layout + branch/row + component ordinal, composed by `withPath` at each nesting
37
+ site), else the run-unique counter for a detached scope. Snapshotted at creation, so a
38
+ nested push doesn't mutate an ancestor's id. */
39
+ const renderPath = CURRENT_PATH.current
40
+ const id =
41
+ renderPath !== ''
42
+ ? renderPath
43
+ : parent === undefined
44
+ ? `scope-${nextId++}`
45
+ : `${parent.id}.${nextId++}`
42
46
  /* Adopted build teardowns (the reactivity stopper from the mount core). Disposed
43
- first and in reverse on teardown, before children and capabilities so the one
44
- `dispose` runs the order the call sites hand-composed as `stop(); lexical.dispose()`. */
47
+ first and in reverse on teardown so the one `dispose` runs the order the call sites
48
+ hand-composed as `stop(); lexical.dispose()`. */
45
49
  const owned: Array<() => void> = []
46
50
  /* Context values shared down the tree, held apart from the reactive doc (which
47
51
  a child does not inherit): keyed by name, read by the closest ancestor walk. */
48
52
  const shared = new Map<string, unknown>()
49
- let past: History | undefined
50
- let persistence: PersistHandle | undefined
51
- let unsync: (() => void) | undefined
53
+ /* Per-component monotonic index for the async cells constructed under this scope, in
54
+ declaration order the local half of a cell's serialization-stable warm-seed key
55
+ (`${scope.id}:${index}`, see `createAsyncCell`). Per-scope (not global) so a client-only
56
+ sibling component can't shift another component's cell keys: divergence stays local. Both
57
+ SSR and client construct a component's cells in the same order, so the indices agree. */
58
+ let cellIndex = 0
52
59
 
53
- /* `cell` is not on the public `Scope` type — it is the compiler-only leaf the
54
- cell-hoisting lowering targets (`const _cell0 = $$scope().cell("path")`, see
55
- `hoistCells`). It stays on the runtime object but off the documented surface,
56
- so authors reach data through `read`/`replace`/`derive`, not a raw cell handle. */
57
- const self: Scope & { cell: <T>(path: string) => Cell<T> } = {
60
+ /* `cell` and `nextCellIndex` are not on the public `Scope` type — `cell` is the compiler-only
61
+ cell-hoisting leaf; `nextCellIndex` is drawn by `createAsyncCell` for its warm-seed key.
62
+ Both stay on the runtime object but off the documented surface. */
63
+ const self: Scope & { cell: <T>(path: string) => Cell<T>; nextCellIndex: () => number } = {
58
64
  id,
59
- label,
65
+ nextCellIndex: () => cellIndex++,
60
66
  parent,
61
67
  read: (path) => data().read(path),
62
68
  replace: (path, value) => data().replace(path, value),
@@ -73,16 +79,11 @@ export function createScope(
73
79
  state,
74
80
  linked,
75
81
  computed,
82
+ trackedComputed,
76
83
  effect,
77
84
  own: (dispose) => {
78
85
  owned.push(dispose)
79
86
  },
80
- child: (childInitial = {}) => {
81
- const created = createScope(childInitial, self)
82
- children.push(created)
83
- return created
84
- },
85
- root: () => (parent === undefined ? self : parent.root()),
86
87
  /* Reference store — no tracking, so a lookup never subscribes; reactivity comes
87
88
  from what is shared (a scope, whose doc is reactive), not from the share. */
88
89
  share: (key, value) => {
@@ -93,49 +94,15 @@ export function createScope(
93
94
  `undefined` from "not provided"; undefined at the root means no provider. */
94
95
  shared: <T>(key: string): T | undefined =>
95
96
  shared.has(key) ? (shared.get(key) as T) : parent?.shared<T>(key),
96
- record: (options) => {
97
- past ??= history(data(), options)
98
- },
99
- persist: (key) => {
100
- persistence ??= persistDoc(data(), key ?? id)
101
- },
102
- broadcast: (transport) => {
103
- unsync ??= sync(data(), transport)
104
- },
105
- undo: () => past?.undo(),
106
- redo: () => past?.redo(),
107
- canUndo: () => past?.canUndo() ?? false,
108
- canRedo: () => past?.canRedo() ?? false,
109
97
  dispose: () => {
110
- /* Stop the build's reactivity first (reverse order), before tearing down nested
111
- children and the boundary-crossing capabilities — the order the call sites
98
+ /* Stop the build's reactivity in reverse order the order the call sites
112
99
  hand-composed as `stop(); lexical.dispose()`. */
113
100
  for (let index = owned.length - 1; index >= 0; index -= 1) {
114
101
  owned[index]?.()
115
102
  }
116
103
  owned.length = 0
117
- /* Children reverse too (last created first), so a later child that captured an
118
- earlier sibling tears down before the sibling it depends on — LIFO like `owned`. */
119
- for (let index = children.length - 1; index >= 0; index -= 1) {
120
- children[index]?.dispose()
121
- }
122
- children.length = 0
123
104
  shared.clear()
124
- past?.dispose()
125
- past = undefined
126
- persistence?.dispose()
127
- persistence = undefined
128
- unsync?.()
129
- unsync = undefined
130
- if (liveScopes.enabled) {
131
- liveScopes.scopes.delete(self)
132
- }
133
105
  },
134
106
  }
135
- /* Dev-only: register for the inspector's scope-tree view. Gated, so production
136
- never touches the set. */
137
- if (liveScopes.enabled) {
138
- liveScopes.scopes.add(self)
139
- }
140
107
  return self
141
108
  }
@@ -0,0 +1,142 @@
1
+ import { scope } from '../runtime/scope.ts'
2
+ import { buildDetachedRange } from './buildDetachedRange.ts'
3
+ import { removeRange } from './removeRange.ts'
4
+
5
+ /*
6
+ The single-active-branch skeleton shared by `awaitBlock` and `tryBlock`: one
7
+ marker-bounded `[`…`]` range parked before an `anchor`, swapped as a unit when the
8
+ block settles to a different branch. Both blocks previously copy-pasted this skeleton —
9
+ the `active` range record, `detach` (dispose + `removeRange`), the create-path `place`
10
+ (build a detached range and insert it before the anchor), and the hydration `adoptStrand`
11
+ guard — even though their ESSENTIAL orchestration (await's then/catch/finally settlement,
12
+ try's boundary recovery) is genuinely bespoke and stays in each block. This carries only
13
+ the accidental copy-paste, notably its two subtle guards:
14
+
15
+ 1. the detached-anchor short-circuit in `place` (drop a late swap whose anchor has been
16
+ pulled from the tree), and
17
+ 2. the adopt-strand-dispose guard in `adoptStrand` (a partial hydration build strands a
18
+ live sub-scope, so ALWAYS take its disposer and run it on ANY failure before
19
+ rethrowing, so the caller's cold rebuild starts clean).
20
+
21
+ `anchor` is a mutable field: each block parks its own anchor text node at create /
22
+ hydrate / rebuild time (placement is bespoke), and `place` reads it. The block owns its
23
+ `scopeGroup`; each built branch's disposer is tracked in it so it disposes on owner
24
+ teardown, not only on a swap.
25
+ */
26
+ export function anchoredBranch(
27
+ parent: Node,
28
+ group: { track: (dispose: () => void) => () => void },
29
+ ): {
30
+ anchor: Node | undefined
31
+ detach: () => void
32
+ place: (build: (host: Node) => void, wrapBuild?: (run: () => void) => void) => void
33
+ adoptStrand: (
34
+ build: (host: Node) => void,
35
+ afterBuild: () => { start: Comment; end: Comment },
36
+ ) => void
37
+ } {
38
+ let active: { start: Comment; end: Comment; dispose: () => void } | undefined
39
+ let anchor: Node | undefined
40
+
41
+ const detach = (): void => {
42
+ if (active !== undefined) {
43
+ active.dispose()
44
+ /* `removeRange` evicts the markers AND everything between them via the end
45
+ marker's LIVE parent — not the captured `parent`, which (when the block is a
46
+ bare child of a control-flow branch) is the branch's build fragment, emptied
47
+ into the document once the enclosing block placed it. */
48
+ removeRange(active.start, active.end)
49
+ active = undefined
50
+ }
51
+ }
52
+
53
+ /* Replace the current content with a freshly-built branch, before the anchor. The branch
54
+ builds into a detached `[`…`]`-bracketed fragment (so any content — components, text,
55
+ nested blocks — appends freely), the same create primitive the keyed-list runtimes use,
56
+ which lands as a marker-bounded range the next swap detaches with `removeRange`.
57
+ `wrapBuild` (tryBlock only) wraps just the build call so the guarded branch's effects
58
+ build under the ambient error boundary; detach/insert stay outside it. */
59
+ const place = (build: (host: Node) => void, wrapBuild?: (run: () => void) => void): void => {
60
+ /* Backstop for a swap whose anchor has been detached from the tree. The block's own
61
+ generation guard is the PRIMARY defense — it drops a late settle after the owner
62
+ tears down — but a gap (e.g. a nested hydration `adopt` that aborts to `rebuildCold`,
63
+ leaving the inner block's range removed while its guard stays live) can still route a
64
+ late swap here with `anchor` already pulled out of the DOM. Inserting before a node
65
+ that is no longer a child of any parent throws `NotFoundError` from `insertBefore` —
66
+ surfacing as a process-fatal unhandled rejection under Bun. A detached anchor
67
+ unambiguously means the block is gone, so drop the swap. */
68
+ if (anchor !== undefined && anchor.parentNode === null) {
69
+ return
70
+ }
71
+ detach()
72
+ const namespaceParent = anchor?.parentNode ?? parent
73
+ let built: ReturnType<typeof buildDetachedRange> | undefined
74
+ const run = (): void => {
75
+ built = buildDetachedRange(namespaceParent, build)
76
+ }
77
+ if (wrapBuild !== undefined) {
78
+ wrapBuild(run)
79
+ } else {
80
+ run()
81
+ }
82
+ const range = built as ReturnType<typeof buildDetachedRange>
83
+ const tracked = group.track(range.dispose)
84
+ namespaceParent.insertBefore(range.fragment, anchor ?? null)
85
+ active = { start: range.start, end: range.end, dispose: tracked }
86
+ }
87
+
88
+ /* Adopt an SSR branch in place (its content claims the existing nodes). Adoption is
89
+ guarded: a build that can't claim the server markup — a resume value that didn't
90
+ round-trip, a nested-adopt claim desync — throws, and the caller recovers by rebuilding
91
+ cold. But the partial build may have already created a live sub-scope (an inner `await`'s
92
+ effect/guard, a subscription) before it threw; letting the throw escape `scope()` would
93
+ strand that scope's disposer (unreachable → never disposed), leaking the effect AND
94
+ leaving its guard un-bumped so a late settle stays "live". So capture the build's error,
95
+ ALWAYS take the returned disposer, and dispose it on ANY failure before rethrowing — the
96
+ caller's cold rebuild then starts from a clean slate. `afterBuild` (bespoke per block)
97
+ claims/creates the range markers around the adopted nodes and parks the anchor, returning
98
+ the range's start/end; it runs only after a clean build. */
99
+ const adoptStrand = (
100
+ build: (host: Node) => void,
101
+ afterBuild: () => { start: Comment; end: Comment },
102
+ ): void => {
103
+ let dispose: (() => void) | undefined
104
+ try {
105
+ let buildFailed = false
106
+ let buildError: unknown
107
+ dispose = group.track(
108
+ scope(() => {
109
+ try {
110
+ build(parent)
111
+ } catch (error) {
112
+ buildFailed = true
113
+ buildError = error
114
+ }
115
+ }),
116
+ )
117
+ if (buildFailed) {
118
+ throw buildError
119
+ }
120
+ const range = afterBuild()
121
+ active = { start: range.start, end: range.end, dispose }
122
+ } catch (error) {
123
+ /* `dispose` (the group-tracked wrapper) is idempotent; running it here tears the
124
+ partial branch scope down and drops it from the group so the cold rebuild doesn't
125
+ inherit a stranded scope, then the caller falls back to a cold build. */
126
+ dispose?.()
127
+ throw error
128
+ }
129
+ }
130
+
131
+ return {
132
+ get anchor(): Node | undefined {
133
+ return anchor
134
+ },
135
+ set anchor(node: Node | undefined) {
136
+ anchor = node
137
+ },
138
+ detach,
139
+ place,
140
+ adoptStrand,
141
+ }
142
+ }
@@ -38,7 +38,10 @@ export function appendText(parent: Node, read: () => unknown, splitAlways = fals
38
38
  const hydration = RENDER.hydration
39
39
  if (hydration !== undefined) {
40
40
  const claimed = claimChild(hydration, parent)
41
- const value = String(read())
41
+ /* Nullish reads render as empty text, never the literal `"undefined"` — so a
42
+ pending async read (undefined-while-pending, ADR-0032 D3) shows nothing. */
43
+ const firstValue = read()
44
+ const value = firstValue == null ? '' : String(firstValue)
42
45
  /* A value that first rendered empty produced NO server text node, so the cursor
43
46
  points at the following node (an element/comment) or past the end (null) — not a
44
47
  text node to claim. Bind to a Text node either way: claim the merged SSR node when
@@ -74,14 +77,16 @@ export function appendText(parent: Node, read: () => unknown, splitAlways = fals
74
77
  at the end). */
75
78
  hydration.next.set(parent, isText ? node.nextSibling : claimed)
76
79
  effect(() => {
77
- node.data = String(read())
80
+ const next = read()
81
+ node.data = next == null ? '' : String(next)
78
82
  })
79
83
  return
80
84
  }
81
85
  const node = document.createTextNode('')
82
86
  parent.appendChild(node)
83
87
  effect(() => {
84
- node.data = String(read())
88
+ const next = read()
89
+ node.data = next == null ? '' : String(next)
85
90
  })
86
91
  }
87
92