@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
package/src/build.ts CHANGED
@@ -1,37 +1,44 @@
1
1
  import { clientBuildPlugins } from './clientBuildPlugins.ts'
2
2
  import { abideLog } from './lib/shared/abideLog.ts'
3
3
  import { exitOnBuildFailure } from './lib/shared/exitOnBuildFailure.ts'
4
+ import { generateDeclarations } from './lib/shared/generateDeclarations.ts'
4
5
  import { markFrameworkSourcesIgnored } from './lib/shared/markFrameworkSourcesIgnored.ts'
5
6
 
6
7
  const CLIENT_ENTRY = new URL('./clientEntry.ts', import.meta.url).pathname
7
8
 
8
9
  /*
9
- Builds the client-side bundle into `${cwd}/dist/_app`. Runs Bun.build with
10
- the abide-ui `.abide` loader, the virtual-module resolver, and (optionally)
11
- Tailwind. When `compress`, each emitted file is also
10
+ Builds the client-side bundle into `${cwd}/dist` (see `appDir` below for the
11
+ exact directory). Runs Bun.build with the abide-ui `.abide` loader, the
12
+ virtual-module resolver, and (optionally) Tailwind. When `compress`, each emitted file is also
12
13
  written as a gzip-compressed `.gz` sibling (level 9 — paid once at build
13
14
  time) so the server can stream the precompressed bytes directly to any client
14
15
  that accepts gzip (effectively all), and decompress on the fly otherwise. Dev
15
16
  skips compression (compressing on every rebuild dwarfs the bundle itself) — the
16
17
  server falls back to serving the plain bytes when no `.gz` sibling exists.
17
18
 
18
- The bundle is emitted into a per-build staging dir, then swapped into
19
- `_app` with two atomic renames. This keeps every build's writes isolated to
20
- a unique path (so a stray concurrent build can never `rm` files Bun is
21
- mid-flushing — the "writing sourcemap: No such file or directory" race) and
22
- means a long-running dev server reading `_app` lazily off disk never sees a
23
- half-built or emptied directory.
19
+ A production build (`clean`) emits into a per-build staging dir, then swaps it
20
+ into the stable `_app` with two atomic renames. This keeps every build's writes
21
+ isolated to a unique path (so a stray concurrent build can never `rm` files Bun
22
+ is mid-flushing — the "writing sourcemap: No such file or directory" race) and
23
+ means a reader never sees a half-built or emptied `_app`. `clean` also clears
24
+ the whole dist up front so downstream writers — the bundle's connect screen, the
25
+ CLI manifest — start fresh.
24
26
 
25
- `clean` (one-shot builds) clears the whole dist up front so downstream
26
- writers the bundle's connect screen, the CLI manifest start fresh; the
27
- dev orchestrator passes `clean: false` to leave the live dist untouched and
28
- only swap `_app` at the end.
27
+ Dev (`clean: false`) instead emits each generation into its OWN
28
+ `_app.gen-<id>` directory and never mutates it afterward. A dev worker serves
29
+ from exactly the generation it was spawned on (via `ABIDE_APP_DIR`), so a rebuild
30
+ gives the new worker a new dir while the retiring worker keeps reading its own —
31
+ there is no shared mutable `_app`, hence no swap gap and no stale-hash 500s while
32
+ a replaced worker drains (both bind the port via reusePort). The orchestrator
33
+ prunes a generation dir once its worker has exited.
29
34
 
30
- Returns whether the build succeeded. Never throws: a thrown Bun.build / fs
31
- error is logged and treated as a failed build, so the dev loop (and its
32
- last-good server) survives instead of crashing and orphaning the child. By
33
- default a failure exits the process (one-shot `abide build` / `compile`);
34
- the dev orchestrator passes `exitOnFailure: false`.
35
+ Returns `{ appDir }` — the directory the bundle now lives in (`_app` for a
36
+ production build, the generation dir for dev) on success, or `false` on
37
+ failure. Never throws: a thrown Bun.build / fs error is logged and treated as a
38
+ failed build, so the dev loop (and its last-good server) survives instead of
39
+ crashing and orphaning the child. By default a failure exits the process
40
+ (one-shot `abide build` / `compile`); the dev orchestrator passes
41
+ `exitOnFailure: false`.
35
42
  */
36
43
  // @documentation building
