@abide/abide 0.49.0 → 0.50.1

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 +564 -437
  2. package/CHANGELOG.md +183 -0
  3. package/README.md +165 -202
  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,30 +1,32 @@
1
1
  // node:fs existsSync — Bun plugin onResolve is sync-only; Bun.file().exists() is async
2
2
  import { existsSync, statSync } from 'node:fs'
3
- import { dirname, join } from 'node:path'
4
3
  import type { BunPlugin } from 'bun'
5
4
  import { Glob } from 'bun'
6
5
  import { abideImportName } from './lib/shared/abideImportName.ts'
7
6
  import { abideLog } from './lib/shared/abideLog.ts'
7
+ import { bundleGraphFromMetafile } from './lib/shared/bundleGraphFromMetafile.ts'
8
+ import type { RpcServerProgram } from './lib/shared/createRpcServerProgram.ts'
8
9
  import { escapeRegex } from './lib/shared/escapeRegex.ts'
9
- import { fileName } from './lib/shared/fileName.ts'
10
10
  import { fileStem } from './lib/shared/fileStem.ts'
11
11
  import { jsonSchemaForPromptArguments } from './lib/shared/jsonSchemaForPromptArguments.ts'
12
12
  import { manifestModule } from './lib/shared/manifestModule.ts'
13
+ import { PROXIED_SERVER_SUBDIRS } from './lib/shared/PROXIED_SERVER_SUBDIRS.ts'
13
14
  import { pageUrlForFile } from './lib/shared/pageUrlForFile.ts'
14
15
  import { parsePromptMarkdown } from './lib/shared/parsePromptMarkdown.ts'
15
16
  import { prepareRpcModule } from './lib/shared/prepareRpcModule.ts'
16
17
  import { prepareSocketModule } from './lib/shared/prepareSocketModule.ts'
17
18
  import { programNameForPackage } from './lib/shared/programNameForPackage.ts'
18
19
  import { promptNameForFile } from './lib/shared/promptNameForFile.ts'
20
+ import {
21
+ DEFINE_RPC_GLOBAL,
22
+ DEFINE_SOCKET_GLOBAL,
23
+ REMOTE_PROXY_GLOBAL,
24
+ } from './lib/shared/RPC_SHIM_GLOBALS.ts'
19
25
  import { readPackageJson } from './lib/shared/readPackageJson.ts'
26
+ import { rpcServerForRoot } from './lib/shared/rpcServerForRoot.ts'
20
27
  import { rpcUrlForFile } from './lib/shared/rpcUrlForFile.ts'
28
+ import { scanPages } from './lib/shared/scanPages.ts'
21
29
  import { socketNameForFile } from './lib/shared/socketNameForFile.ts'
22
- import { writeHealthDts } from './lib/shared/writeHealthDts.ts'
23
- import { writePublicAssetsDts } from './lib/shared/writePublicAssetsDts.ts'
24
- import { writeRoutesDts } from './lib/shared/writeRoutesDts.ts'
25
- import { writeRpcDts } from './lib/shared/writeRpcDts.ts'
26
- import { writeTestRpcDts } from './lib/shared/writeTestRpcDts.ts'
27
- import { writeTestSocketsDts } from './lib/shared/writeTestSocketsDts.ts'
28
30
 
29
31
  /*
30
32
  Resolves a bare directory or extensionless path to a concrete file. Mirrors
@@ -68,6 +70,13 @@ function resolveExtensionUncached(path: string): string {
68
70
 
69
71
  const NS = 'abide-virtual'
70
72
 
73
+ /* Per-input source-byte budget for the client bundle-hygiene diagnostic (ADR-0031 D2): a single
74
+ project module that SURVIVES tree-shaking into the client bundle heavier than this earns a
75
+ non-blocking build warning, so an accidentally-huge module (an inlined dataset, a fat vendored
76
+ dep pulled into src) is observable without a new bundler pass. Never fails the build — the only
77
+ hard onEnd failure stays the real side-crossing violation. */
78
+ const CLIENT_BUNDLE_INPUT_BUDGET_BYTES = 512 * 1024
79
+
71
80
  /* Memoises a zero-arg async producer so repeat calls reuse the first in-flight promise. */
