@abide/abide 0.49.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/AGENTS.md +70 -52
  2. package/CHANGELOG.md +177 -0
  3. package/README.md +12 -7
  4. package/package.json +14 -5
  5. package/src/abideLsp.ts +5 -6
  6. package/src/abideResolverPlugin.ts +197 -169
  7. package/src/build.ts +79 -56
  8. package/src/buildDisconnected.ts +0 -2
  9. package/src/checkAbide.ts +12 -2
  10. package/src/devEntry.ts +113 -28
  11. package/src/discoveryEntry.ts +3 -2
  12. package/src/lib/bundle/disconnected.abide +69 -73
  13. package/src/lib/bundle/infoPlist.ts +13 -7
  14. package/src/lib/bundle/spawnEmbeddedServer.ts +13 -4
  15. package/src/lib/bundle/waitForServer.ts +6 -1
  16. package/src/lib/mcp/mcpTools.ts +15 -6
  17. package/src/lib/server/error.ts +6 -6
  18. package/src/lib/server/json.ts +17 -3
  19. package/src/lib/server/rpc/defineRpc.ts +58 -24
  20. package/src/lib/server/rpc/parseArgs.ts +98 -13
  21. package/src/lib/server/rpc/resolveRpcJsonSchema.ts +28 -0
  22. package/src/lib/server/rpc/types/RpcHelper.ts +75 -105
  23. package/src/lib/server/rpc/types/RpcRegistryEntry.ts +24 -9
  24. package/src/lib/server/rpc/validationError.ts +1 -1
  25. package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +0 -15
  26. package/src/lib/server/runtime/buildCacheSnapshot.ts +10 -9
  27. package/src/lib/server/runtime/buildInspectorSurface.ts +6 -4
  28. package/src/lib/server/runtime/buildOpenApiSpec.ts +55 -11
  29. package/src/lib/server/runtime/buildPreloadManifest.ts +12 -10
  30. package/src/lib/server/runtime/createAppAssetServer.ts +18 -15
  31. package/src/lib/server/runtime/createAppRouteResolver.ts +145 -0
  32. package/src/lib/server/runtime/createPlumbingRouter.ts +212 -0
  33. package/src/lib/server/runtime/createRouteDispatcher.ts +4 -10
  34. package/src/lib/server/runtime/createServer.ts +120 -324
  35. package/src/lib/server/runtime/createUiPageRenderer.ts +137 -26
  36. package/src/lib/server/runtime/devClientFingerprint.ts +19 -34
  37. package/src/lib/server/runtime/installAmbientScopeStore.ts +30 -9
  38. package/src/lib/server/runtime/logExposedSurfaces.ts +8 -7
  39. package/src/lib/server/runtime/pathStore.ts +15 -0
  40. package/src/lib/server/runtime/registryManifests.ts +1 -1
  41. package/src/lib/server/runtime/renderCellBarrierStore.ts +15 -0
  42. package/src/lib/server/runtime/runWithRequestScope.ts +3 -0
  43. package/src/lib/server/runtime/serializeCacheSnapshot.ts +6 -4
  44. package/src/lib/server/runtime/snapshotEntryFromCache.ts +14 -14
  45. package/src/lib/server/runtime/streamCacheResolutions.ts +14 -7
  46. package/src/lib/server/runtime/streamFromIterator.ts +30 -2
  47. package/src/lib/server/runtime/textResponse.ts +23 -0
  48. package/src/lib/server/runtime/types/DevReloadStamp.ts +6 -10
  49. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
  50. package/src/lib/server/runtime/types/RequestStore.ts +27 -0
  51. package/src/lib/server/runtime/warnUnguardedMcp.ts +15 -5
  52. package/src/lib/server/sockets/createSocketDispatcher.ts +9 -11
  53. package/src/lib/server/sockets/defineSocket.ts +5 -4
  54. package/src/lib/server/sse.ts +5 -1
  55. package/src/lib/shared/ASYNC_CELL.ts +5 -0
  56. package/src/lib/shared/DEV_REBUILD_PATH.ts +6 -0
  57. package/src/lib/shared/HTTP_METHODS.ts +6 -0
  58. package/src/lib/shared/HttpError.ts +1 -5
  59. package/src/lib/shared/MCP_PATH.ts +6 -0
  60. package/src/lib/shared/PROXIED_SERVER_SUBDIRS.ts +15 -0
  61. package/src/lib/shared/REF_JSON_TAGS.ts +13 -1
  62. package/src/lib/shared/RPC_SHIM_GLOBALS.ts +9 -0
  63. package/src/lib/shared/activeCacheStore.ts +1 -0
  64. package/src/lib/shared/activePage.ts +1 -0
  65. package/src/lib/shared/buildArtifact.ts +4 -1
  66. package/src/lib/shared/buildSocketOverChannel.ts +4 -3
  67. package/src/lib/shared/bundleGraphFromMetafile.ts +68 -0
  68. package/src/lib/shared/cache.ts +156 -131
  69. package/src/lib/shared/changeAffectsClient.ts +12 -7
  70. package/src/lib/shared/createCacheStore.ts +31 -8
  71. package/src/lib/shared/createLifecycleChannel.ts +7 -1
  72. package/src/lib/shared/createReachable.ts +47 -78
  73. package/src/lib/shared/createRemoteFunction.ts +52 -30
  74. package/src/lib/shared/createRpcServerProgram.ts +820 -0
  75. package/src/lib/shared/decodeRefJson.ts +4 -4
  76. package/src/lib/shared/decodeResponse.ts +2 -2
  77. package/src/lib/shared/decodeWireBody.ts +35 -0
  78. package/src/lib/shared/detectRpcMethod.ts +8 -1
  79. package/src/lib/shared/done.ts +8 -2
  80. package/src/lib/shared/encodeRefJson.ts +4 -4
  81. package/src/lib/shared/encodeWireBody.ts +18 -0
  82. package/src/lib/{server/rpc → shared}/fieldErrorsFromIssues.ts +5 -1
  83. package/src/lib/shared/generateDeclarations.ts +72 -0
  84. package/src/lib/shared/hasSeedableRequest.ts +25 -0
  85. package/src/lib/shared/hydrationWindow.ts +53 -0
  86. package/src/lib/shared/isAsyncCell.ts +11 -0
  87. package/src/lib/shared/isAsyncIterable.ts +8 -0
  88. package/src/lib/shared/isSubscribable.ts +3 -3
  89. package/src/lib/shared/isThenable.ts +9 -0
  90. package/src/lib/shared/jsonSchemaForType.ts +258 -0
  91. package/src/lib/shared/lenientDecode.ts +15 -0
  92. package/src/lib/shared/loadProjectTsConfig.ts +28 -0
  93. package/src/lib/shared/markFrameworkSourcesIgnored.ts +1 -1
  94. package/src/lib/shared/matchRoute.ts +4 -14
  95. package/src/lib/shared/peek.ts +14 -0
  96. package/src/lib/shared/pending.ts +9 -6
  97. package/src/lib/shared/pendingAsyncCellsSlot.ts +13 -0
  98. package/src/lib/shared/prepareRpcModule.ts +91 -93
  99. package/src/lib/shared/prepareSocketModule.ts +3 -2
  100. package/src/lib/shared/probeRegistries.ts +5 -18
  101. package/src/lib/shared/reachable.ts +7 -10
  102. package/src/lib/shared/refresh.ts +12 -2
  103. package/src/lib/shared/refreshing.ts +8 -2
  104. package/src/lib/shared/resolvedCellsSlot.ts +13 -0
  105. package/src/lib/shared/reviveWireField.ts +49 -0
  106. package/src/lib/shared/reviveWireOutput.ts +36 -0
  107. package/src/lib/shared/rpcServerForRoot.ts +25 -0
  108. package/src/lib/shared/scanPages.ts +25 -0
  109. package/src/lib/shared/snapshotShippable.ts +8 -7
  110. package/src/lib/shared/streamedCellsSlot.ts +14 -0
  111. package/src/lib/shared/subscribableFromResponse.ts +4 -4
  112. package/src/lib/shared/subscribableProbes.ts +1 -1
  113. package/src/lib/shared/tailProbeSlot.ts +1 -1
  114. package/src/lib/shared/types/AsyncComputed.ts +20 -0
  115. package/src/lib/shared/types/AsyncState.ts +13 -0
  116. package/src/lib/shared/types/CacheEntry.ts +7 -7
  117. package/src/lib/shared/types/CacheOptions.ts +23 -37
  118. package/src/lib/shared/types/CachePolicy.ts +25 -0
  119. package/src/lib/shared/types/CacheSnapshotEntry.ts +6 -5
  120. package/src/lib/shared/types/ErrorJsonSchemas.ts +8 -0
  121. package/src/lib/shared/types/HttpMethod.ts +3 -1
  122. package/src/lib/shared/types/InputCoercion.ts +17 -0
  123. package/src/lib/shared/types/{Subscribable.ts → NamedAsyncIterable.ts} +1 -1
  124. package/src/lib/shared/types/OutputWirePlan.ts +17 -0
  125. package/src/lib/shared/types/PagesScan.ts +7 -0
  126. package/src/lib/shared/types/PendingAsyncCells.ts +10 -0
  127. package/src/lib/shared/types/RawRemoteFunction.ts +6 -0
  128. package/src/lib/shared/types/RemoteCallable.ts +8 -7
  129. package/src/lib/shared/types/RemoteFunction.ts +16 -15
  130. package/src/lib/shared/types/ResolvedCells.ts +11 -0
  131. package/src/lib/shared/types/ReturnBody.ts +15 -0
  132. package/src/lib/shared/types/RpcBuildStamps.ts +23 -0
  133. package/src/lib/shared/types/RpcErrorGuard.ts +3 -8
  134. package/src/lib/shared/types/Socket.ts +4 -4
  135. package/src/lib/shared/types/SsrPayload.ts +5 -1
  136. package/src/lib/shared/types/StreamPolicy.ts +11 -0
  137. package/src/lib/shared/types/StreamedCells.ts +19 -0
  138. package/src/lib/shared/types/StreamedResolution.ts +24 -6
  139. package/src/lib/shared/types/TailHooks.ts +1 -1
  140. package/src/lib/shared/types/WireKind.ts +11 -0
  141. package/src/lib/shared/validationHttpError.ts +33 -0
  142. package/src/lib/shared/warmSeedKey.ts +16 -0
  143. package/src/lib/shared/wireJsonReplacer.ts +30 -0
  144. package/src/lib/shared/writeRpcDts.ts +11 -1
  145. package/src/lib/shared/writeTestSocketsDts.ts +1 -1
  146. package/src/lib/test/createTestApp.ts +19 -1
  147. package/src/lib/ui/README.md +1 -1
  148. package/src/lib/ui/activePendingCells.ts +14 -0
  149. package/src/lib/ui/compile/BLOCK_KEYWORDS.ts +21 -0
  150. package/src/lib/ui/compile/SSR_ESCAPE.ts +4 -1
  151. package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +35 -8
  152. package/src/lib/ui/compile/abideUiPlugin.ts +19 -2
  153. package/src/lib/ui/compile/analyzeComponent.ts +89 -6
  154. package/src/lib/ui/compile/assignmentTargetNames.ts +54 -0
  155. package/src/lib/ui/compile/asyncInterpolationFields.ts +152 -0
  156. package/src/lib/ui/compile/asyncValuePositionError.ts +21 -0
  157. package/src/lib/ui/compile/asyncValuePositionInterpolations.ts +64 -0
  158. package/src/lib/ui/compile/attrLiftPosition.ts +18 -0
  159. package/src/lib/ui/compile/cachedSourceFile.ts +40 -0
  160. package/src/lib/ui/compile/catchBinding.ts +7 -5
  161. package/src/lib/ui/compile/classifyInterpolationType.ts +46 -0
  162. package/src/lib/ui/compile/collectAbideDiagnostics.ts +96 -0
  163. package/src/lib/ui/compile/compileComponent.ts +19 -3
  164. package/src/lib/ui/compile/compileModule.ts +27 -58
  165. package/src/lib/ui/compile/compileSSR.ts +51 -12
  166. package/src/lib/ui/compile/compileShadow.ts +145 -18
  167. package/src/lib/ui/compile/composeProps.ts +12 -2
  168. package/src/lib/ui/compile/createShadowLanguageService.ts +116 -68
  169. package/src/lib/ui/compile/createShadowProgram.ts +35 -6
  170. package/src/lib/ui/compile/declaredNames.ts +36 -0
  171. package/src/lib/ui/compile/desugarSignals.ts +418 -36
  172. package/src/lib/ui/compile/expressionIsPrefixEvaluable.ts +19 -0
  173. package/src/lib/ui/compile/generateBuild.ts +52 -9
  174. package/src/lib/ui/compile/generateSSR.ts +242 -43
  175. package/src/lib/ui/compile/hoistableAwaits.ts +193 -0
  176. package/src/lib/ui/compile/hoistableChildRenders.ts +112 -0
  177. package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +3 -1
  178. package/src/lib/ui/compile/interpolationClassifierForRoot.ts +37 -0
  179. package/src/lib/ui/compile/isSpuriousAsyncReadDiagnostic.ts +174 -0
  180. package/src/lib/ui/compile/liftAsyncSubExpressions.ts +166 -0
  181. package/src/lib/ui/compile/lowerAsyncInterpolations.ts +92 -0
  182. package/src/lib/ui/compile/lowerContext.ts +10 -0
  183. package/src/lib/ui/compile/lowerDocAccess.ts +26 -23
  184. package/src/lib/ui/compile/lowerScript.ts +40 -4
  185. package/src/lib/ui/compile/nodeAtShadowOffset.ts +28 -0
  186. package/src/lib/ui/compile/parseTemplate.ts +18 -1092
  187. package/src/lib/ui/compile/parseTemplateRecovering.ts +1385 -0
  188. package/src/lib/ui/compile/referencedIdentifiers.ts +35 -0
  189. package/src/lib/ui/compile/renameSignalRefs.ts +21 -22
  190. package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +65 -0
  191. package/src/lib/ui/compile/shadowInterpolationClassifier.ts +47 -0
  192. package/src/lib/ui/compile/sourceFileOptionsSignature.ts +20 -0
  193. package/src/lib/ui/compile/structuralHeadTokens.ts +107 -0
  194. package/src/lib/ui/compile/templateSemanticTokens.ts +21 -0
  195. package/src/lib/ui/compile/templateStartOffset.ts +15 -0
  196. package/src/lib/ui/compile/tryPlan.ts +4 -3
  197. package/src/lib/ui/compile/types/AnalyzedComponent.ts +4 -0
  198. package/src/lib/ui/compile/types/AsyncInterpolationField.ts +19 -0
  199. package/src/lib/ui/compile/types/InterpolationClassifier.ts +12 -0
  200. package/src/lib/ui/compile/types/InterpolationKind.ts +7 -0
  201. package/src/lib/ui/compile/types/ParseDiagnostic.ts +8 -0
  202. package/src/lib/ui/compile/types/SeedTypeClassifier.ts +15 -0
  203. package/src/lib/ui/compile/types/TemplateNode.ts +36 -4
  204. package/src/lib/ui/compile/types/ValuePositionInterpolation.ts +13 -0
  205. package/src/lib/ui/compile/writtenTemplateNames.ts +84 -0
  206. package/src/lib/ui/computed.ts +28 -1
  207. package/src/lib/ui/createScope.ts +35 -68
  208. package/src/lib/ui/dom/anchoredBranch.ts +142 -0
  209. package/src/lib/ui/dom/appendText.ts +8 -3
  210. package/src/lib/ui/dom/awaitBlock.ts +43 -94
  211. package/src/lib/ui/dom/bindProp.ts +22 -0
  212. package/src/lib/ui/dom/bindableProp.ts +47 -0
  213. package/src/lib/ui/dom/cellPending.ts +24 -0
  214. package/src/lib/ui/dom/disposeRange.ts +2 -1
  215. package/src/lib/ui/dom/each.ts +26 -4
  216. package/src/lib/ui/dom/eachAsync.ts +39 -3
  217. package/src/lib/ui/dom/fillBoundary.ts +2 -3
  218. package/src/lib/ui/dom/fillRange.ts +4 -4
  219. package/src/lib/ui/dom/hydrate.ts +6 -12
  220. package/src/lib/ui/dom/isComment.ts +1 -1
  221. package/src/lib/ui/dom/matchingRangeClose.ts +29 -0
  222. package/src/lib/ui/dom/mount.ts +1 -2
  223. package/src/lib/ui/dom/mountChild.ts +9 -40
  224. package/src/lib/ui/dom/mountRange.ts +2 -3
  225. package/src/lib/ui/dom/mountStreamedChild.ts +84 -0
  226. package/src/lib/ui/dom/mountSwappableRange.ts +46 -4
  227. package/src/lib/ui/dom/mutateDocContainer.ts +65 -0
  228. package/src/lib/ui/dom/on.ts +2 -2
  229. package/src/lib/ui/dom/readCell.ts +40 -0
  230. package/src/lib/ui/dom/switchBlock.ts +30 -7
  231. package/src/lib/ui/dom/tryBlock.ts +230 -66
  232. package/src/lib/ui/dom/types/SwitchCase.ts +5 -1
  233. package/src/lib/ui/dom/when.ts +12 -2
  234. package/src/lib/ui/dom/withScope.ts +2 -2
  235. package/src/lib/ui/finalizeStreamedChildren.ts +89 -0
  236. package/src/lib/ui/flight.ts +56 -0
  237. package/src/lib/ui/html.ts +12 -1
  238. package/src/lib/ui/isolateCellBarrier.ts +17 -0
  239. package/src/lib/ui/linked.ts +48 -2
  240. package/src/lib/ui/remoteProxy.ts +84 -159
  241. package/src/lib/ui/renderChain.ts +49 -13
  242. package/src/lib/ui/renderToStream.ts +22 -18
  243. package/src/lib/ui/resumeSeedScript.ts +1 -1
  244. package/src/lib/ui/router.ts +63 -36
  245. package/src/lib/ui/runtime/AsyncCellError.ts +20 -0
  246. package/src/lib/ui/runtime/CELL_SEED.ts +14 -0
  247. package/src/lib/ui/runtime/CURRENT_BOUNDARY.ts +11 -0
  248. package/src/lib/ui/runtime/CURRENT_PATH.ts +29 -0
  249. package/src/lib/ui/runtime/RENDER.ts +10 -7
  250. package/src/lib/ui/runtime/RESUME.ts +4 -4
  251. package/src/lib/ui/runtime/STREAMED_CELLS.ts +57 -0
  252. package/src/lib/ui/runtime/ambientPathBacking.ts +32 -0
  253. package/src/lib/ui/runtime/applyPatchToTree.ts +1 -1
  254. package/src/lib/ui/runtime/blockId.ts +31 -0
  255. package/src/lib/ui/runtime/boundaryFor.ts +11 -0
  256. package/src/lib/ui/runtime/cellBarrierBacking.ts +30 -0
  257. package/src/lib/ui/runtime/createAsyncCell.ts +300 -0
  258. package/src/lib/ui/runtime/createDoc.ts +3 -42
  259. package/src/lib/ui/runtime/createEffectNode.ts +9 -0
  260. package/src/lib/ui/runtime/enterRenderPass.ts +5 -4
  261. package/src/lib/ui/runtime/flushEffects.ts +16 -6
  262. package/src/lib/ui/runtime/isAsyncFunction.ts +14 -0
  263. package/src/lib/ui/runtime/nextBlockId.ts +8 -7
  264. package/src/lib/ui/runtime/renderPath.ts +16 -0
  265. package/src/lib/ui/runtime/types/Boundary.ts +9 -0
  266. package/src/lib/ui/runtime/types/RenderContext.ts +9 -7
  267. package/src/lib/ui/runtime/types/SsrRender.ts +9 -2
  268. package/src/lib/ui/runtime/types/UiComponent.ts +1 -5
  269. package/src/lib/ui/runtime/withOptionalPath.ts +8 -0
  270. package/src/lib/ui/runtime/withPath.ts +16 -0
  271. package/src/lib/ui/runtime/withPathFrom.ts +20 -0
  272. package/src/lib/ui/seedStreamedResolution.ts +31 -6
  273. package/src/lib/ui/settleAsyncCells.ts +24 -0
  274. package/src/lib/ui/socketProxy.ts +1 -1
  275. package/src/lib/ui/startClient.ts +16 -31
  276. package/src/lib/ui/trackedComputed.ts +68 -0
  277. package/src/lib/ui/types/Scope.ts +8 -24
  278. package/src/lib/ui/watch.ts +3 -3
  279. package/src/serverEntry.ts +14 -0
  280. package/template/src/server/rpc/getHello.ts +15 -13
  281. package/template/test/app.test.ts +1 -1
  282. package/src/lib/server/runtime/devHotModuleResponse.ts +0 -41
  283. package/src/lib/shared/DEV_HOT_PREFIX.ts +0 -7
  284. package/src/lib/shared/UNREACHABLE_STATUSES.ts +0 -13
  285. package/src/lib/shared/hasReplayableRequest.ts +0 -17
  286. package/src/lib/shared/hydratingSlot.ts +0 -12
  287. package/src/lib/shared/outboxProbeSlot.ts +0 -20
  288. package/src/lib/shared/types/Outbox.ts +0 -9
  289. package/src/lib/shared/types/OutboxEntry.ts +0 -27
  290. package/src/lib/shared/types/SmartReadOptions.ts +0 -36
  291. package/src/lib/shared/wakeHydrationPeeks.ts +0 -20
  292. package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +0 -5
  293. package/src/lib/ui/compile/REACTIVE_CALLEES.ts +0 -11
  294. package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +0 -80
  295. package/src/lib/ui/compile/markupTokens.ts +0 -313
  296. package/src/lib/ui/compile/structuralBlockTokens.ts +0 -100
  297. package/src/lib/ui/dom/applyResolved.ts +0 -87
  298. package/src/lib/ui/dom/mutateDocArray.ts +0 -38
  299. package/src/lib/ui/dom/scopeLabel.ts +0 -21
  300. package/src/lib/ui/dom/text.ts +0 -20
  301. package/src/lib/ui/history.ts +0 -108
  302. package/src/lib/ui/installHotBridge.ts +0 -95
  303. package/src/lib/ui/installInspectorBridge.ts +0 -140
  304. package/src/lib/ui/outbox.ts +0 -35
  305. package/src/lib/ui/persist.ts +0 -115
  306. package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +0 -281
  307. package/src/lib/ui/rpcOutbox/outboxRegistry.ts +0 -69
  308. package/src/lib/ui/runtime/PATCH_BUS.ts +0 -28
  309. package/src/lib/ui/runtime/captureModelDoc.ts +0 -43
  310. package/src/lib/ui/runtime/hotInstances.ts +0 -10
  311. package/src/lib/ui/runtime/hotReloadEnabled.ts +0 -8
  312. package/src/lib/ui/runtime/hotReplace.ts +0 -38
  313. package/src/lib/ui/runtime/liveScopes.ts +0 -15
  314. package/src/lib/ui/runtime/localStoragePersistence.ts +0 -47
  315. package/src/lib/ui/runtime/registerHotInstance.ts +0 -23
  316. package/src/lib/ui/runtime/seedModelDoc.ts +0 -26
  317. package/src/lib/ui/runtime/types/HotInstance.ts +0 -22
  318. package/src/lib/ui/runtime/types/PatchEvent.ts +0 -16
  319. package/src/lib/ui/seedResolved.ts +0 -28
  320. package/src/lib/ui/sync.ts +0 -48
  321. package/src/lib/ui/types/History.ts +0 -14
  322. package/src/lib/ui/types/PersistHandle.ts +0 -11
  323. package/src/lib/ui/types/PersistenceStore.ts +0 -12
  324. package/src/lib/ui/types/ResolvedFrame.ts +0 -15
  325. package/src/lib/ui/types/SyncTransport.ts +0 -13