37
44
  export async function build({
@@ -48,15 +55,19 @@ export async function build({
48
55
  clean?: boolean
49
56
  exitOnFailure?: boolean
50
57
  dev?: boolean
51
- } = {}): Promise<boolean> {
58
+ } = {}): Promise<{ appDir: string } | false> {
52
59
  const distDir = `${cwd}/dist`
53
- const outDir = `${distDir}/_app`
54
- // Per-build staging + holding dirs; the suffix isolates concurrent builds.
60
+ // The suffix isolates concurrent/successive builds.
55
61
  const buildId = crypto.randomUUID().slice(0, 8)
56
- const stagingDir = `${distDir}/_app.staging-${buildId}`
62
+ /* Production owns the stable `_app`; dev owns a fresh per-generation dir it never
63
+ rewrites (see the header — this is what removes the shared-mutable-`_app` race). */
64
+ const appDir = clean ? `${distDir}/_app` : `${distDir}/_app.gen-${buildId}`
65
+ /* Production stages then atomically swaps into `_app`. Dev builds straight into its
66
+ generation dir — nothing reads it until a worker is spawned pointing at it. */
67
+ const stagingDir = clean ? `${distDir}/_app.staging-${buildId}` : appDir
57
68
  const previousDir = `${distDir}/_app.old-${buildId}`
58
69
 
59
- const fail = (): boolean => {
70
+ const fail = (): false => {
60
71
  if (exitOnFailure) {
61
72
  process.exit(1)
62
73
  }
@@ -74,27 +85,36 @@ export async function build({
74
85
  tailwindWarning: 'bun-plugin-tailwind not installed; building without Tailwind',
75
86
  })
76
87
 
77
- const result = await Bun.build({
78
- entrypoints: [CLIENT_ENTRY],
79
- outdir: stagingDir,
80
- target: 'browser',
81
- splitting: true,
82
- minify,
83
- sourcemap: 'linked',
84
- naming: {
85
- entry: 'client-[hash].[ext]',
86
- chunk: '[name]-[hash].[ext]',
87
- asset: '[name].[ext]',
88
- },
89
- /* Build-time flag the client reads to fold away dev-only machinery: production
90
- (`dev: false`) defines it false so the hot-reload subtree — mountChild's
91
- record path, hotReplace, captureModelDoc — dead-code-eliminates instead of
92
- shipping behind a runtime flag the minifier can't prove. Dev defines it true
93
- to keep HMR. Under `bun test` no define applies; the source falls back to true
94
- so `hotReloadEnabled` alone governs (see startClient/mountChild). */
95
- define: { __ABIDE_DEV__: dev ? 'true' : 'false' },
96
- plugins,
97
- })
88
+ if (!dev) {
89
+ abideLog.info('building client bundle…')
90
+ }
91
+
92
+ /* generateDeclarations builds the rpc `ts.Program` ONCE per build (ADR-0025 D2's
93
+ alias-aware method detection) and writes every src/.abide/*.d.ts editor artifact. Run it
94
+ CONCURRENTLY with Bun.build so its program build hides under the bundle. The runtime
95
+ worker (dev worker / `abide start`) builds NO program at boot — only lazily on the first
96
+ rpc request for the wire codec — so all boot-time .d.ts generation is gone. It never
97
+ throws (editor types are non-critical), so it can't fail the build. */
98
+ const [result] = await Promise.all([
99
+ Bun.build({
100
+ entrypoints: [CLIENT_ENTRY],
101
+ outdir: stagingDir,
102
+ target: 'browser',
103
+ splitting: true,
104
+ minify,
105
+ sourcemap: 'linked',
106
+ /* The abideResolverPlugin's onEnd reachability guard (ADR-0022 D3) reads this to
107
+ classify surviving modules post-DCE and reject any server-only code that leaked. */
108
+ metafile: true,
109
+ naming: {
110
+ entry: 'client-[hash].[ext]',
111
+ chunk: '[name]-[hash].[ext]',
112
+ asset: '[name].[ext]',
113
+ },
114
+ plugins,
115
+ }),
116
+ generateDeclarations({ cwd }),
117
+ ])
98
118
 
99
119
  if (!result.success) {
100
120
  await Bun.$`rm -rf ${stagingDir}`.quiet().nothrow()
@@ -135,29 +155,32 @@ export async function build({
135
155
  : 0
136
156
 
137
157
  /*
138
- Swap staging into _app with two renames: move any existing _app aside,
139
- then rename staging into place. The window where _app is absent is a
140
- single rename, so a reader (the running dev server) never observes a
141
- partial bundle. nothrow on the first move: no _app exists on the first
142
- build or after `clean`.
158
+ Production swap: move any existing `_app` aside, then rename staging into
159
+ place. The window where `_app` is absent is a single rename, so a reader
160
+ never observes a partial bundle; nothrow on the first move since no `_app`
161
+ exists on a fresh (just-cleaned) dist. Dev skips this entirely its bundle
162
+ was built straight into the generation dir, which no worker reads until the
163
+ orchestrator spawns one on it.
143
164
  */
144
- await Bun.$`mv ${outDir} ${previousDir}`.quiet().nothrow()
145
- await Bun.$`mv ${stagingDir} ${outDir}`.quiet()
146
- await Bun.$`rm -rf ${previousDir}`.quiet().nothrow()
165
+ if (clean) {
166
+ await Bun.$`mv ${appDir} ${previousDir}`.quiet().nothrow()
167
+ await Bun.$`mv ${stagingDir} ${appDir}`.quiet()
168
+ await Bun.$`rm -rf ${previousDir}`.quiet().nothrow()
169
+ }
147
170
 
148
171
  if (compress) {
149
172
  abideLog.info(
150
- `wrote ${result.outputs.length} files to ${outDir} (+${result.outputs.length} .gz, ${(compressedBytes / 1024).toFixed(1)} KiB total)`,
173
+ `wrote ${result.outputs.length} files to ${appDir} (+${result.outputs.length} .gz, ${(compressedBytes / 1024).toFixed(1)} KiB total)`,
151
174
  )
152
175
  // Per-file paths are noise at startup; surface them only under DEBUG=abide:build.
153
176
  const buildLog = abideLog.channel('abide:build')
154
177
  result.outputs.forEach((output) => {
155
- buildLog(` - ${output.path.replace(stagingDir, outDir)}`)
178
+ buildLog(` - ${output.path.replace(stagingDir, appDir)}`)
156
179
  })
157
180
  } else {
158
- abideLog.info(`wrote ${result.outputs.length} files to ${outDir}`)
181
+ abideLog.info(`wrote ${result.outputs.length} files to ${appDir}`)
159
182
  }
160
- return true
183
+ return { appDir }
161
184
  } catch (error) {
162
185
  abideLog.error(error)
163
186
  await Bun.$`rm -rf ${stagingDir} ${previousDir}`.quiet().nothrow()
@@ -51,8 +51,6 @@ export async function buildDisconnected({
51
51
  entrypoints: [ENTRY, CSS_ENTRY],
52
52
  target: 'browser',
53
53
  minify: true,
54
- // Connect screen ships in production bundles only — fold dev-only machinery out.
55
- define: { __ABIDE_DEV__: 'false' },
56
54
  plugins,
57
55
  })
58
56
 
package/src/checkAbide.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { resolve } from 'node:path'
2
2
  import ts from 'typescript'
3
3
  import { collectAbideDiagnostics } from './lib/ui/compile/collectAbideDiagnostics.ts'
4
- import { createShadowProgram } from './lib/ui/compile/createShadowProgram.ts'
4
+ import { createShadowProgram, type ShadowProgram } from './lib/ui/compile/createShadowProgram.ts'
5
+ import { interpolationClassifierForRoot } from './lib/ui/compile/interpolationClassifierForRoot.ts'
5
6
  import { nearestProjectRoot } from './lib/ui/compile/nearestProjectRoot.ts'
6
7
  import type { AbideDiagnostic } from './lib/ui/compile/types/AbideDiagnostic.ts'
7
8
 
@@ -58,8 +59,17 @@ function collectByProject(cwd: string): { diagnostics: AbideDiagnostic[]; checke
58
59
  const root = nearestProjectRoot(path, cwd)
59
60
  byProject.set(root, [...(byProject.get(root) ?? []), path])
60
61
  }
62
+ /* Per-root cache of the VERBATIM classifier program (ADR-0032). The type-check pass peek-wraps
63
+ async interpolations so they resolve to their settled value, but the classifier that decides
64
+ WHICH sub-expressions are async must read un-wrapped shadows — so it rides a separate verbatim
65
+ program, built (and reused) here per root. */
66
+ const classifierCache = new Map<string, ShadowProgram | undefined>()
61
67
  const diagnostics = [...byProject].flatMap(([root, paths]) =>
62
- collectAbideDiagnostics(createShadowProgram(root, paths)),
68
+ collectAbideDiagnostics(
69
+ createShadowProgram(root, paths, (abidePath) =>
70
+ interpolationClassifierForRoot(classifierCache, root, abidePath),
71
+ ),
72
+ ),
63
73
  )
64
74
  const checked = [...byProject.values()].reduce((total, paths) => total + paths.length, 0)
65
75
  return { diagnostics, checked }
package/src/devEntry.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { watch } from 'node:fs'
1
+ import { readdirSync, watch } from 'node:fs'
2
2
  import type { Subprocess } from 'bun'
3
3
  import { build } from './build.ts'
4
4
  import { DEFAULT_PORT } from './lib/server/runtime/DEFAULT_PORT.ts'
@@ -7,6 +7,7 @@ import { DEV_REBUILD_MESSAGE } from './lib/server/runtime/DEV_REBUILD_MESSAGE.ts
7
7
  import { findOpenPort } from './lib/server/runtime/findOpenPort.ts'
8
8
  import { abideLog } from './lib/shared/abideLog.ts'
9
9
  import { changeAffectsClient } from './lib/shared/changeAffectsClient.ts'
10
+ import { DEV_REBUILD_PATH } from './lib/shared/DEV_REBUILD_PATH.ts'
10
11
 
11
12
  /*
12
13
  Dev orchestrator. Replaces `bun --watch` (which only watches the import graph,
@@ -27,6 +28,13 @@ loop we own end to end:
27
28
  live-reload channel; it reconnects straight onto the already-listening
28
29
  replacement and reloads itself.
29
30
 
31
+ Each client build lands in its OWN `dist/_app.gen-<id>` directory (build.ts,
32
+ `clean: false`) and the worker spawned for it is pinned to that dir via
33
+ `ABIDE_APP_DIR`. So the incumbent keeps serving its generation's chunks off disk
34
+ for the whole overlap while the replacement serves the new one — nothing is a
35
+ shared mutable `_app`, so a draining worker can never 500 on chunks the new build
36
+ would otherwise have deleted. A generation dir is pruned once its worker exits.
37
+
30
38
  Restarts are serialized (a build mid-flight queues the next) and the port is
31
39
  fixed so the browser tab stays valid across restarts. A failed build — or a
32
40
  replacement that dies or hangs while booting — keeps the last-good server
@@ -56,8 +64,9 @@ function isGenerated(filename: string): boolean {
56
64
  return filename.split(/[\\/]/).includes(GENERATED_DIR)
57
65
  }
58
66
 
59
- // clean:false leaves the live dist in place — each build swaps _app in atomically,
60
- // so the running server never serves a half-built or emptied bundle.
67
+ // clean:false leaves the live dist in place — each build emits its own
68
+ // `_app.gen-<id>` dir, so the running server never serves a half-built bundle and
69
+ // a rebuild never mutates the dir an existing worker is reading.
61
70
  const buildOptions = {
62
71
  cwd,
63
72
  minify: false,
@@ -69,16 +78,55 @@ const buildOptions = {
69
78
 
70
79
  // The worker currently meant to be serving; undefined while crashed or replaced.
71
80
  let server: Subprocess | undefined
81
+ // The `_app.gen-<id>` dir the active worker is pinned to — the bundle it serves and
82
+ // the dir to prune once it (and no successor sharing it) is gone.
83
+ let currentAppDir = `${cwd}/dist/_app`
72
84
  let shuttingDown = false
73
85
  // Worker exits since the last ready signal — bounds the crash-respawn loop.
74
86
  let exitsWithoutReady = 0
75
87
 
88
+ /* Remove a spent generation dir. Guarded to a `_app.gen-*` path so a bug (or the
89
+ `${cwd}/dist/_app` fallback when the first build failed) can never `rm` the wrong
90
+ thing. Best-effort — a leftover is swept by pruneStaleBuildDirs next dev start. */
91
+ async function pruneGenDir(dir: string): Promise<void> {
92
+ if (!dir.includes('/_app.gen-')) {
93
+ return
94
+ }
95
+ await Bun.$`rm -rf ${dir}`.quiet().nothrow()
96
+ }
97
+
98
+ /* Sweep generation/staging/old dirs left by a prior dev session that exited without
99
+ cleaning up (crash, kill -9). Runs once before the first build so dist doesn't
100
+ accumulate them across sessions. */
101
+ function pruneStaleBuildDirs(): void {
102
+ const distDir = `${cwd}/dist`
103
+ let entries: string[]
104
+ try {
105
+ entries = readdirSync(distDir)
106
+ } catch {
107
+ return // no dist yet — nothing to sweep
108
+ }
109
+ for (const name of entries) {
110
+ if (/^_app\.(gen|staging|old)-/.test(name)) {
111
+ void Bun.$`rm -rf ${distDir}/${name}`.quiet().nothrow()
112
+ }
113
+ }
114
+ }
115
+
76
116
  /*
77
- Spawn a server worker against the fixed dev port. `ready` resolves when the
78
- worker reports its listener is up and init() has run (DEV_READY_MESSAGE) — the
79
- cue that a replacement may retire its predecessor.
117
+ Spawn a server worker against the fixed dev port, pinned to `appDir` (its build
118
+ generation) via ABIDE_APP_DIR so its shell, preload manifest, and asset server all
119
+ read that one dir for its whole life. `ready` resolves when the worker reports its
120
+ listener is up and init() has run (DEV_READY_MESSAGE) — the cue that a replacement
121
+ may retire its predecessor.
80
122
  */
81
- function spawnWorker(port: number): { proc: Subprocess; ready: Promise<void> } {
123
+ function spawnWorker(
124
+ port: number,
125
+ appDir: string,
126
+ /* Only the first worker prints the diagnostic surface map (it eager-loads the
127
+ registry — off the reload hot path for every respawn, whose surface is identical). */
128
+ printSurface = false,
129
+ ): { proc: Subprocess; ready: Promise<void> } {
82
130
  const readiness = Promise.withResolvers<void>()
83
131
  const proc = Bun.spawn({
84
132
  cmd: ['bun', '--preload', PRELOAD, SERVER_ENTRY],
@@ -94,6 +142,8 @@ function spawnWorker(port: number): { proc: Subprocess; ready: Promise<void> } {
94
142
  PORT: String(port),
95
143
  ABIDE_DEV: '1',
96
144
  ABIDE_PARENT_PID: String(process.pid),
145
+ ABIDE_APP_DIR: appDir,
146
+ ABIDE_DEV_SURFACE: printSurface ? '1' : '0',
97
147
  },
98
148
  stdio: ['inherit', 'inherit', 'inherit'],
99
149
  // The child's POST /__abide/reload route signals a rebuild over IPC, so the
@@ -108,7 +158,7 @@ function spawnWorker(port: number): { proc: Subprocess; ready: Promise<void> } {
108
158
  }
109
159
  },
110
160
  })
111
- respawnOnUnexpectedExit(proc, port)
161
+ respawnOnUnexpectedExit(proc, port, appDir)
112
162
  return { proc, ready: readiness.promise }
113
163
  }
114
164
 
@@ -118,7 +168,7 @@ shutdown — used to leave the port dead until a manual restart. Respawn it,
118
168
  giving up after MAX_EXITS_WITHOUT_READY exits in a row so a crash-on-boot
119
169
  doesn't spin; the next save retries through rebuild.
120
170
  */
121
- function respawnOnUnexpectedExit(proc: Subprocess, port: number): void {
171
+ function respawnOnUnexpectedExit(proc: Subprocess, port: number, appDir: string): void {
122
172
  void proc.exited.then((exitCode) => {
123
173
  if (shuttingDown || server !== proc) {
124
174
  return
@@ -132,7 +182,8 @@ function respawnOnUnexpectedExit(proc: Subprocess, port: number): void {
132
182
  return
133
183
  }
134
184
  abideLog.warn(`server exited unexpectedly (code ${exitCode}) — restarting`)
135
- server = spawnWorker(port).proc
185
+ // Respawn on the SAME generation dir — the bundle is fine, the process crashed.
186
+ server = spawnWorker(port, appDir).proc
136
187
  })
137
188
  }
138
189
 
@@ -145,16 +196,21 @@ async function stopWorker(proc: Subprocess): Promise<void> {
145
196
  }
146
197
 
147
198
  /*
148
- Zero-downtime swap. The replacement overlaps the incumbent both bind the dev
149
- port via reusePort (see createServer) and the kernel keeps delivering
150
- connections to the incumbent until it stops so the port answers throughout
151
- the new module graph's boot. Only a replacement that reports ready retires the
152
- incumbent; one that dies or hangs booting is discarded and the last-good server
153
- keeps serving, mirroring how a failed build is handled.
199
+ Zero-downtime swap onto `nextAppDir` (the new build's generation dir). The
200
+ replacement overlaps the incumbent both bind the dev port via reusePort (see
201
+ createServer) and the kernel keeps delivering connections to the incumbent until it
202
+ stops — so the port answers throughout the new module graph's boot, and each worker
203
+ serves its own generation dir so the overlap never crosses chunk sets. Only a
204
+ replacement that reports ready retires the incumbent; one that dies or hangs booting
205
+ is discarded and the last-good server keeps serving, mirroring how a failed build is
206
+ handled. A generation dir is pruned once no worker references it: the retired
207
+ incumbent's dir after it exits, or the failed replacement's own dir if it never
208
+ took over (unless a server-only restart reused the incumbent's dir — then it stays).
154
209
  */
155
- async function replaceServer(port: number): Promise<void> {
210
+ async function replaceServer(port: number, nextAppDir: string): Promise<void> {
156
211
  const previous = server
157
- const next = spawnWorker(port)
212
+ const previousAppDir = currentAppDir
213
+ const next = spawnWorker(port, nextAppDir)
158
214
  const outcome = await Promise.race([
159
215
  next.ready.then(() => 'ready' as const),
160
216
  next.proc.exited.then(() => 'exited' as const),
@@ -165,11 +221,22 @@ async function replaceServer(port: number): Promise<void> {
165
221
  await stopWorker(next.proc)
166
222
  }
167
223
  abideLog.warn('new server failed to boot — the previous one (if any) keeps serving')
224
+ // The replacement never served; its fresh generation dir is orphaned. Prune it,
225
+ // unless a server-only restart reused the still-serving incumbent's dir.
226
+ if (nextAppDir !== previousAppDir) {
227
+ await pruneGenDir(nextAppDir)
228
+ }
168
229
  return
169
230
  }
170
231
  server = next.proc
232
+ currentAppDir = nextAppDir
171
233
  if (previous) {
172
234
  await stopWorker(previous)
235
+ // The incumbent is gone; retire its generation dir — unless the replacement is
236
+ // serving that same dir (a server-only restart with no client rebuild).
237
+ if (previousAppDir !== nextAppDir) {
238
+ await pruneGenDir(previousAppDir)
239
+ }
173
240
  }
174
241
  }
175
242
 
@@ -200,17 +267,20 @@ async function rebuild(port: number, skipClientBuild = false): Promise<void> {
200
267
  }
201
268
  building = true
202
269
  try {
203
- const succeeded = skipClientBuild ? true : await build(buildOptions)
204
- if (succeeded) {
205
- await replaceServer(port)
270
+ /* A server/MCP-only change keeps the existing client bundle, so the replacement
271
+ worker reuses the incumbent's generation dir; a client rebuild produces a fresh
272
+ one. Either way the swap needs the dir the new worker should serve. */
273
+ const built = skipClientBuild ? { appDir: currentAppDir } : await build(buildOptions)
274
+ if (built) {
275
+ await replaceServer(port, built.appDir)
206
276
  }
207
277
  /* A server/MCP-only change reuses the existing client bundle. That's correct for
208
- an rpc/socket handler BODY edit, but changing an rpc's method, its export name,
209
- or `outbox: true` changes the client proxy stub too — the stale bundle then
210
- calls with the old shape (e.g. a 405). Flag the skip so that case isn't silent. */
278
+ an rpc/socket handler BODY edit, but changing an rpc's method or its export name
279
+ changes the client proxy stub too — the stale bundle then calls with the old shape
280
+ (e.g. a 405). Flag the skip so that case isn't silent. */
211
281
  if (skipClientBuild) {
212
282
  abideLog.info(
213
- 'server-only change — kept the existing client bundle; if you changed an rpc method, export name, or outbox, save a client file (or restart dev) for a full rebuild',
283
+ 'server-only change — kept the existing client bundle; if you changed an rpc method or export name, save a client file (or restart dev) for a full rebuild',
214
284
  )
215
285
  }
216
286
  } finally {
@@ -231,11 +301,23 @@ on the same predictable 3000+ address as `bun start`; reusing the number across
231
301
  restarts (not re-scanning) is what keeps the tab valid.
232
302
  */
233
303
  const port = findOpenPort(DEFAULT_PORT)
304
+ // Sweep generation dirs a prior session may have left behind before building afresh.
305
+ pruneStaleBuildDirs()
306
+ /* Announce the boot before the first build. build() stays silent under dev (its
307
+ "building…" log is gated so watch rebuilds don't spam), so without this line the
308
+ terminal shows nothing for the whole initial build — a cold boot reads as a hang.
309
+ One line up front: the server is alive, building, and where it will land. */
310
+ abideLog.info(`starting dev server at http://localhost:${port} — building client…`)
234
311
  const firstBuild = await build(buildOptions)
235
- if (!firstBuild) {
312
+ if (firstBuild) {
313
+ currentAppDir = firstBuild.appDir
314
+ } else {
315
+ // Leave currentAppDir at the `${cwd}/dist/_app` fallback; the worker will serve
316
+ // broken assets until a save produces a real generation, matching prior behaviour.
236
317
  abideLog.warn('initial build failed — fix the error and save to retry')
237
318
  }
238
- server = spawnWorker(port).proc
319
+ // The initial worker prints the surface map once (printSurface); respawns don't.
320
+ server = spawnWorker(port, currentAppDir, true).proc
239
321
 
240
322
  /*
241
323
  ABIDE_DEV_NO_WATCH=1 skips the fs watcher: rebuild only on demand via POST
@@ -268,7 +350,7 @@ const watcher = manualRebuild
268
350
  })
269
351
  if (manualRebuild) {
270
352
  abideLog.info(
271
- `manual rebuild mode — POST http://localhost:${port}/__abide/reload to apply changes`,
353
+ `manual rebuild mode — POST http://localhost:${port}${DEV_REBUILD_PATH} to apply changes`,
272
354
  )
273
355
  }
274
356
 
@@ -279,6 +361,9 @@ const shutdown = async () => {
279
361
  if (server) {
280
362
  await stopWorker(server)
281
363
  }
364
+ // Drop the active generation dir on the way out; any other leftover is swept on the
365
+ // next dev start by pruneStaleBuildDirs.
366
+ await pruneGenDir(currentAppDir)
282
367
  process.exit(0)
283
368
  }
284
369
  process.on('SIGINT', shutdown)
@@ -3,6 +3,7 @@ import { rpc } from './_virtual/rpc.ts'
3
3
  // @ts-expect-error virtual module resolved by abideResolverPlugin
4
4
  import { sockets } from './_virtual/sockets.ts'
5
5
  import type { CliManifestEntry } from './lib/cli/types/CliManifestEntry.ts'
6
+ import { resolveInputJsonSchema } from './lib/server/rpc/resolveRpcJsonSchema.ts'
6
7
  import { rpcRegistry } from './lib/server/rpc/rpcRegistry.ts'
7
8
  import { socketOperations } from './lib/server/sockets/socketOperations.ts'
8
9
  import { socketRegistry } from './lib/server/sockets/socketRegistry.ts'
@@ -24,13 +25,13 @@ await Promise.all([
24
25
  const manifest: Record<string, CliManifestEntry> = {}
25
26
 
26
27
  for (const entry of rpcRegistry.values()) {
27
- if (!entry.clients.cli) {
28
+ if (!entry.remote.clients.cli) {
28
29
  continue
29
30
  }
30
31
  manifest[commandNameForUrl(entry.remote.url)] = {
31
32
  method: entry.remote.method,
32
33
  url: entry.remote.url,
33
- jsonSchema: jsonSchemaForSchema(entry.inputSchema),
34
+ jsonSchema: resolveInputJsonSchema(entry) ?? jsonSchemaForSchema(undefined),
34
35
  }
35
36
  }
36
37