72
81
  function once<T>(produce: () => Promise<T>): () => Promise<T> {
73
82
  let promise: Promise<T> | undefined
@@ -144,57 +153,19 @@ export function abideResolverPlugin({
144
153
  re-globbing the trees. The shell read is memoised the same way so two
145
154
  passes don't re-read app.html from disk.
146
155
  */
147
- const scanPagesOnce = once(() =>
148
- scanPages(pagesDir).then(async (scan) => {
149
- await writeRoutesDts({
150
- cwd,
151
- pageFiles: scan.pageFiles,
152
- importName: await abideImportNameOnce(),
153
- })
154
- return scan
155
- }),
156
- )
157
- const scanRpcOnce = once(() =>
158
- scanDir(rpcDir, '**/*.ts').then(async (rpcFiles) => {
159
- const importName = await abideImportNameOnce()
160
- await writeRpcDts({ cwd, rpcDir, rpcFiles, importName })
161
- /* Typed createTestApp `app.rpc.<rpc>` surface. */
162
- await writeTestRpcDts({ cwd, rpcFiles, importName })
163
- return rpcFiles
164
- }),
165
- )
166
- const scanSocketsOnce = once(() =>
167
- scanDir(socketsDir, '**/*.ts').then(async (socketFiles) => {
168
- /* Typed createTestApp `app.sockets.<name>` surface. */
169
- await writeTestSocketsDts({
170
- cwd,
171
- socketFiles,
172
- importName: await abideImportNameOnce(),
173
- })
174
- return socketFiles
175
- }),
176
- )
177
- /* One write per build, from the abide:app loader (the seam that already knows whether src/app.ts exists). */
178
- let healthDtsWritten: Promise<void> | undefined
179
- const writeHealthDtsOnce = (hasAppModule: boolean): Promise<void> => {
180
- healthDtsWritten ??= abideImportNameOnce().then((importName) =>
181
- writeHealthDts({ cwd, hasAppModule, importName }),
182
- )
183
- return healthDtsWritten
184
- }
156
+ const scanPagesOnce = once(() => scanPages(pagesDir))
157
+ const scanRpcOnce = once(() => scanDir(rpcDir, '**/*.ts'))
158
+ const scanSocketsOnce = once(() => scanDir(socketsDir, '**/*.ts'))
185
159
  /*
186
- Globs public/ once per build and writes publicAssets.d.ts so url() can
187
- autocomplete known assetsindependent of embedding (runs in dev/start
188
- too, where the files are read off disk). The public-assets virtual reuses
189
- the returned list for its embed.
160
+ Globs public/ once per build; the public-assets virtual reuses the returned
161
+ list for its embed. (.d.ts codegen publicAssets.d.ts and its siblings
162
+ moved to generateDeclarations, owned by build().)
190
163
  */
191
- const scanPublicOnce = once(async () => {
192
- const publicFiles = existsSync(publicDir)
164
+ const scanPublicOnce = once(async () =>
165
+ existsSync(publicDir)
193
166
  ? await Array.fromAsync(new Glob('**/*').scan({ cwd: publicDir, onlyFiles: true }))
194
- : []
195
- await writePublicAssetsDts({ cwd, publicFiles, importName: await abideImportNameOnce() })
196
- return publicFiles
197
- })
167
+ : [],
168
+ )
198
169
  const scanPromptsOnce = once(() => scanDir(promptsDir, '**/*.md'))
199
170
  const loadShellOnce = once(() => loadShell(cwd))
200
171
  /* Project package.json read once per build — three virtuals (cli-name,
@@ -206,53 +177,46 @@ export function abideResolverPlugin({
206
177
  const promptsFilter = new RegExp(`^${escapeRegex(promptsDir)}/.*\\.md$`)
207
178
 
208
179
  /*
209
- Side-crossing guard (client target only). The client bundle must never pull a
210
- server-only name into the browser. The exception is a registered rpc/socket
211
- `$server/rpc/*` and `$server/sockets/*` are replaced with remoteProxy/socketProxy
212
- stubs by the onLoad hooks below so only OTHER paths under src/server/ (helpers,
213
- runtime/) and the public `abide/server/*` names are violations. `importerOf` records
214
- each resolved edge so a violation can show the import chain back to its client-side
215
- origin as evidence; it is reset per build in onStart.
180
+ Side-crossing guard (client target only). The client bundle must never ship server-only
181
+ code to the browser. A registered rpc/socket is the carve-out — `$server/rpc/*` and
182
+ `$server/sockets/*` are replaced with remoteProxy/socketProxy stubs by the onLoad hooks —
183
+ so a violation is a SURVIVING module under src/server/ that isn't one of those, or the
184
+ public `abide/server/*` names.
185
+
186
+ ADR-0022 D3 makes the project-server edge REACHABILITY-based rather than
187
+ presence-at-resolve: the D2 client rpc transform keeps the real module (handler elided),
188
+ so the elided handler's now-dead `$server/*` / `abide/server/*` imports are textually
189
+ present until tree-shaking removes them. A resolve-time throw would false-positive on
190
+ those. So the authority is a post-bundle `build.onEnd` pass that walks `metafile.inputs`
191
+ (the DCE-accurate graph) and flags any server-only module that SURVIVED. The one guard
192
+ that still fires at resolve is the public `abide/server/*` names reaching a genuine client
193
+ module — those live in node_modules, outside `serverDir`, so the metafile classifier can't
194
+ see them; the kept rpc module (proxied) is exempted so its dead handler imports don't
195
+ reject before DCE.
216
196
  */
217
- const importerOf = new Map<string, string>()
197
+ /* Absolute proxied-server dirs, derived from the shared PROXIED_SERVER_SUBDIRS authority so this
198
+ resolver and the dev orchestrator's changeAffectsClient classify the same set — adding a
199
+ proxied dir is one edit there, not a silent lockstep across both. */
200
+ const proxiedServerDirPrefixes = PROXIED_SERVER_SUBDIRS.map(
201
+ (subdir) => `${serverDir}/${subdir}/`,
202
+ )
218
203
  const isProxiedServerModule = (path: string): boolean =>
219
- path.startsWith(`${rpcDir}/`) || path.startsWith(`${socketsDir}/`)
204
+ proxiedServerDirPrefixes.some((prefix) => path.startsWith(prefix))
220
205
  const isServerOnlyModule = (path: string): boolean =>
221
206
  path.startsWith(`${serverDir}/`) && !isProxiedServerModule(path)
222
207
  const showPath = (path: string): string =>
223
208
  path.startsWith(`${cwd}/`) ? path.slice(cwd.length + 1) : path
224
- /* Walks the recorded edges from the offending importer back to a graph root (cycle-safe),
225
- formatting each module relative to cwd — the evidence a side-crossing throws. */
226
- function sideCrossingChain(leaf: string, importer: string): string {
227
- const chain = [leaf, importer]
228
- const seen = new Set([importer])
229
- let cursor = importer
230
- while (importerOf.has(cursor)) {
231
- cursor = importerOf.get(cursor) as string
232
- if (seen.has(cursor)) {
233
- break
234
- }
235
- seen.add(cursor)
236
- chain.push(cursor)
237
- }
238
- return chain.reverse().map(showPath).join('\n → ')
239
- }
240
- function throwSideCrossing(leaf: string, label: string, importer: string): never {
241
- throw new Error(
242
- `[abide] a client module imports the server-only name \`${label}\` — server code must not reach the browser bundle. Move it to src/shared/ or behind an RPC. Import chain:\n ${sideCrossingChain(leaf, importer)}`,
243
- )
244
- }
245
- /* Records the edge and, on the client target, rejects a non-server module reaching a
246
- server-only one. Shared by the relative-import and `$server` alias resolvers. */
247
- function recordAndGuard(resolved: string, label: string, importer: string | undefined): void {
248
- if (importer === undefined) {
249
- return
250
- }
251
- importerOf.set(resolved, importer)
252
- if (target === 'client' && isServerOnlyModule(resolved) && !isServerOnlyModule(importer)) {
253
- throwSideCrossing(resolved, label, importer)
254
- }
255
- }
209
+ /* Renders a root→offender absolute-path chain (from the bundle graph seam) as the cwd-relative
210
+ `a\n → b\n → c` evidence the onEnd diagnostics print — the same shape the resolve-time guard
211
+ gives. */
212
+ const formatChain = (chain: string[]): string => chain.map(showPath).join('\n → ')
213
+
214
+ /* Warm per-root rpc server program (ADR-0025 D1). Lives in the setup closure so one build
215
+ reuses a single `ts.Program` across every rpc transform (streaming/method queries);
216
+ built lazily on the first rpc onLoad (so a build with no rpc modules never pays for it) and
217
+ failing open to undefined, in which case each query stays on its char-scan/regex. (The
218
+ rpc.d.ts codegen builds its own program in generateDeclarations, owned by build().) */
219
+ const rpcServerByRoot = new Map<string, RpcServerProgram | undefined>()
256
220
 
257
221
  return {
258
222
  name: 'abide-resolver',
@@ -264,33 +228,74 @@ export function abideResolverPlugin({
264
228
  build, so a freshly-created file resolves instead of staying "not found".
265
229
  onStart is build-time only — absent in the runtime/preload plugin context. */
266
230
  build.onStart?.(() => {
267
- importerOf.clear()
268
231
  resolveExtensionCache.clear()
269
232
  })
270
233
 
271
234
  /*
272
- Relative-import edge recorder + side-crossing guard. Matches `./` and `../`
273
- specifiers, records the edge for the evidence chain, and (client target) throws
274
- when a non-server module reaches a server-only file. Returns undefined so normal
275
- resolution proceeds this only observes and, on violation, aborts.
235
+ Reachability-based side-crossing guard (client target, ADR-0022 D3). After the
236
+ bundle is built, `metafile.inputs` is the post-DCE module graph: a textually-imported
237
+ but tree-shaken module is ABSENT, a surviving one PRESENT. So walk it, flag any
238
+ server-only module that survived (the elided rpc handler's dead `$server/*` imports
239
+ are gone by now; a policy or page that LIVE-reaches server-only code is not), and on a
240
+ hit reconstruct the import chain from the graph's own edges. This is the sole authority
241
+ for the project-server edge — the resolve-time throw was relaxed so the kept rpc module
242
+ can carry its handler's imports until DCE. metafile.inputs keys are relative to
243
+ process.cwd(); edge `path`s are absolute — resolve both to absolute to match serverDir.
276
244
  */
277
- build.onResolve({ filter: /^\.\.?\// }, (args) => {
278
- if (args.importer) {
279
- const resolved = resolveExtension(join(dirname(args.importer), args.path))
280
- recordAndGuard(resolved, args.path, args.importer)
245
+ build.onEnd?.((result) => {
246
+ if (target !== 'client' || !result.metafile) {
247
+ return
248
+ }
249
+ /* The post-DCE graph, walked once (ADR-0031 D2). Both consumers below read it — the
250
+ side-crossing guard and the bundle-budget diagnostic — instead of each re-walking
251
+ the metafile. */
252
+ const graph = bundleGraphFromMetafile(result.metafile, process.cwd())
253
+
254
+ /* Side-crossing guard (ADR-0022 D3): the FIRST surviving server-only module is a real
255
+ violation — throw with its import chain. The sole hard onEnd failure. */
256
+ const offender = graph.modules.find((module) => isServerOnlyModule(module.path))
257
+ if (offender !== undefined) {
258
+ throw new Error(
259
+ `[abide] a client module reaches the server-only name \`${showPath(offender.path)}\` — server code must not reach the browser bundle. Move it to src/shared/ or behind an RPC. Import chain:\n ${formatChain(graph.importerChain(offender.path))}`,
260
+ )
261
+ }
262
+
263
+ /* Bundle-budget diagnostic (ADR-0031 D2), first consumer of the seam: a project src
264
+ module that survived DCE heavier than the budget earns a NON-BLOCKING warn with its
265
+ import chain — never fails the build. Scoped to files under cwd (excluding
266
+ node_modules) so the warning is about code the author owns and can act on. */
267
+ for (const module of graph.modules) {
268
+ if (
269
+ module.bytes > CLIENT_BUNDLE_INPUT_BUDGET_BYTES &&
270
+ module.path.startsWith(`${cwd}/`) &&
271
+ !module.path.includes('/node_modules/')
272
+ ) {
273
+ abideLog.warn(
274
+ `client bundle input \`${showPath(module.path)}\` is ${(module.bytes / 1024).toFixed(1)} KiB (over the ${CLIENT_BUNDLE_INPUT_BUDGET_BYTES / 1024} KiB budget) — consider splitting or lazy-loading it. Import chain:\n ${formatChain(graph.importerChain(module.path))}`,
275
+ )
276
+ }
281
277
  }
282
- return undefined
283
278
  })
284
279
 
285
280
  /*
286
- The public `abide/server/*` names are server-only. Importing one into the client
287
- bundle is a side-crossing (the canonical `abide` and `@abide/abide` specifiers; a
288
- custom package alias falls through to the relative/`$server` guards). The server
289
- target resolves these normally.
281
+ The public `abide/server/*` names are server-only and live in node_modules — outside
282
+ `serverDir`, so the onEnd metafile classifier can't see them; this stays the resolve-time
283
+ guard for them (the canonical `abide` and `@abide/abide` specifiers; a custom package
284
+ alias falls through to the reachability guard). A genuine client module importing one is a
285
+ side-crossing. The kept rpc/socket module (proxied) is EXEMPTED: the D2 client transform
286
+ leaves its elided handler's `abide/server/*` imports textually present until DCE drops
287
+ them, so rejecting here would false-positive. The server target resolves these normally.
290
288
  */
291
289
  build.onResolve({ filter: /(^|\/)abide\/server\// }, (args) => {
292
- if (target === 'client' && args.importer && !isServerOnlyModule(args.importer)) {
293
- throwSideCrossing(args.path, args.path, args.importer)
290
+ if (
291
+ target === 'client' &&
292
+ args.importer &&
293
+ !isServerOnlyModule(args.importer) &&
294
+ !isProxiedServerModule(args.importer)
295
+ ) {
296
+ throw new Error(
297
+ `[abide] a client module imports the server-only name \`${args.path}\` — server code must not reach the browser bundle. Move it to src/shared/ or behind an RPC. Import chain:\n ${showPath(args.importer)}\n → ${args.path}`,
298
+ )
294
299
  }
295
300
  return undefined
296
301
  })
@@ -325,9 +330,10 @@ export function abideResolverPlugin({
325
330
  build.onResolve({ filter: new RegExp(`^\\${alias}(\\/.*)?$`) }, (args) => {
326
331
  const subpath = args.path.slice(alias.length)
327
332
  const resolved = resolveExtension(subpath ? `${baseDir}${subpath}` : baseDir)
328
- /* `$server/rpc/*` + `$server/sockets/*` are proxied (allowed); other
329
- `$server/*` imports into the client bundle are side-crossings. */
330
- recordAndGuard(resolved, args.path, args.importer)
333
+ /* No resolve-time side-crossing throw: a `$server/*` import that only the
334
+ elided rpc handler reaches is dead code DCE removes. Whether server-only
335
+ code actually survives into the client bundle is decided post-bundle by the
336
+ onEnd metafile reachability guard (ADR-0022 D3). */
331
337
  return { path: resolved }
332
338
  })
333
339
  }
@@ -361,7 +367,50 @@ export function abideResolverPlugin({
361
367
  const source = await Bun.file(args.path).text()
362
368
  const url = rpcUrlForFile(relativePath)
363
369
  const importName = await abideImportNameOnce()
364
- const prepared = prepareRpcModule(source, importName)
370
+ /* Ask the warm server program for this handler's streaming verdict (return-type
371
+ query — a stream returned via a wrapper function is seen); undefined when no
372
+ program built or the node didn't resolve, so prepareRpcModule falls open to its
373
+ char-scan (ADR-0025 D2). */
374
+ const rpcServerProgram = rpcServerForRoot(rpcServerByRoot, cwd, rpcDir)
375
+ const streamingOverride = rpcServerProgram?.streamingForModule(args.path)
376
+ /* The numeric/boolean input fields the server rewrite stamps as a `coerce` plan so
377
+ parseArgs turns a string query/form value into the typed value the schema
378
+ expects (ADR-0028). undefined (no program / unresolvable Args / no coercible
379
+ field) stamps no plan — a GET field stays a string, exactly as today. */
380
+ const coercion = rpcServerProgram?.inputCoercionForModule(args.path)
381
+ /* The handler's return type projected to JSON Schema, stamped as `outputJsonSchema` so
382
+ the runtime OpenAPI 200 / MCP outputSchema / inspector surface renders the real
383
+ response shape without an author-declared `schemas.output` (ADR-0030 D2). undefined
384
+ (no program / unprojectable body / an explicit override wins at runtime) stamps
385
+ nothing — the surface falls back to today's behavior. */
386
+ const returnBodySchema = rpcServerProgram?.returnBodySchemaForModule(args.path)
387
+ /* The handler's typed-error branches projected to a status-keyed JSON-Schema map,
388
+ stamped as `errorJsonSchemas` so the runtime OpenAPI surface documents each typed
389
+ error's status + data payload alongside the 200 (ADR-0030). undefined (no program /
390
+ no typed errors / unresolvable branch) stamps nothing — the surface omits the error
391
+ responses exactly as today. */
392
+ const errorSchemas = rpcServerProgram?.errorSchemasForModule(args.path)
393
+ /* The handler's structured success fields, baked onto the CLIENT stub as
394
+ `outputWirePlan` so the proxy revives a `Set`/`Map`/`bigint`/`Date` off a decoded
395
+ response the server encoded to honest JSON (ADR-0029 output path). undefined (no
396
+ program / unresolvable body / no structured field) bakes nothing — a response array
397
+ stays an array. */
398
+ const outputWirePlan = rpcServerProgram?.outputWirePlanForModule(args.path)
399
+ /* The handler's input args projected to JSON Schema, stamped as `inputJsonSchema` so the
400
+ runtime OpenAPI parameters/body / MCP inputSchema / inspector input surface renders the
401
+ real input shape (and the endpoint becomes advertisable) without an author-declared
402
+ `schemas.input` — mirroring the `outputJsonSchema` output-side path (ADR-0030). A
403
+ declared `schemas.input` VALIDATOR still overrides it at runtime. undefined (no program
404
+ / unresolvable Args / File-only body) stamps nothing — the surface behaves as today. */
405
+ const inputSchema = rpcServerProgram?.inputSchemaForModule(args.path)
406
+ const prepared = prepareRpcModule(source, importName, {
407
+ streaming: streamingOverride,
408
+ coercion,
409
+ inputSchema,
410
+ outputSchema: returnBodySchema,
411
+ errorSchemas,
412
+ outputWirePlan,
413
+ })
365
414
  if (!prepared) {
366
415
  throw new Error(
367
416
  `[abide] src/server/rpc/${relativePath} has no \`export const <name> = <METHOD>(...)\` — every $rpc module must declare exactly one remote function`,
@@ -374,25 +423,22 @@ export function abideResolverPlugin({
374
423
  )
375
424
  }
376
425
  /*
377
- For the client bundle, replace the entire module source
378
- with a single proxy stub so the handler body and any
379
- server-only top-level imports never reach the browser.
380
- The stub keeps the same export name the source declared,
381
- so page imports resolve identically on both sides.
426
+ Client bundle: keep the real module but swap the METHOD( call for a remoteProxy(
427
+ call with the handler ELIDED (ADR-0022 D2), symmetric with the server rewrite
428
+ below. The handler body and the top-level imports only it used become dead code the
429
+ bundler tree-shakes out; the endpoint `opts` (schemas/cache/stream) rides through as
430
+ a LIVE expression, so policy can reference imports and separate modules. The
431
+ surviving-server-module reachability guard (build.onEnd) is the authority that no
432
+ server-only code leaks — resolve-time presence of the elided handler's imports is no
433
+ longer a violation.
382
434
  */
383
435
  if (target === 'client') {
384
- /* Build-time flags on the client proxy stub: `outbox: true` parks
385
- unreachable calls; `streaming: true` (handler returns jsonl()/sse())
386
- makes the bare call return the Subscribable directly. */
387
- const flags = [
388
- prepared.durable ? 'outbox: true' : undefined,
389
- prepared.streaming ? 'streaming: true' : undefined,
390
- ].filter(Boolean)
391
- const optsArg = flags.length > 0 ? `, { ${flags.join(', ')} }` : ''
392
- const contents = `import { remoteProxy as __abideRemoteProxy__ } from '${importName}/ui/remoteProxy';
393
- export const ${prepared.exportName} = __abideRemoteProxy__(${JSON.stringify(prepared.method)}, ${JSON.stringify(url)}${optsArg});
436
+ const banner = `import { remoteProxy as ${REMOTE_PROXY_GLOBAL} } from '${importName}/ui/remoteProxy';
394
437
  `
395
- return { contents, loader: 'ts' }
438
+ return {
439
+ contents: `${banner}${prepared.rewriteForClient(url)}`,
440
+ loader: 'ts',
441
+ }
396
442
  }
397
443
  /*
398
444
  Server target: strip the user's rpc import, then rewrite
@@ -404,7 +450,7 @@ export const ${prepared.exportName} = __abideRemoteProxy__(${JSON.stringify(prep
404
450
  tokenizer-driven so `GET` mentions inside strings and
405
451
  comments are left alone.
406
452
  */
407
- const banner = `import { defineRpc as __abideDefineRpc__ } from '${importName}/server/rpc/defineRpc';
453
+ const banner = `import { defineRpc as ${DEFINE_RPC_GLOBAL} } from '${importName}/server/rpc/defineRpc';
408
454
  `
409
455
  return { contents: `${banner}${prepared.rewriteForServer(url)}`, loader: 'ts' }
410
456
  })
@@ -440,7 +486,7 @@ export const ${prepared.exportName} = __abideSocketProxy__(${JSON.stringify(name
440
486
  `
441
487
  return { contents, loader: 'ts' }
442
488
  }
443
- const banner = `import { defineSocket as __abideDefineSocket__ } from '${importName}/server/sockets/defineSocket';
489
+ const banner = `import { defineSocket as ${DEFINE_SOCKET_GLOBAL} } from '${importName}/server/sockets/defineSocket';
444
490
  `
445
491
  return {
446
492
  contents: `${banner}${prepared.rewriteForServer(name)}`,
@@ -545,8 +591,6 @@ ${optionLines}
545
591
  if (args.path === 'abide:app') {
546
592
  const userApp = `${cwd}/src/app.ts`
547
593
  const hasAppModule = await Bun.file(userApp).exists()
548
- /* health.d.ts keys the client health() read to the app hook's return type. */
549
- await writeHealthDtsOnce(hasAppModule)
550
594
  if (hasAppModule) {
551
595
  abideLog.info('using custom src/app.ts')
552
596
  return {
@@ -882,30 +926,6 @@ ${encoded.map((entry) => entry.line).join('\n')}
882
926
  `
883
927
  }
884
928
 
885
- type PagesScan = {
886
- pageFiles: string[]
887
- layoutFiles: string[]
888
- }
889
-
890
- /*
891
- Walks src/ui/pages once and classifies each `.abide` leaf by filename: a
892
- `page.abide` is a route (its URL is the folder path), a `layout.abide` is a
893
- layout that wraps every page at or below its folder (keyed by the same folder
894
- URL). Any other `.abide` file (a shared component) is ignored here — free to
895
- live anywhere and be imported relatively.
896
- */
897
- async function scanPages(pagesDir: string): Promise<PagesScan> {
898
- if (!existsSync(pagesDir)) {
899
- return { pageFiles: [], layoutFiles: [] }
900
- }
901
- const allFiles = await Array.fromAsync(new Glob('**/*.abide').scan({ cwd: pagesDir }))
902
- const leafIs = (name: string) => (file: string) => fileName(file) === name
903
- return {
904
- pageFiles: allFiles.filter(leafIs('page.abide')),
905
- layoutFiles: allFiles.filter(leafIs('layout.abide')),
906
- }
907
- }
908
-
909
929
  /*
910
930
  Walks one registry directory once: src/server/rpc (every `.ts` file is an
911
931
  HTTP-method rpc handler), src/server/sockets (each `.ts` file declares one
@@ -1005,16 +1025,24 @@ function injectBeforeTag(
1005
1025
  }
1006
1026
 
1007
1027
  /*
1008
- Scans `dist/_app/` for the hashed client entry filenames produced by
1009
- build.ts (e.g. `client-abc12345.js`, `client-abc12345.css`) and swaps the
1010
- shell's literal `/_app/client.js` and `/_app/client.css` references for
1011
- them. The js reference appears twice (the modulepreload <link> and the entry
1012
- <script>), so replaceAll covers both. When the directory is missing (someone
1013
- running the server before a build) the shell is returned unchanged so the
1014
- existing broken-asset behaviour is preserved.
1028
+ Scans the client-bundle directory for the hashed client entry filenames produced
1029
+ by build.ts (e.g. `client-abc12345.js`, `client-abc12345.css`) and swaps the
1030
+ shell's literal `/_app/client.js` and `/_app/client.css` references for them. The
1031
+ js reference appears twice (the modulepreload <link> and the entry <script>), so
1032
+ replaceAll covers both. When the directory is missing (someone running the server
1033
+ before a build) the shell is returned unchanged so the existing broken-asset
1034
+ behaviour is preserved.
1035
+
1036
+ The directory is `dist/_app` for a production build, but under `abide dev` the
1037
+ orchestrator serves each build generation from its own `_app.gen-<id>` dir and
1038
+ passes it as `ABIDE_APP_DIR` (this runs in the worker, which resolves `abide:shell`
1039
+ at boot). Reading that env keeps the shell's entry ref, the preload manifest, and
1040
+ the asset server all pointed at the one generation this worker owns — never a
1041
+ sibling generation a concurrent worker is serving. Each generation dir holds
1042
+ exactly one entry, so `.find` is unambiguous.
1015
1043
  */
1016
1044
  async function rewriteHashedClientEntries(shell: string, cwd: string): Promise<string> {
1017
- const appDir = `${cwd}/dist/_app`
1045
+ const appDir = process.env.ABIDE_APP_DIR ?? `${cwd}/dist/_app`
1018
1046
  if (!existsSync(appDir)) {
1019
1047
  return shell
1020
1048
  }