@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
@@ -13,6 +13,8 @@ import { composeProps } from './composeProps.ts'
13
13
  import { eachPlan } from './eachPlan.ts'
14
14
  import { elementPlan } from './elementPlan.ts'
15
15
  import { groupBindParts } from './groupBindParts.ts'
16
+ import { hoistableAwaits } from './hoistableAwaits.ts'
17
+ import { hoistableChildRenders } from './hoistableChildRenders.ts'
16
18
  import { ifPlan } from './ifPlan.ts'
17
19
  import { interpolatedTemplateLiteral } from './interpolatedTemplateLiteral.ts'
18
20
  import { isAnchorPositioned } from './isAnchorPositioned.ts'
@@ -66,10 +68,16 @@ export function generateSSR(
66
68
  derivedNames: ReadonlySet<string>,
67
69
  computedNames: ReadonlySet<string>,
68
70
  isLayout = false,
69
- ): string {
71
+ /* `linked` / async `computed` names, lowered to `$$readCell(name)` in template exprs. */
72
+ cellReadNames: ReadonlySet<string> = new Set(),
73
+ ): { body: string; flightDecls: string; hasStagedChildren: boolean } {
70
74
  /* Unique temp var names (child render results); runtime block ids are
71
75
  allocated separately at runtime via `$ctx.next++`. */
72
76
  const nextVar = makeVarNamer()
77
+ /* A per-body source-order ordinal for each `<Child/>` render site — the render-path segment the
78
+ child roots under, matching the client `mountChild`'s `childOrdinal` (`generateBuild`) drawn
79
+ in the identical document-order walk, so both compose the same cell scope ids. */
80
+ let childOrdinal = 0
73
81
 
74
82
  /* The enclosing `<select bind:value>`s, innermost last: each carries the JS var holding
75
83
  its bound value and whether it's a `multiple` (array) select, so an `<option>` rendered
@@ -84,7 +92,8 @@ export function generateSSR(
84
92
  withNestedScripts,
85
93
  withShadow,
86
94
  bindRead,
87
- } = lowerContext(stateNames, derivedNames, computedNames)
95
+ bindWrite,
96
+ } = lowerContext(stateNames, derivedNames, computedNames, cellReadNames)
88
97
 
89
98
  /* SSR has no cells, so every plan `Binding` — `reactive` or `plain` — renders as a `plain`
90
99
  shadow (a real JS local / loop var / arrow param, read as the bare identifier). The one
@@ -150,6 +159,28 @@ export function generateSSR(
150
159
  through the generic element path exactly as the client clones it. */
151
160
  const rootNodes = isLayout ? nodes.map(asOutlet) : nodes
152
161
 
162
+ /* ADR-0034: the await blocks whose promise-start hoists to the synchronous render prefix so
163
+ independent flights overlap instead of serializing. Server-only — this rewires only the SSR
164
+ promise SOURCE (a blocking `await $flightN`, a streaming `promise: () => $flightN`); the
165
+ block's markers, `$ctx.next++` id, RESUME wire, and the whole client build stay byte-identical.
166
+ The `flightDecls` are emitted by compileSSR after the lowered script and BEFORE the barrier, so
167
+ a hoisted flight is already in-flight while the barrier awaits any unrelated blocking cell. */
168
+ const flightNameByNode = new Map<Extract<TemplateNode, { kind: 'await' }>, string>()
169
+ for (const flight of hoistableAwaits(rootNodes, cellReadNames)) {
170
+ flightNameByNode.set(flight.node, flight.name)
171
+ }
172
+
173
+ /* ADR-0037 Phase 2: the top-level-spine `<Child/>` renders whose start hoists to the prefix so
174
+ sibling renders overlap instead of serializing behind each other's `await`. The component walk
175
+ below emits each hoisted child's flight decl into `childFlightDecls` (with its childOrdinal and
176
+ lowered props computed at the SAME site as the body's `await`, so the two can't drift) and
177
+ awaits the flight const at the structural position. Each hoisted render starts under
178
+ `$$isolateCellBarrier` so its async cells drain in their own list, not a concurrent sibling's
179
+ (the request-scoped barrier is `splice(0)`-drained). */
180
+ const hoistableChildSet = hoistableChildRenders(rootNodes, cellReadNames)
181
+ const childFlightDecls: string[] = []
182
+ let childFlightCounter = 0
183
+
153
184
  /* A snippet name (any identifier, `$` included) interpolated into a RegExp must have its
154
185
  regex metacharacters escaped, or e.g. a trailing `$` would read as an end-anchor and the
155
186
  call site would never match — leaving an un-awaited Promise stringified as `[object
@@ -184,15 +215,17 @@ export function generateSSR(
184
215
  })
185
216
 
186
217
  /* Snippet names whose body produces an `await`, so the snippet must be an `async function`
187
- and its `{name(...)}` call sites awaited: it inlines a child component / holds an await
188
- block (a structural scan), OR it text-calls another async snippet. The latter is a
189
- dependency between snippets, so resolve it to a fixpoint seed with the structural set,
190
- then keep adding any snippet that calls an already-async one until nothing changes. */
218
+ and its `{name(...)}` call sites awaited: it inlines a child component, holds an await
219
+ block, or emits a `{children()}` slot fill (all `await $props.$children()` in SSR) a
220
+ structural scan OR it text-calls another async snippet. The latter is a dependency
221
+ between snippets, so resolve it to a fixpoint seed with the structural set, then keep
222
+ adding any snippet that calls an already-async one until nothing changes. */
191
223
  const subtreeAwaits = (children: TemplateNode[]): boolean =>
192
224
  children.some(
193
225
  (child) =>
194
226
  child.kind === 'component' ||
195
227
  child.kind === 'await' ||
228
+ (child.kind === 'element' && child.tag === 'slot') ||
196
229
  ('children' in child && subtreeAwaits(child.children)),
197
230
  )
198
231
  const snippetDefs = new Map<string, TemplateNode[]>()
@@ -223,14 +256,20 @@ export function generateSSR(
223
256
  }
224
257
  }