@@ -1,5 +1,4 @@
1
1
  import { scope } from '../runtime/scope.ts'
2
- import { scopeLabel } from './scopeLabel.ts'
3
2
  import { withScope } from './withScope.ts'
4
3
 
5
4
  /*
@@ -23,7 +22,7 @@ export function mount(
23
22
  /* Establish this component's lexical scope (nested, `awaiting` so it adopts the model
24
23
  doc the build's first `doc()` creates) and render pass, run the build under it, and
25
24
  restore the previous scope — the shared mount core (see `withScope`). */
26
- const { lexical } = withScope(scopeLabel(host), () => scope(() => build(host, props)))
25
+ const { lexical } = withScope(() => scope(() => build(host, props)))
27
26
  return () => {
28
27
  lexical.dispose()
29
28
  host.textContent = ''
@@ -1,25 +1,12 @@
1
- import { captureModelDoc } from '../runtime/captureModelDoc.ts'
2
- import { hotReloadEnabled } from '../runtime/hotReloadEnabled.ts'
3
1
  import { OWNER } from '../runtime/OWNER.ts'
4
- import { registerHotInstance } from '../runtime/registerHotInstance.ts'
5
2
  import type { UiComponent } from '../runtime/types/UiComponent.ts'
3
+ import { withOptionalPath } from '../runtime/withOptionalPath.ts'
6
4
  import { mountRange } from './mountRange.ts'
7
5
 
8
- /* Build-time flag the production client defines false (see build.ts `define`) so the hot
9
- path below — and its captureModelDoc/registerHotInstance imports — dead-code-eliminate
10
- (`!false` folds to a constant the minifier can prove) instead of shipping behind a runtime
11
- flag it can't. Dev defines it true; the test preload sets it on globalThis so the bare
12
- reference resolves to true there, leaving `hotReloadEnabled` alone in charge as before. */
13
- declare const __ABIDE_DEV__: boolean
14
-
15
6
  /*
16
7
  Mounts a child component as a marker-bounded range at `before` in `parent` — no
17
8
  wrapper element, so the child's root is a true direct child of the parent (see
18
- `mountRange`). Plain path (production, and dev without the hot bridge): just run the
19
- range mount with the component's own `build`. Hot path (hotReloadEnabled and the
20
- factory carries a module id): keep the mount handle (its range markers + disposer)
21
- and record the instance so an edit can re-fill the same range in place (see
22
- `hotReplace`), and file a cleanup with the mounting owner so the record and its scope
9
+ `mountRange`). Files the range's dispose with the mounting owner so its scope and DOM
23
10
  leave together when the parent (or branch/row) tears down.
24
11
  */
25
12
  // @documentation plumbing
@@ -28,30 +15,12 @@ export function mountChild(
28
15
  factory: UiComponent,
29
16
  props: Parameters<UiComponent>[1],
30
17
  before: Node | null = null,
31
- label?: string,
18
+ /* The compiler's source-order ordinal for this `<Child/>` mount site — pushed onto the render
19
+ path so the child's scope (and its cells) get a serialization-stable id under this parent
20
+ (two same-type siblings differ by ordinal; the same site across `{#each}` rows differs by the
21
+ row key the each block already pushed). Absent (a non-compiled caller) → no path segment. */
22
+ ordinal?: number,
32
23
  ): void {
33
- const moduleId = factory.__abideId
34
- if (!__ABIDE_DEV__ || !hotReloadEnabled.current || moduleId === undefined) {
35
- mountRange(parent, factory.build, props, before, label)
36
- return
37
- }
38
- /* Capture the component's model alongside its mount handle, so a later swap can
39
- carry its state across (see `hotReplace`). */
40
- const { value: handle, model } = captureModelDoc(() =>
41
- mountRange(parent, factory.build, props, before, label),
42
- )
43
- const instance = {
44
- factory,
45
- props,
46
- label,
47
- start: handle.start,
48
- end: handle.end,
49
- dispose: handle.dispose,
50
- model,
51
- }
52
- const remove = registerHotInstance(moduleId, instance)
53
- OWNER.current?.push(() => {
54
- instance.dispose()
55
- remove()
56
- })
24
+ const handle = withOptionalPath(ordinal, () => mountRange(parent, factory.build, props, before))
25
+ OWNER.current?.push(handle.dispose)
57
26
  }
@@ -29,18 +29,17 @@ export function mountRange(
29
29
  build: (host: Node, props?: UiProps) => void,
30
30
  props: UiProps | undefined,
31
31
  before: Node | null = null,
32
- label: string | undefined = undefined,
33
32
  ): { start: Comment; end: Comment; dispose: () => void } {
34
33
  const hydration = RENDER.hydration
35
34
  const start = openMarker(parent, RANGE_OPEN, before)
36
35
  if (hydration === undefined) {
37
36
  const end = openMarker(parent, RANGE_CLOSE, before)
38
- return fillRange(start, end, build, props, label)
37
+ return fillRange(start, end, build, props)
39
38
  }
40
39
  /* Hydrate: adopt the server range in place. Establish the child's lexical scope
41
40
  and render pass (same as `fillRange`), build claiming the existing nodes, then
42
41
  claim the end marker the build's content stops before. */
43
- const scoped = withScope(label, () => scope(() => build(parent, props)))
42
+ const scoped = withScope(() => scope(() => build(parent, props)))
44
43
  const end = openMarker(parent, RANGE_CLOSE)
45
44
  return { start, end, dispose: disposeRange(scoped, start, end) }
46
45
  }
@@ -0,0 +1,84 @@
1
+ import { CURRENT_PATH } from '../runtime/CURRENT_PATH.ts'
2
+ import { claimChild } from '../runtime/claimChild.ts'
3
+ import { OWNER } from '../runtime/OWNER.ts'
4
+ import { RANGE_OPEN } from '../runtime/RANGE_MARKER.ts'
5
+ import { RENDER } from '../runtime/RENDER.ts'
6
+ import type { UiComponent } from '../runtime/types/UiComponent.ts'
7
+ import { withOptionalPath } from '../runtime/withOptionalPath.ts'
8
+ import { withoutHydration } from '../runtime/withoutHydration.ts'
9
+ import { discardBoundary } from './discardBoundary.ts'
10
+ import { mountRange } from './mountRange.ts'
11
+ import { openMarker } from './openMarker.ts'
12
+
13
+ /*
14
+ The client mount for a HOISTABLE child (ADR-0039) — a DUAL-MODE adopter. The server decides per
15
+ render whether a hoistable child inlined (fast/settled) or streamed (still-pending), and the client
16
+ build can't know which, so this probes the hydration cursor:
17
+
18
+ - `<!--[-->` (RANGE_OPEN) next → the server INLINED the child; adopt its range exactly as
19
+ `mountChild` does (byte-identical to the pre-ADR-0039 path).
20
+ - `<!--abide:await:CHILDPATH-->` next → the server STREAMED it; `__abideSwap` has already spliced
21
+ the child's `<!--[-->…<!--]-->` between the boundary markers before the bundle ran, so claim the
22
+ boundary open, adopt the inner range in place, claim the boundary close.
23
+ - no hydration (a client-side navigation) → a plain create-mode mount, like `mountChild`.
24
+
25
+ CHILDPATH is the child's render-path — `withPath(ordinal, () => CURRENT_PATH.current)` — computed the
26
+ same way the server's `renderPath(ordinal)` wrote the boundary id, so the two agree with no counter.
27
+ A streamed child re-mounts (no RESUME value of its own — the boundary is html-only); nested `{#await}`
28
+ blocks and async cells inside it adopt through their own existing channels.
29
+ */
30
+ // @documentation plumbing
31
+ export function mountStreamedChild(
32
+ parent: Node,
33
+ factory: UiComponent,
34
+ props: Parameters<UiComponent>[1],
35
+ before: Node | null = null,
36
+ ordinal?: number,
37
+ ): void {
38
+ const run = <T>(build: () => T): T => withOptionalPath(ordinal, build)
39
+ const mount = (): { dispose: () => void } =>
40
+ run(() => mountRange(parent, factory.build, props, before))
41
+
42
+ const hydration = RENDER.hydration
43
+ if (hydration === undefined) {
44
+ /* Call mount() first, THEN register its dispose — `OWNER.current?.push(mount())` would
45
+ short-circuit the whole expression (never mounting) when there is no owner. */
46
+ const handle = mount()
47
+ OWNER.current?.push(handle.dispose)
48
+ return
49
+ }
50
+
51
+ /* Probe the cursor without consuming it. Streamed iff the next node is this child's boundary. */
52
+ const childPath = run(() => CURRENT_PATH.current)
53
+ const cursor = claimChild(hydration, parent)
54
+ const streamed =
55
+ cursor !== null &&
56
+ cursor.nodeType === 8 &&
57
+ (cursor as Comment).data === `abide:await:${childPath}`
58
+
59
+ if (!streamed) {
60
+ /* INLINE — the server inlined a settled child; adopt its `[ … ]` range in place. */
61
+ const handle = mount()
62
+ OWNER.current?.push(handle.dispose)
63
+ return
64
+ }
65
+
66
+ /* STREAMED — claim the boundary open (advances the cursor to the swapped-in `[`). */
67
+ const open = openMarker(parent, `abide:await:${childPath}`)
68
+ const inner = claimChild(hydration, parent)
69
+ const warm = inner !== null && inner.nodeType === 8 && (inner as Comment).data === RANGE_OPEN
70
+ if (warm) {
71
+ const handle = mount()
72
+ openMarker(parent, `/abide:await:${childPath}`)
73
+ OWNER.current?.push(handle.dispose)
74
+ return
75
+ }
76
+ /* COLD (defensive — renderToStream drains every await before closing, so a warm boundary is the
77
+ deterministic path). The fragment never arrived: discard the empty boundary and create-mount
78
+ the child fresh at that position; its streamed cells resolve post-mount via receiveStreamedCell. */
79
+ const after = discardBoundary(parent, open, `/abide:await:${childPath}`, hydration)
80
+ const handle = withoutHydration(() =>
81
+ run(() => mountRange(parent, factory.build, props, after)),
82
+ )
83
+ OWNER.current?.push(handle.dispose)
84
+ }
@@ -1,9 +1,13 @@
1
1
  import { effect } from '../effect.ts'
2
+ import { CURRENT_PATH } from '../runtime/CURRENT_PATH.ts'
2
3
  import { RANGE_CLOSE, RANGE_OPEN } from '../runtime/RANGE_MARKER.ts'
3
4
  import { RENDER } from '../runtime/RENDER.ts'
4
5
  import { scope } from '../runtime/scope.ts'
5
6
  import { scopeGroup } from '../runtime/scopeGroup.ts'
7
+ import { withPathFrom } from '../runtime/withPathFrom.ts'
8
+ import { clearBetween } from './clearBetween.ts'
6
9
  import { fillBefore } from './fillBefore.ts'
10
+ import { matchingRangeClose } from './matchingRangeClose.ts'
7
11
  import { openMarker } from './openMarker.ts'
8
12
  import { replaceRange } from './replaceRange.ts'
9
13
 
@@ -35,6 +39,15 @@ export function mountSwappableRange<Key>(
35
39
  key: () => Key,
36
40
  contentFor: (key: Key) => ((parent: Node) => void) | undefined,
37
41
  before: Node | null = null,
42
+ /* The chosen branch MAY differ between SSR and the client's first render — set only for an
43
+ ASYNC subject (a bare async `{#if}`/`{#switch}`, or an async cond-chain branch), whose cell
44
+ can be settled on the server (a barrier-baked value) yet pending on the client, so each side
45
+ selects a different branch. Adopting SSR nodes in place then orphans them (they are neither
46
+ claimed nor cleared) and the next swap duplicates. When set, hydration DISCARDS the SSR range
47
+ content and builds the client's branch fresh instead — correct regardless of what the server
48
+ rendered. Left false for a sync range, where SSR and client always agree and in-place
49
+ adoption (node reuse, preserved focus/scroll) is safe. */
50
+ mayDiverge = false,
38
51
  ): void {
39
52
  const hydration = RENDER.hydration
40
53
  /* The live branch's scope, registered with the owner so it disposes on owner
@@ -44,10 +57,39 @@ export function mountSwappableRange<Key>(
44
57
  let activeKey: Key
45
58
  let end: Comment
46
59
 
60
+ /* Capture the render path at construction (this block's ancestry), and wrap each chosen
61
+ branch's builder so its content builds under `basePath/branchKey` — the key is the swap
62
+ discriminator (`'then'`/`'else'` or a case index), so a component/cell in one branch gets a
63
+ distinct, stable id from the other, re-established on every reactive swap (when the ambient
64
+ path is gone). An empty branch (`undefined`) needs no wrap. */
65
+ const basePath = CURRENT_PATH.current
66
+ const chosenFor = (branchKey: Key): ((parent: Node) => void) | undefined => {
67
+ const content = contentFor(branchKey)
68
+ if (content === undefined) {
69
+ return undefined
70
+ }
71
+ return (host) => withPathFrom(basePath, String(branchKey), () => content(host))
72
+ }
73
+
47
74
  const start = openMarker(parent, RANGE_OPEN, before)
48
- if (hydration !== undefined) {
75
+ if (hydration !== undefined && mayDiverge) {
76
+ /* Async range: don't trust the SSR content to match the client's branch. Discard the
77
+ server's rendered range (balanced `[`…`]`), park the cursor on the real close so it
78
+ claims correctly, then build the client's chosen branch FRESH (`fillBefore` clears the
79
+ claim cursor for the build). Usually the client is pending here → nothing built → an
80
+ empty range the later reactive swap fills, matching a clean create. */
81
+ activeKey = key()
82
+ const chosen = chosenFor(activeKey)
83
+ const matchedEnd = matchingRangeClose(start)
84
+ clearBetween(start, matchedEnd)
85
+ hydration.next.set(parent, matchedEnd)
86
+ end = openMarker(parent, RANGE_CLOSE)
87
+ if (chosen !== undefined) {
88
+ dispose = group.track(fillBefore(end, chosen))
89
+ }
90
+ } else if (hydration !== undefined) {
49
91
  activeKey = key()
50
- const chosen = contentFor(activeKey)
92
+ const chosen = chosenFor(activeKey)
51
93
  if (chosen !== undefined) {
52
94
  dispose = group.track(scope(() => chosen(parent))) // content claims the SSR nodes in place
53
95
  }
@@ -55,7 +97,7 @@ export function mountSwappableRange<Key>(
55
97
  } else {
56
98
  end = openMarker(parent, RANGE_CLOSE, before)
57
99
  activeKey = key()
58
- const chosen = contentFor(activeKey)
100
+ const chosen = chosenFor(activeKey)
59
101
  if (chosen !== undefined) {
60
102
  dispose = group.track(fillBefore(end, chosen))
61
103
  }
@@ -67,7 +109,7 @@ export function mountSwappableRange<Key>(
67
109
  return
68
110
  }
69
111
  activeKey = next
70
- const chosen = contentFor(next)
112
+ const chosen = chosenFor(next)
71
113
  /* Null `dispose` before `replaceRange` builds the new branch: a reentrant swap
72
114
  during that build (an effect in the new content writing the source) would
73
115
  otherwise re-enter with the already-disposed disposer and clear it twice. */
@@ -0,0 +1,65 @@
1
+ import type { Doc } from '../runtime/types/Doc.ts'
2
+ import { readCall } from './readCall.ts'
3
+
4
+ /*
5
+ An in-place-mutating container method lowered on a reactive-document value. The
6
+ doc-access lowering rewrites e.g. `model.items.splice(i, 1)` to
7
+ `$$mutateDocContainer(model, "items", "splice", [i, 1])` rather than the bare
8
+ `$$readCall(model.read("items"), …)` it emits for non-mutating calls. The reason:
9
+ `read` returns the live tree value by reference, so a bare `.splice()`/`.sort()`/
10
+ `.add()`/`.set()`/… would mutate the document in place while never emitting a
11
+ patch — so no reader would re-render (`.push` is the one already-handled exception,
12
+ lowered to `add` patches). Cloning the container, applying the mutation to the copy,
13
+ and writing it back through `replace` emits a real patch: readers wake.
14
+
15
+ Covers the three mutable containers the doc codec serializes (encodeRefJson):
16
+ Array (cloned by `slice`), Map (`new Map`) and Set (`new Set`) — the mutating
17
+ method names are disjoint across the three (`pop`/`splice`/… vs `add`/`set`/…), so
18
+ the caller routes them all here and the actual kind is decided at runtime by the
19
+ value's type, no compile-time check needed. Element identity is preserved for
20
+ untouched entries (a shallow copy), so a keyed `{#for}` reconciles instead of
21
+ rebuilding. The native method's return value (spliced-out elements, the new
22
+ length, the deleted flag, …) is returned unchanged.
23
+ */
24
+ // @documentation plumbing
25
+ export function mutateDocContainer(
26
+ doc: Doc,
27
+ path: string,
28
+ member: string,
29
+ args: unknown[],
30
+ ): unknown {
31
+ const current = doc.read<unknown>(path)
32
+ const copy = cloneContainer(current)
33
+ /* Not a container we clone (array/Map/Set) — fall back to the guarded in-place call so
34
+ the author still gets readCall's authored-scope error message (a plain doc object has
35
+ no methods, so this is the same throw the bare call would raise). */
36
+ if (copy === undefined) {
37
+ return readCall(current, path, member, args)
38
+ }
39
+ const method = (copy as unknown as Record<string, unknown>)[member]
40
+ if (typeof method !== 'function') {
41
+ throw new TypeError(
42
+ `abide: cannot call .${member}() — "${path}".${member} is not a function (got ${typeof method})`,
43
+ )
44
+ }
45
+ const returned = (method as (...callArgs: unknown[]) => unknown).apply(copy, args)
46
+ doc.replace(path, copy)
47
+ return returned
48
+ }
49
+
50
+ /* A shallow clone of a mutable doc container, or undefined when the value is not one — the
51
+ shallow copy preserves element/entry identity so keyed reconciliation survives the patch. */
52
+ function cloneContainer(
53
+ current: unknown,
54
+ ): unknown[] | Map<unknown, unknown> | Set<unknown> | undefined {
55
+ if (Array.isArray(current)) {
56
+ return current.slice()
57
+ }
58
+ if (current instanceof Map) {
59
+ return new Map(current)
60
+ }
61
+ if (current instanceof Set) {
62
+ return new Set(current)
63
+ }
64
+ return undefined
65
+ }
@@ -8,8 +8,8 @@ Attaches an event listener and registers its removal with the current ownership
8
8
  scope, so a component's listeners detach when it disposes. This is the runtime
9
9
  target for an `onclick={…}` binding; the handler body is where the compiler's
10
10
  lowered patches (`model.replace(...)`) run. The handler is pinned to the scope it
11
- was attached under, so an ambient `scope()` inside it (e.g. `scope().undo()`)
12
- resolves the component, not whatever is current when the event fires.
11
+ was attached under, so an ambient `scope()` inside it resolves the component, not
12
+ whatever is current when the event fires.
13
13
  */
14
14
  // @documentation plumbing
15
15
  export function on(element: Element, type: string, handler: EventListener): void {
@@ -0,0 +1,40 @@
1
+ import { isAsyncCell } from '../../shared/isAsyncCell.ts'
2
+ import type { AsyncComputed } from '../../shared/types/AsyncComputed.ts'
3
+ import { AsyncCellError } from '../runtime/AsyncCellError.ts'
4
+
5
+ /*
6
+ Unified read for a `computed`/`linked` reference the compiler lowers to `$$readCell(NAME)`:
7
+ an async cell (`AsyncComputed`/`AsyncState`) yields its latest retained value, a `derive`
8
+ reader (a function) is called, and a sync `Computed`/`State` yields its `.value`. One read
9
+ shape lets a `linked`/`computed` binding auto-track whichever source it resolved to — a
10
+ settling promise, a stream, or a plain value — with no read-site branching in codegen.
11
+
12
+ For an async cell this is a **throwing peek** (ADR-0019 D3.2), the value-aware rule that
13
+ routes a "nothing to render" error to the nearest `{#try}`:
14
+ - error AND no retained value → throw `AsyncCellError` (carries the cell for keep-the-watch);
15
+ - error WITH a retained value → the value (a failed background refresh stays visible — SWR);
16
+ - pending (no value) → `undefined`;
17
+ - resolved → the value.
18
+ The throw is a codegen behaviour only — `cell.peek()`/`cell.error()` in JS never throw; the
19
+ author handles an error locally via the probes, or lets it reach a `{#try}`.
20
+ */
21
+ // @documentation plumbing
22
+ export function readCell(cell: unknown): unknown {
23
+ if (isAsyncCell(cell)) {
24
+ const asyncCell = cell as AsyncComputed<unknown>
25
+ const value = asyncCell.peek()
26
+ /* A retained value wins over an error (SWR) and over pending — return it. Only when
27
+ nothing is retained does an error become "nothing to render" and throw. */
28
+ if (value === undefined) {
29
+ const error = asyncCell.error()
30
+ if (error !== undefined) {
31
+ throw new AsyncCellError(asyncCell, error)
32
+ }
33
+ }
34
+ return value
35
+ }
36
+ if (typeof cell === 'function') {
37
+ return (cell as () => unknown)()
38
+ }
39
+ return (cell as { value: unknown }).value
40
+ }
@@ -18,22 +18,45 @@ export function switchBlock(
18
18
  subject: () => unknown,
19
19
  cases: SwitchCase[],
20
20
  before: Node | null = null,
21
+ isPending?: () => boolean,
21
22
  ): void {
22
- /* Pick the first case matching the subject (`===`), else the default (`match`
23
- undefined), else -1 for no match. */
23
+ /* Walk the cases in source order (the same order a `{#if}`/`{:elseif}` chain reads): the
24
+ first whose own async condition is still `pending()` HOLDS the chain — returns -1, so no
25
+ later branch renders on a not-yet-known earlier condition; the first whose `match() ===`
26
+ the subject wins; a match-less `default` is the fallback once every case settled without a
27
+ match. A plain `{#switch}` (no per-case `pending`) reduces to first-match-else-default. */
24
28
  const select = (value: unknown): number => {
25
- const matched = cases.findIndex(
26
- (entry) => entry.match !== undefined && entry.match() === value,
27
- )
28
- return matched === -1 ? cases.findIndex((entry) => entry.match === undefined) : matched
29
+ let fallback = -1
30
+ for (let index = 0; index < cases.length; index++) {
31
+ const entry = cases[index]
32
+ if (entry === undefined) {
33
+ continue
34
+ }
35
+ if (entry.pending?.()) {
36
+ return -1
37
+ }
38
+ if (entry.match === undefined) {
39
+ fallback = index
40
+ } else if (entry.match() === value) {
41
+ return index
42
+ }
43
+ }
44
+ return fallback
29
45
  }
30
46
  mountSwappableRange(
31
47
  parent,
32
- () => select(subject()),
48
+ /* A pending bare async subject (compiler-supplied `isPending`) selects no case — not
49
+ even the default — so the block renders nothing until the cell settles, rather than
50
+ conflating "still loading" with a subject that matched the default. */
51
+ () => (isPending?.() === true ? -1 : select(subject())),
33
52
  (index) => {
34
53
  const chosen = index === -1 ? undefined : cases[index]
35
54
  return chosen && ((p) => chosen.render(p))
36
55
  },
37
56
  before,
57
+ /* Any async subject/branch (a bare async subject, or an async `{:else if}` in a cond-chain)
58
+ can select a different branch on the server than the pending client, so hydration must
59
+ discard the SSR range rather than adopt it in place. */
60
+ isPending !== undefined || cases.some((entry) => entry.pending !== undefined),
38
61
  )
39
62
  }