225
258
  }
226
- /* A text-part expression that calls an async snippet, so its value is `await`ed before
227
- `$text`. */
228
- const callsAsyncSnippet = (code: string): boolean => {
229
- // The common no-async-snippet component pays zero regex work per text part.
230
- if (asyncSnippets.size === 0) {
259
+ /* A text-part expression whose value may be a Promise, so `$text` must `await` it:
260
+ either it calls an async snippet declared HERE, or it CALLS a computed-backed
261
+ binding. The latter covers a snippet handed down as a prop and called by its prop
262
+ name (`{item(label)}`) that prop lowers to a computed, so it never appears in this
263
+ component's own `asyncSnippets`, yet the parent's snippet body may be async and the
264
+ call returns a Promise (the `[object Promise]` bug). A computed READ (`{full}`) is not
265
+ a call, so plain interpolation stays sync; only `name(...)` on a computed is awaited. */
266
+ const awaitableCallNames = new Set<string>([...asyncSnippets, ...computedNames])
267
+ const callsAwaitable = (code: string): boolean => {
268
+ // The common component with no async snippet and no computed pays zero regex work.
269
+ if (awaitableCallNames.size === 0) {
231
270
  return false
232
271
  }
233
- for (const name of asyncSnippets) {
272
+ for (const name of awaitableCallNames) {
234
273
  if (callPattern(name).test(code)) {
235
274
  return true
236
275
  }
@@ -258,6 +297,26 @@ export function generateSSR(
258
297
  const openRange = (target: string): string => push(target, RANGE_OPEN)
259
298
  const closeRange = (target: string): string => push(target, RANGE_CLOSE)
260
299
 
300
+ /* Wrap a control-flow branch/row body in the render-path segment the CLIENT pushes for it (the
301
+ `withPathFrom`/`withPath` call the `each`/`when`/`switchBlock` dom runtimes make), so a scope
302
+ created inside — a nested `<Child/>`, always an inline `await` in SSR — composes the SAME
303
+ serialization-stable id on both sides (the warm-seed key). Emitted ONLY when the subtree can
304
+ create such a scope: a component render is the sole scope-creating construct in a branch/row
305
+ and always lowers to an inline `await`, so `subtreeAwaits` is the gate. A purely-synchronous
306
+ branch creates no scope, so its segment is inert — skipping the wrap keeps the static render
307
+ synchronous (no per-row closure/await, matching today's output byte-for-byte). `segment` is a
308
+ JS expression (a literal branch key, or the each row's key/index); `$$withPath` escapes it,
309
+ exactly like the client. The row/branch RANGE markers stay OUTSIDE this call (they create no
310
+ scope), so only the content composes the path — mirroring the client, where the markers are
311
+ built outside `withPathFrom`. */
312
+ const withPathBranch = (segment: string, children: TemplateNode[], target: string): string => {
313
+ const content = branchContent(children, target)
314
+ if (!subtreeAwaits(children)) {
315
+ return content
316
+ }
317
+ return `await $$withPath(${segment}, async () => {\n${content}});\n`
318
+ }
319
+
261
320
  /* In a skeleton, a control-flow block or slot is positioned by an `<!--a-->` anchor
262
321
  (cloned into the located parent), so it can sit anywhere among static siblings.
263
322
  Emitted both sides in document order — the client's anchor scan lines up with it.
@@ -285,7 +344,7 @@ export function generateSSR(
285
344
  Plain expressions stay sync, so a component with only interpolation keeps a
286
345
  sync render. */
287
346
  const lowered = lowerExpression(part.code)
288
- const value = callsAsyncSnippet(part.code)
347
+ const value = callsAwaitable(part.code)
289
348
  ? `$text(await (${lowered}))`
290
349
  : `$text(${lowered})`
291
350
  return markText.get(node)
@@ -295,31 +354,98 @@ export function generateSSR(
295
354
  .join('')
296
355
  }
297
356
  if (node.kind === 'if') {
298
- /* `case` children are the `elseif`/`else` branches in source order; the rest are
299
- the `then` content. Each `elseif` becomes an `else if`, the match-less `else`
300
- the trailing `else`. */
357
+ /* `case` children are the `elseif`/`else` branches in source order; the rest are the
358
+ `then` content. The whole `if`/`elseif`/`else` chain desugars to a run of `if` /
359
+ `else if` clauses. A branch whose condition is a bare async subject contributes TWO
360
+ clauses in order — an empty `$$cellPending` guard (renders nothing, and stops the
361
+ chain: a still-loading branch holds, so nothing later renders on its unknown value)
362
+ then the `$$readCell` truthy test — so sync and async branches interleave and each
363
+ resolves at render time, mirroring the client `switchBlock` cond-chain. */
301
364
  const plan = ifPlan(node)
302
- let code = `if (${lowerExpression(node.condition)}) {\n${branchContent(plan.thenChildren, target)}}`
303
- for (const branch of plan.branches) {
304
- code +=
305
- branch.condition !== undefined
306
- ? ` else if (${lowerExpression(branch.condition)}) {\n${branchContent(branch.children, target)}}`
307
- : ` else {\n${branchContent(branch.children, target)}}`
365
+ /* The client build lowers a simple `if`/`else` through `when` (branch keys `'then'` /
366
+ `'else'`) and an `if` with any `elseif` through `switchBlock` over `[then, ...branches]`
367
+ (branch key = the case's array index). Mirror whichever the client picks so a nested
368
+ `<Child/>`'s scope id composes identically. */
369
+ const clauses: string[] = []
370
+ const conditional = (
371
+ condition: string,
372
+ asyncSubject: boolean | undefined,
373
+ children: TemplateNode[],
374
+ segment: string,
375
+ ): void => {
376
+ if (asyncSubject === true) {
377
+ const cell = condition.trim()
378
+ clauses.push(`($$cellPending(${cell})) {\n}`)
379
+ clauses.push(
380
+ `($$readCell(${cell})) {\n${withPathBranch(segment, children, target)}}`,
381
+ )
382
+ } else {
383
+ clauses.push(
384
+ `(${lowerExpression(condition)}) {\n${withPathBranch(segment, children, target)}}`,
385
+ )
386
+ }
387
+ }
388
+ /* `then` is the `when` `'then'` branch, or the `switchBlock` case at index 0. */
389
+ conditional(
390
+ node.condition,
391
+ node.asyncSubject,
392
+ plan.thenChildren,
393
+ JSON.stringify(plan.hasElseif ? '0' : 'then'),
394
+ )
395
+ for (let index = 0; index < plan.branches.length; index += 1) {
396
+ const branch = plan.branches[index] as Extract<TemplateNode, { kind: 'case' }>
397
+ if (branch.condition !== undefined) {
398
+ /* An `elseif` is the `switchBlock` case at `[then, ...branches]` index `index + 1`
399
+ (a chain with any condition always lowers to `switchBlock`). */
400
+ conditional(
401
+ branch.condition,
402
+ branch.asyncSubject,
403
+ branch.children,
404
+ JSON.stringify(String(index + 1)),
405
+ )
406
+ }
407
+ }
408
+ let code = clauses
409
+ .map((clause, index) => `${index === 0 ? 'if' : ' else if'} ${clause}`)
410
+ .join('')
411
+ if (plan.elseBranch !== undefined) {
412
+ /* `else` is the `when` `'else'` branch, or (for a cond-chain) the `switchBlock`
413
+ default at its own `[then, ...branches]` index. */
414
+ const elseSegment = plan.hasElseif
415
+ ? String(plan.branches.indexOf(plan.elseBranch) + 1)
416
+ : 'else'
417
+ code += ` else {\n${withPathBranch(JSON.stringify(elseSegment), plan.elseBranch.children, target)}}`
308
418
  }
309
419
  return `${anchor}${openRange(target)}${code}\n${closeRange(target)}`
310
420
  }
311
421
  if (node.kind === 'switch') {
312
422
  const plan = switchPlan(node)
313
- let code = `{ const $s = (${lowerExpression(node.subject)});\n`
423
+ /* A bare async subject: read the peek, but only match once the cell has settled —
424
+ a pending subject renders no case (matching the client's `switchBlock`). */
425
+ const subjectExpr =
426
+ node.asyncSubject === true
427
+ ? `$$readCell(${node.subject.trim()})`
428
+ : `(${lowerExpression(node.subject)})`
429
+ let code = `{ const $s = ${subjectExpr};\n`
430
+ if (node.asyncSubject === true) {
431
+ code += `if (!$$cellPending(${node.subject.trim()})) {\n`
432
+ }
314
433
  let started = false
315
- for (const branch of plan.cases) {
434
+ /* The client `switchBlock` keys each branch by its index in `plan.cases` (source order,
435
+ the default at its own position); mirror that index so a nested `<Child/>` matches. */
436
+ for (let index = 0; index < plan.cases.length; index += 1) {
437
+ const branch = plan.cases[index] as Extract<TemplateNode, { kind: 'case' }>
316
438
  if (branch.match !== undefined) {
317
- code += `${started ? 'else ' : ''}if ($s === (${lowerExpression(branch.match)})) {\n${branchContent(branch.children, target)}}\n`
439
+ code += `${started ? 'else ' : ''}if ($s === (${lowerExpression(branch.match)})) {\n${withPathBranch(JSON.stringify(String(index)), branch.children, target)}}\n`
318
440
  started = true
319
441
  }
320
442
  }
321
443
  if (plan.fallback !== undefined) {
322
- code += `${started ? 'else ' : ''}{\n${branchContent(plan.fallback.children, target)}}\n`
444
+ const fallbackSegment = String(plan.cases.indexOf(plan.fallback))
445
+ code += `${started ? 'else ' : ''}{\n${withPathBranch(JSON.stringify(fallbackSegment), plan.fallback.children, target)}}\n`
446
+ }
447
+ if (node.asyncSubject === true) {
448
+ code += `}\n`
323
449
  }
324
450
  return `${anchor}${openRange(target)}${code}}\n${closeRange(target)}`
325
451
  }
@@ -361,26 +487,45 @@ export function generateSSR(
361
487
  if (plan.async) {
362
488
  return anchor
363
489
  }
490
+ /* The client `each` pushes a render-path segment per row (keyed → the `by` key evaluated
491
+ on the raw item, exactly like its `keyOf`; keyless → the row position), so a nested
492
+ `<Child/>`'s scope id composes identically. Mirror it only when the row can create a
493
+ scope (`subtreeAwaits` — it holds a component); a static row needs no segment and stays
494
+ synchronous. A keyless row that needs its position reuses a bound `index`, else a
495
+ synthesized loop index — the same 0-based `entries()` position the client's row cell
496
+ carries, so the segments agree. */
497
+ const rowSegments = subtreeAwaits(plan.children)
498
+ const keylessIndex =
499
+ plan.key === undefined && plan.index === undefined && rowSegments
500
+ ? nextVar('$i')
501
+ : undefined
502
+ const indexBinding = plan.index ?? keylessIndex
364
503
  /* The row item (and index) are real `for`-loop locals, so the body must lower
365
504
  references to them as the bare identifier — `withBindings` registers the plan's
366
505
  row bindings (under `plain`, SSR's only kind) so a row binding that shadows a
367
506
  same-named component signal reads the loop value, not the (whole-list) signal it
368
507
  shadows. The names come straight from `plan.bindings` (the single source the
369
508
  client also reads); the items expression stays outside the shadow. */
370
- const rowBody = withBindings(
371
- withShadow,
372
- plan.bindings,
373
- ssrBindingKind,
374
- () =>
375
- `${openRange(target)}${branchContent(plan.children, target)}${closeRange(target)}`,
376
- )
509
+ const rowBody = withBindings(withShadow, plan.bindings, ssrBindingKind, () => {
510
+ /* The row's render-path segment, lowered inside the row's plain shadow so a keyed
511
+ `by` reads the raw loop item exactly like the client `keyOf` (a keyless row uses
512
+ its position). Unused when `rowSegments` is false — `withPathBranch` then skips it. */
513
+ const segment =
514
+ plan.key === undefined
515
+ ? (indexBinding ?? '0')
516
+ : `(${lowerExpression(plan.key)})`
517
+ return `${openRange(target)}${withPathBranch(segment, plan.children, target)}${closeRange(target)}`
518
+ })
377
519
  /* `index="i"` binds the row position. SSR reads it as a plain number from
378
520
  `entries()` over a materialized array; the client reads the same number from a
379
521
  cell, so first paint is congruent. No index → a plain `for…of` over the items. */
522
+ /* An undefined source renders no rows, not a throw — a `{#for x in promise}`
523
+ whose lifted source peeks undefined while pending (ADR-0032 D3). Mirrors the
524
+ client `each`'s undefined-as-empty guard. */
380
525
  const header =
381
- plan.index === undefined
382
- ? `for (const ${plan.as} of (${lowerExpression(plan.items)}))`
383
- : `for (const [${plan.index}, ${plan.as}] of [...(${lowerExpression(plan.items)})].entries())`
526
+ indexBinding === undefined
527
+ ? `for (const ${plan.as} of ((${lowerExpression(plan.items)}) ?? []))`
528
+ : `for (const [${indexBinding}, ${plan.as}] of [...((${lowerExpression(plan.items)}) ?? [])].entries())`
384
529
  return `${anchor}${header} {\n${rowBody}}\n`
385
530
  }
386
531
  if (node.kind === 'await') {
@@ -396,7 +541,7 @@ export function generateSSR(
396
541
  /* Server-render the child via its `render` and inline the HTML inside the same
397
542
  `[ … ]` marker range the client mounts into (`mountRange`) — no wrapper element,
398
543
  so SSR and client agree and the child's root lays out as a direct child. Props
399
- pass as thunks; slot content passes as a string-returning `$children` the child
544
+ pass as thunks; slot content passes as a string-returning `children` the child
400
545
  invokes from its <slot>. */
401
546
  /* Slot content is a fresh build context — the child's `<slot>` mounts it via
402
547
  `mountSlot`, not the parent skeleton clone, and the client builds it through
@@ -404,33 +549,72 @@ export function generateSSR(
404
549
  records it reset, so its children emit no enclosing-skeleton anchors the client
405
550
  slot builder would lack. */
406
551
  const slotCode = generateInto(node.children, '$slot')
407
- /* `$children` is an ASYNC builder the child `await`s at its `<slot>` position
408
- (`generateSlot`), NOT a pre-resolved string. Pre-resolving here would run the
409
- slot's `$ctx.next++` block ids BEFORE the child render's own, but the client
410
- builds slot content lazily at the `<slot>` site so a child with an await/try
411
- before its `<slot>` would allocate ids in the opposite order and desync hydration.
412
- Keeping the slot lazy makes the slot's ids draw at the `<slot>` site on both
413
- sides. The builder shares the enclosing render's `$ctx`/`$awaits`/`$resume` (a
414
- closure), so nested awaits register and number correctly during the child render.
415
- A child with a `<slot>` is therefore always an async render. */
552
+ /* Slot content rides the `children` prop key as a `Snippet`: a zero-arg callable
553
+ returning an ASYNC builder the child `await`s at its `{children()}` position
554
+ (`generateSlot`), whose resolved value is a `$snip`-branded string so it renders
555
+ through the same `$text` snippet-marker path as any `{snippet(args)}` and unifies
556
+ with a passed `children={snippet}`. It is NOT pre-resolved: pre-resolving here would
557
+ run the slot's `$ctx.next++` block ids BEFORE the child render's own, but the client
558
+ builds slot content lazily at the `{children()}` site so a child with an await/try
559
+ before its slot would allocate ids in the opposite order and desync hydration.
560
+ Keeping the slot lazy draws its ids at the `{children()}` site on both sides. The
561
+ builder shares the enclosing render's `$ctx`/`$awaits`/`$resume` (a closure), so
562
+ nested awaits register and number correctly during the child render. A child with a
563
+ slot fill is therefore always an async render. */
416
564
  const slotPart =
417
565
  slotCode.trim() === ''
418
566
  ? undefined
419
- : `"$children": async () => { const $slot = []; ${slotCode}return $slot.join(''); }`
567
+ : `"children": () => (async () => { const $slot = []; ${slotCode}return $snip($slot.join('')); })`
420
568
  /* The same last-wins layering the client build emits (`composeProps`), so SSR
421
569
  and hydration read the same prop bag. */
422
- const propsExpr = composeProps(node.props, lowerExpression, slotPart)
570
+ const propsExpr = composeProps(
571
+ node.props,
572
+ lowerExpression,
573
+ slotPart,
574
+ bindRead,
575
+ bindWrite,
576
+ )
423
577
  /* Render the child (awaited — render is async) sharing this render's `$ctx`,
424
578
  so its `await`/`try` block ids draw from the same depth-first counter,
425
579
  unique across page + children, and the streamed fragments resolve into the
426
580
  right boundaries. MERGE its streaming awaits into `$awaits` and its inline
427
581
  blocking values into `$resume`. ($awaits/$resume are captured from the
428
582
  enclosing render body, including from branch closures.) */
583
+ /* The tag lowers like any reference (see generateBuild): a static import is left bare, a
584
+ reactive/loop/await binding derefs — SSR registers such a binding as `plain`, so it
585
+ reads the bare local holding the resolved component, keeping SSR and client congruent.
586
+ Root the child's render-path at this mount site's source-order ordinal — the same
587
+ segment the client's `mountChild` pushes — so the child's cells and block ids (ADR-0037)
588
+ get an id matching the client's. `$$withPath` sets the path across the child's awaits. */
589
+ const ordinal = childOrdinal++
590
+ const renderExpr = `$$withPath(${ordinal}, () => ${lowerExpression(node.name)}.render(${propsExpr}, $ctx))`
591
+ if (hoistableChildSet.has(node)) {
592
+ /* Hoistable (ADR-0037/0039): start the render as an isolated flight in the prefix, and
593
+ RESERVE its output position rather than awaiting inline. After the walk,
594
+ `finalizeStreamedChildren` fills the slot — inline `<!--[-->html<!--]-->` if the
595
+ flight already settled (byte-identical to the old inline await), or a streaming
596
+ `abide:await:CHILDPATH` boundary + html-only await if it is still pending. The
597
+ boundary id is the child's render-path (`$$renderPath(ordinal)`), congruent with the
598
+ client's `mountStreamedChild`. */
599
+ const flightName = `$cf${childFlightCounter++}`
600
+ childFlightDecls.push(
601
+ `const ${flightName} = $$flight(() => $$isolateCellBarrier(() => ${renderExpr}));`,
602
+ )
603
+ const slotVar = nextVar('$slot')
604
+ return (
605
+ anchor +
606
+ `${target}.push('');\n` +
607
+ `const ${slotVar} = ${target}.length - 1;\n` +
608
+ `$childSlots.push({ slot: ${slotVar}, out: ${target}, id: $$renderPath(${ordinal}), flight: ${flightName} });\n`
609
+ )
610
+ }
611
+ /* Non-hoistable child (off the top-level spine): await inline at its structural position,
612
+ sequential, as before — never streams. */
429
613
  const result = nextVar('$child')
430
614
  return (
431
615
  anchor +
432
616
  push(target, RANGE_OPEN) +
433
- `const ${result} = await ${node.name}.render(${propsExpr}, $ctx);\n` +
617
+ `const ${result} = await ${renderExpr};\n` +
434
618
  `${target}.push(${result}.html);\n` +
435
619
  `for (const $a of ${result}.awaits) { $awaits.push($a); }\n` +
436
620
  `Object.assign($resume, ${result}.resume);\n` +
@@ -572,26 +756,23 @@ export function generateSSR(
572
756
  return code
573
757
  }
574
758
 
575
- /* A `<slot>` outlet: emit the parent-provided content (`$children`), falling back to the
576
- slot's own children when none was supplied. Inside a skeleton the slot is positioned
577
- by an `<!--a-->` anchor and its content bounded by a `[ ]` range (matching the
578
- client's `mountSlot`), so it can sit among static siblings. The fallback is a fresh,
579
- non-skeleton build context the client builds it via `mountSlot`/`fillBefore`, not the
580
- skeleton clone so its reactive text takes no anchor (`skeletonContext` records the
581
- fallback children reset). */
759
+ /* A component `{children()}` fill point: render the `children` prop (a `Snippet`) as a
760
+ snippet text part. `children()()` reads the destructured `children` computed and calls
761
+ it a Promise of a `$snip`-branded string; `await` it and push through `$text`, which
762
+ wraps the branded string in `<!--abide:snippet-->` markers the same range the client's
763
+ appendText→appendSnippet emits and claims, so hydration stays congruent. Inside a skeleton
764
+ the slot is positioned by an `<!--a-->` anchor and bounded by a `[ … ]` range (matching the
765
+ client's `mountSlot`); outside one it emits just the snippet markers (matching the client's
766
+ direct `appendText`). The `await` makes a component with a slot an async render (its caller
767
+ already `await`s `render()`). A fallback is now an authored `{#if children}…{:else}…{/if}`,
768
+ so the slot node carries no children. */
582
769
  function generateSlot(
583
770
  node: Extract<TemplateNode, { kind: 'element' }>,
584
771
  target: string,
585
772
  anchor: string,
586
773
  ): string {
587
774
  const wrap = inSkeleton.get(node)
588
- /* `$children` is an async builder the parent passes lazily; `await` it here so the
589
- slot content's block ids allocate AT the slot position — the same order the
590
- client builds slot content — keeping hydration congruent. The `await` makes a
591
- component with a slot an async render (its caller already `await`s `render()`).
592
- A fallback is now an authored `{#if children}…{:else}…{/if}`, so the slot node
593
- carries no children. */
594
- const body = `if ($props && $props.$children) { ${target}.push(await $props.$children()); }\n`
775
+ const body = `${target}.push($text(await (${lowerExpression('children()')})));\n`
595
776
  if (!wrap) {
596
777
  return body
597
778
  }
@@ -632,10 +813,13 @@ export function generateSSR(
632
813
  `withBindings` shadow models, so a reference to the binding reads the plain
633
814
  local rather than the (unresolved) component signal it shadows. */
634
815
  const resolved = nextVar('$av')
635
- let code = `const ${id} = $ctx.next++;\n`
816
+ /* ADR-0034: a hoisted block awaits the prefix flight const (already in-flight); a
817
+ non-hoisted block evaluates + awaits its promise inline as before. */
818
+ const hoistedPromise = flightNameByNode.get(node)
819
+ let code = `const ${id} = $$blockId($ctx);\n`
636
820
  code += `${target}.push("<!--abide:await:" + ${id} + "-->");\n`
637
821
  code += `try {\n`
638
- code += `const ${resolved} = await (${lowerExpression(node.promise)});\n`
822
+ code += `const ${resolved} = await (${hoistedPromise ?? lowerExpression(node.promise)});\n`
639
823
  code += `{\n`
640
824
  code += `const ${plan.resolvedAs} = ${resolved};\n`
641
825
  code += withBindings(withShadow, plan.resolvedBindings, ssrBindingKind, () =>
@@ -681,7 +865,7 @@ export function generateSSR(
681
865
  ): string {
682
866
  const plan = awaitPlan(node)
683
867
  const id = nextVar('$aid')
684
- let code = `const ${id} = $ctx.next++;\n`
868
+ let code = `const ${id} = $$blockId($ctx);\n`
685
869
  code += `${target}.push("<!--abide:await:" + ${id} + "-->");\n`
686
870
  code += branchContent(plan.pending, target)
687
871
  code += `${target}.push("<!--/abide:await:" + ${id} + "-->");\n`
@@ -701,26 +885,34 @@ export function generateSSR(
701
885
  const catchProp = plan.surfaceRejection
702
886
  ? ''
703
887
  : `catch: ${settled(plan.catchAs, plan.catchBindings, plan.catchChildren)} `
888
+ /* ADR-0034: a hoisted streaming block's thunk returns the prefix flight const (started
889
+ in the prefix, so it overlaps a preceding blocking await); non-hoisted evaluates lazily
890
+ at drain time as before. */
891
+ const hoistedPromise = flightNameByNode.get(node)
704
892
  code +=
705
893
  `$awaits.push({ id: ${id}, ` +
706
- `promise: () => (${lowerExpression(node.promise)}), ` +
894
+ `promise: () => (${hoistedPromise ?? lowerExpression(node.promise)}), ` +
707
895
  `then: ${settled(plan.resolvedAs, plan.resolvedBindings, plan.resolvedChildren)}, ` +
708
896
  `${catchProp}});\n`
709
897
  return code
710
898
  }
711
899
 
712
- /* A sync error boundary: push the guarded markup (++ finally) inside a real
713
- try/catch; on a throw, truncate the output back to the boundary start and push
714
- the catch markup (++ finally) instead — so even mid-stream a render throw
715
- becomes catch markup, not a broken response. No catch re-throws (propagates to
716
- an enclosing boundary / the 500 / the stream). Boundary comments let hydration
717
- discard the server content if the client adoption fails. */
900
+ /* A reactive error boundary: push the guarded markup (++ finally) inside a real
901
+ try/catch; on a throw, truncate the output back to the range start and push the
902
+ catch markup (++ finally) instead — so even mid-stream a render throw becomes catch
903
+ markup, not a broken response. No catch re-throws (propagates to an enclosing boundary
904
+ / the 500 / the stream). The `abide:try:N` boundary comments let hydration discard the
905
+ server content if the client adoption fails; the inner `[ … ]` range markers are the
906
+ live range the render-many client (`tryBlock`) tracks — emitted here so a FRESH client
907
+ mount (which brackets its branch) and the SSR markup stay congruent. */
718
908
  function generateTry(node: Extract<TemplateNode, { kind: 'try' }>, target: string): string {
719
909
  const plan = tryPlan(node)
720
910
  const id = nextVar('$tid')
721
911
  const mark = nextVar('$trim')
722
- let code = `const ${id} = $ctx.next++;\n`
912
+ let code = `const ${id} = $$blockId($ctx);\n`
723
913
  code += `${target}.push("<!--abide:try:" + ${id} + "-->");\n`
914
+ code += openRange(target)
915
+ /* Truncate back to just after the range-open marker on a catch, so `[` survives. */
724
916
  code += `const ${mark} = ${target}.length;\n`
725
917
  code += `try {\n`
726
918
  code += branchContent(plan.guarded, target)
@@ -735,9 +927,28 @@ export function generateSSR(
735
927
  code += `throw ${plan.catchAs};\n`
736
928
  }
737
929
  code += `}\n`
930
+ code += closeRange(target)
738
931
  code += `${target}.push("<!--/abide:try:" + ${id} + "-->");\n`
739
932
  return code
740
933
  }
741
934
 
742
- return generateInto(rootNodes, '$out')
935
+ /* The body walk emits `await $flightN` / `promise: () => $flightN` for hoisted nodes; the
936
+ matching prefix declarations (lowered at component scope — hoisted promises reference only
937
+ component-scope names, never a branch shadow, so this lowering matches the body's) are
938
+ returned for compileSSR to place after the lowered script and before the barrier. */
939
+ const body = generateInto(rootNodes, '$out')
940
+ let flightDecls = ''
941
+ for (const [node, name] of flightNameByNode) {
942
+ flightDecls += `const ${name} = $$flight(() => (${lowerExpression(node.promise)}));\n`
943
+ }
944
+ /* Child-render flights emitted during the body walk above (ADR-0037 Phase 2) — appended after
945
+ the await flights; both are independent prefix promise-starts, so order between them is free. */
946
+ for (const decl of childFlightDecls) {
947
+ flightDecls += `${decl}\n`
948
+ }
949
+ /* `hasStagedChildren` tells compileSSR to declare `$childSlots` and emit the post-walk
950
+ `$$finalizeStreamedChildren` await (ADR-0039); a component with no hoistable child stays
951
+ synchronous. Each hoistable child pushed one `childFlightDecls` entry, so its length is the
952
+ staged-child count. */
953
+ return { body, flightDecls, hasStagedChildren: childFlightDecls.length > 0 }
743
954
  }