@mysten-incubation/devstack 0.1.0 → 0.2.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 (858) hide show
  1. package/README.md +7 -4
  2. package/dashboard-ui/assets/grpc-a4usE3Nk.js +3 -0
  3. package/dashboard-ui/assets/index-B82Bh84P.css +1 -0
  4. package/dashboard-ui/assets/index-CRYJ4pru.js +1277 -0
  5. package/dashboard-ui/index.html +2 -2
  6. package/dist/api/define-capabilities.d.mts +1 -20
  7. package/dist/api/define-capabilities.mjs +15 -22
  8. package/dist/api/define-capabilities.mjs.map +1 -1
  9. package/dist/api/define-devstack-with.d.mts +7 -1
  10. package/dist/api/define-devstack-with.mjs.map +1 -1
  11. package/dist/api/define-devstack.mjs.map +1 -1
  12. package/dist/api/define-plugin.d.mts +2 -1
  13. package/dist/api/inference-network.d.mts +31 -17
  14. package/dist/api/inference-network.mjs +40 -43
  15. package/dist/api/inference-network.mjs.map +1 -1
  16. package/dist/api/load-config.mjs +88 -0
  17. package/dist/api/load-config.mjs.map +1 -0
  18. package/dist/api/mode-narrowed-factory.d.mts +1 -1
  19. package/dist/api/mode-narrowed-factory.mjs.map +1 -1
  20. package/dist/api/run-stack-internal.mjs +202 -0
  21. package/dist/api/run-stack-internal.mjs.map +1 -0
  22. package/dist/api/run-stack.d.mts +82 -31
  23. package/dist/api/run-stack.mjs +22 -141
  24. package/dist/api/run-stack.mjs.map +1 -1
  25. package/dist/build-integrations/dapp-kit/index.d.mts +61 -0
  26. package/dist/build-integrations/dapp-kit/index.mjs +58 -0
  27. package/dist/build-integrations/dapp-kit/index.mjs.map +1 -0
  28. package/dist/build-integrations/playwright/config.d.mts +1 -30
  29. package/dist/build-integrations/playwright/config.mjs +1 -27
  30. package/dist/build-integrations/playwright/config.mjs.map +1 -1
  31. package/dist/build-integrations/playwright/errors.d.mts +4 -7
  32. package/dist/build-integrations/playwright/errors.mjs.map +1 -1
  33. package/dist/build-integrations/playwright/global-setup.d.mts +42 -71
  34. package/dist/build-integrations/playwright/global-setup.mjs +142 -218
  35. package/dist/build-integrations/playwright/global-setup.mjs.map +1 -1
  36. package/dist/build-integrations/playwright/index.d.mts +2 -2
  37. package/dist/build-integrations/playwright/index.mjs +2 -2
  38. package/dist/build-integrations/playwright/stack-context.d.mts +3 -1
  39. package/dist/build-integrations/playwright/stack-context.mjs +4 -2
  40. package/dist/build-integrations/playwright/stack-context.mjs.map +1 -1
  41. package/dist/build-integrations/playwright/wallet-context.mjs +44 -19
  42. package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
  43. package/dist/build-integrations/runtime/cold-start-url.mjs.map +1 -1
  44. package/dist/build-integrations/runtime/conventional-routes.mjs.map +1 -1
  45. package/dist/build-integrations/runtime/dapp-kit-slot.mjs.map +1 -1
  46. package/dist/build-integrations/runtime/discover.d.mts +6 -4
  47. package/dist/build-integrations/runtime/discover.mjs +15 -4
  48. package/dist/build-integrations/runtime/discover.mjs.map +1 -1
  49. package/dist/build-integrations/runtime/endpoint-registry.mjs.map +1 -1
  50. package/dist/build-integrations/runtime/errors.mjs.map +1 -1
  51. package/dist/build-integrations/runtime/read-stack-context.mjs +3 -3
  52. package/dist/build-integrations/runtime/read-stack-context.mjs.map +1 -1
  53. package/dist/build-integrations/runtime/resolve-discovery-env.d.mts +23 -7
  54. package/dist/build-integrations/runtime/resolve-discovery-env.mjs +25 -12
  55. package/dist/build-integrations/runtime/resolve-discovery-env.mjs.map +1 -1
  56. package/dist/build-integrations/runtime/stack-context.d.mts +2 -2
  57. package/dist/build-integrations/vite/index.d.mts +84 -19
  58. package/dist/build-integrations/vite/index.mjs +203 -28
  59. package/dist/build-integrations/vite/index.mjs.map +1 -1
  60. package/dist/build-integrations/vitest/config.d.mts +20 -2
  61. package/dist/build-integrations/vitest/config.mjs +16 -4
  62. package/dist/build-integrations/vitest/config.mjs.map +1 -1
  63. package/dist/build-integrations/vitest/env.d.mts +18 -10
  64. package/dist/build-integrations/vitest/env.mjs +11 -10
  65. package/dist/build-integrations/vitest/env.mjs.map +1 -1
  66. package/dist/build-integrations/vitest/errors.d.mts +7 -11
  67. package/dist/build-integrations/vitest/errors.mjs +3 -4
  68. package/dist/build-integrations/vitest/errors.mjs.map +1 -1
  69. package/dist/build-integrations/vitest/global-setup.d.mts +35 -0
  70. package/dist/build-integrations/vitest/global-setup.mjs +156 -0
  71. package/dist/build-integrations/vitest/global-setup.mjs.map +1 -0
  72. package/dist/build-integrations/vitest/index.d.mts +3 -2
  73. package/dist/build-integrations/vitest/index.mjs +3 -2
  74. package/dist/build-integrations/vitest/setup.mjs +2 -2
  75. package/dist/build-integrations/vitest/setup.mjs.map +1 -1
  76. package/dist/build-integrations/vitest/stack-context.d.mts +5 -3
  77. package/dist/build-integrations/vitest/stack-context.mjs +3 -2
  78. package/dist/build-integrations/vitest/stack-context.mjs.map +1 -1
  79. package/dist/cli/bail.mjs.map +1 -1
  80. package/dist/cli/doctor-probes.mjs +1 -3
  81. package/dist/cli/doctor-probes.mjs.map +1 -1
  82. package/dist/cli/main.d.mts +23 -1
  83. package/dist/cli/main.mjs +63 -8
  84. package/dist/cli/main.mjs.map +1 -1
  85. package/dist/cli/prune-direct.mjs.map +1 -1
  86. package/dist/cli/snapshot-reader.mjs.map +1 -1
  87. package/dist/cli/up-lifecycle.mjs.map +1 -1
  88. package/dist/cli/wirings/apply.mjs +28 -21
  89. package/dist/cli/wirings/apply.mjs.map +1 -1
  90. package/dist/cli/wirings/codegen.mjs +108 -0
  91. package/dist/cli/wirings/codegen.mjs.map +1 -0
  92. package/dist/cli/wirings/config-loader.mjs +12 -62
  93. package/dist/cli/wirings/config-loader.mjs.map +1 -1
  94. package/dist/cli/wirings/dump-ids.mjs +86 -0
  95. package/dist/cli/wirings/dump-ids.mjs.map +1 -0
  96. package/dist/cli/wirings/engine-command.mjs +1 -1
  97. package/dist/cli/wirings/engine-command.mjs.map +1 -1
  98. package/dist/cli/wirings/identity.mjs +2 -2
  99. package/dist/cli/wirings/identity.mjs.map +1 -1
  100. package/dist/cli/wirings/provide-file-system.mjs.map +1 -1
  101. package/dist/cli/wirings/read-devstack-version.mjs +17 -0
  102. package/dist/cli/wirings/read-devstack-version.mjs.map +1 -0
  103. package/dist/cli/wirings/snapshot.mjs +27 -34
  104. package/dist/cli/wirings/snapshot.mjs.map +1 -1
  105. package/dist/cli/wirings/up-ipc.mjs +147 -0
  106. package/dist/cli/wirings/up-ipc.mjs.map +1 -0
  107. package/dist/cli/wirings/up.mjs +218 -198
  108. package/dist/cli/wirings/up.mjs.map +1 -1
  109. package/dist/cli/wirings/wipe.mjs +1 -1
  110. package/dist/cli/wirings/wipe.mjs.map +1 -1
  111. package/dist/contracts/chain-probe.d.mts +1 -1
  112. package/dist/contracts/chain-probe.mjs +1 -1
  113. package/dist/contracts/chain-probe.mjs.map +1 -1
  114. package/dist/contracts/codegenable.d.mts +60 -6
  115. package/dist/contracts/codegenable.mjs +28 -0
  116. package/dist/contracts/codegenable.mjs.map +1 -0
  117. package/dist/contracts/config-bindings.mjs +162 -0
  118. package/dist/contracts/config-bindings.mjs.map +1 -0
  119. package/dist/contracts/container-runtime.d.mts +23 -17
  120. package/dist/contracts/plugin-expander.mjs.map +1 -1
  121. package/dist/contracts/snapshotable.d.mts +5 -7
  122. package/dist/contracts/wallet-protocol.d.mts +2 -5
  123. package/dist/contracts/wallet-protocol.mjs +2 -5
  124. package/dist/contracts/wallet-protocol.mjs.map +1 -1
  125. package/dist/index.d.mts +21 -26
  126. package/dist/index.mjs +8 -9
  127. package/dist/orchestrators/boot.d.mts +28 -0
  128. package/dist/orchestrators/boot.mjs +440 -0
  129. package/dist/orchestrators/boot.mjs.map +1 -0
  130. package/dist/orchestrators/codegen/bindings.d.mts +13 -1
  131. package/dist/orchestrators/codegen/bindings.mjs +23 -9
  132. package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
  133. package/dist/orchestrators/codegen/config-runtime.mjs +208 -0
  134. package/dist/orchestrators/codegen/config-runtime.mjs.map +1 -0
  135. package/dist/orchestrators/codegen/emit.mjs +1 -1
  136. package/dist/orchestrators/codegen/emit.mjs.map +1 -1
  137. package/dist/orchestrators/codegen/errors.mjs +21 -1
  138. package/dist/orchestrators/codegen/errors.mjs.map +1 -1
  139. package/dist/orchestrators/codegen/format.mjs +2 -0
  140. package/dist/orchestrators/codegen/format.mjs.map +1 -1
  141. package/dist/orchestrators/codegen/gitignore.mjs +30 -11
  142. package/dist/orchestrators/codegen/gitignore.mjs.map +1 -1
  143. package/dist/orchestrators/codegen/id-config.mjs +107 -0
  144. package/dist/orchestrators/codegen/id-config.mjs.map +1 -0
  145. package/dist/orchestrators/codegen/output-location.mjs +23 -31
  146. package/dist/orchestrators/codegen/output-location.mjs.map +1 -1
  147. package/dist/orchestrators/codegen/paths.mjs +8 -1
  148. package/dist/orchestrators/codegen/paths.mjs.map +1 -1
  149. package/dist/orchestrators/codegen/permissions.mjs.map +1 -1
  150. package/dist/orchestrators/codegen/service.mjs +286 -58
  151. package/dist/orchestrators/codegen/service.mjs.map +1 -1
  152. package/dist/orchestrators/layers.mjs +31 -0
  153. package/dist/orchestrators/layers.mjs.map +1 -0
  154. package/dist/orchestrators/lifecycle-prune/errors.mjs.map +1 -1
  155. package/dist/orchestrators/lifecycle-prune/index.mjs +8 -10
  156. package/dist/orchestrators/lifecycle-prune/index.mjs.map +1 -1
  157. package/dist/orchestrators/network-options.d.mts +52 -0
  158. package/dist/orchestrators/network-options.mjs +43 -0
  159. package/dist/orchestrators/network-options.mjs.map +1 -0
  160. package/dist/orchestrators/router/cleanup.mjs.map +1 -1
  161. package/dist/orchestrators/router/cors.mjs.map +1 -1
  162. package/dist/orchestrators/router/entrypoints.mjs.map +1 -1
  163. package/dist/orchestrators/router/errors.mjs.map +1 -1
  164. package/dist/orchestrators/router/file-provider.mjs.map +1 -1
  165. package/dist/orchestrators/router/hostname.mjs.map +1 -1
  166. package/dist/orchestrators/router/profile.mjs +1 -1
  167. package/dist/orchestrators/router/profile.mjs.map +1 -1
  168. package/dist/orchestrators/router/service.d.mts +2 -2
  169. package/dist/orchestrators/router/service.mjs +12 -27
  170. package/dist/orchestrators/router/service.mjs.map +1 -1
  171. package/dist/orchestrators/router/traefik-container.mjs +1 -1
  172. package/dist/orchestrators/router/traefik-container.mjs.map +1 -1
  173. package/dist/orchestrators/snapshot/capture.mjs +171 -195
  174. package/dist/orchestrators/snapshot/capture.mjs.map +1 -1
  175. package/dist/orchestrators/snapshot/descriptor.d.mts +1 -1
  176. package/dist/orchestrators/snapshot/descriptor.mjs +53 -11
  177. package/dist/orchestrators/snapshot/descriptor.mjs.map +1 -1
  178. package/dist/orchestrators/snapshot/identity-guard.mjs +3 -5
  179. package/dist/orchestrators/snapshot/identity-guard.mjs.map +1 -1
  180. package/dist/orchestrators/snapshot/image-bundle-tags.mjs +32 -101
  181. package/dist/orchestrators/snapshot/image-bundle-tags.mjs.map +1 -1
  182. package/dist/orchestrators/snapshot/index.mjs +1 -4
  183. package/dist/orchestrators/snapshot/integrity.mjs.map +1 -1
  184. package/dist/orchestrators/snapshot/interrupted-restore.mjs +116 -0
  185. package/dist/orchestrators/snapshot/interrupted-restore.mjs.map +1 -0
  186. package/dist/orchestrators/snapshot/phase-error.mjs.map +1 -1
  187. package/dist/orchestrators/snapshot/prune.mjs +40 -31
  188. package/dist/orchestrators/snapshot/prune.mjs.map +1 -1
  189. package/dist/orchestrators/snapshot/restore.mjs +221 -196
  190. package/dist/orchestrators/snapshot/restore.mjs.map +1 -1
  191. package/dist/orchestrators/snapshot/service.mjs +72 -62
  192. package/dist/orchestrators/snapshot/service.mjs.map +1 -1
  193. package/dist/orchestrators/snapshot/wipe.mjs +67 -56
  194. package/dist/orchestrators/snapshot/wipe.mjs.map +1 -1
  195. package/dist/plugins/account/codegen.mjs +3 -0
  196. package/dist/plugins/account/codegen.mjs.map +1 -1
  197. package/dist/plugins/account/errors.d.mts +2 -2
  198. package/dist/plugins/account/errors.mjs +1 -4
  199. package/dist/plugins/account/errors.mjs.map +1 -1
  200. package/dist/plugins/account/funding.mjs +3 -27
  201. package/dist/plugins/account/funding.mjs.map +1 -1
  202. package/dist/plugins/account/index.d.mts +3 -8
  203. package/dist/plugins/account/index.mjs +71 -35
  204. package/dist/plugins/account/index.mjs.map +1 -1
  205. package/dist/plugins/account/keypair.d.mts +8 -7
  206. package/dist/plugins/account/keypair.mjs +5 -18
  207. package/dist/plugins/account/keypair.mjs.map +1 -1
  208. package/dist/plugins/account/lease.mjs.map +1 -1
  209. package/dist/plugins/account/registry.d.mts +1 -46
  210. package/dist/plugins/account/registry.mjs.map +1 -1
  211. package/dist/plugins/account/service.d.mts +43 -34
  212. package/dist/plugins/account/service.mjs +9 -54
  213. package/dist/plugins/account/service.mjs.map +1 -1
  214. package/dist/plugins/account/snapshot.mjs.map +1 -1
  215. package/dist/plugins/account/variants/ephemeral.mjs.map +1 -1
  216. package/dist/plugins/account/variants/impersonate.mjs.map +1 -1
  217. package/dist/plugins/account/variants/signer.mjs.map +1 -1
  218. package/dist/plugins/action/discriminator.mjs.map +1 -1
  219. package/dist/plugins/action/errors.mjs +1 -4
  220. package/dist/plugins/action/errors.mjs.map +1 -1
  221. package/dist/plugins/action/execute.mjs +3 -4
  222. package/dist/plugins/action/execute.mjs.map +1 -1
  223. package/dist/plugins/action/index.d.mts +2 -2
  224. package/dist/plugins/action/index.mjs +7 -11
  225. package/dist/plugins/action/index.mjs.map +1 -1
  226. package/dist/plugins/action/service.d.mts +5 -7
  227. package/dist/plugins/action/service.mjs +2 -16
  228. package/dist/plugins/action/service.mjs.map +1 -1
  229. package/dist/plugins/coin/address-resolution.mjs.map +1 -1
  230. package/dist/plugins/coin/codegen.mjs +97 -16
  231. package/dist/plugins/coin/codegen.mjs.map +1 -1
  232. package/dist/plugins/coin/discovery.mjs.map +1 -1
  233. package/dist/plugins/coin/errors.mjs +1 -4
  234. package/dist/plugins/coin/errors.mjs.map +1 -1
  235. package/dist/plugins/coin/index.d.mts +4 -34
  236. package/dist/plugins/coin/index.mjs +47 -28
  237. package/dist/plugins/coin/index.mjs.map +1 -1
  238. package/dist/plugins/coin/metadata.d.mts +1 -0
  239. package/dist/plugins/coin/metadata.mjs +8 -9
  240. package/dist/plugins/coin/metadata.mjs.map +1 -1
  241. package/dist/plugins/coin/mint.mjs +7 -21
  242. package/dist/plugins/coin/mint.mjs.map +1 -1
  243. package/dist/plugins/coin/registry.mjs +33 -20
  244. package/dist/plugins/coin/registry.mjs.map +1 -1
  245. package/dist/plugins/coin/service.d.mts +1 -1
  246. package/dist/plugins/coin/service.mjs +1 -8
  247. package/dist/plugins/coin/service.mjs.map +1 -1
  248. package/dist/plugins/coin/snapshot.mjs.map +1 -1
  249. package/dist/plugins/coin/type-strings.mjs.map +1 -1
  250. package/dist/plugins/dashboard/domain.mjs +106 -167
  251. package/dist/plugins/dashboard/domain.mjs.map +1 -1
  252. package/dist/plugins/dashboard/index.mjs +13 -13
  253. package/dist/plugins/dashboard/index.mjs.map +1 -1
  254. package/dist/plugins/dashboard/origin-policy.mjs +3 -3
  255. package/dist/plugins/dashboard/origin-policy.mjs.map +1 -1
  256. package/dist/plugins/dashboard/routable.mjs.map +1 -1
  257. package/dist/plugins/dashboard/schema/builder.mjs.map +1 -1
  258. package/dist/plugins/dashboard/schema/enums.mjs.map +1 -1
  259. package/dist/plugins/dashboard/schema/root.mjs +23 -44
  260. package/dist/plugins/dashboard/schema/root.mjs.map +1 -1
  261. package/dist/plugins/dashboard/schema/types.mjs +37 -91
  262. package/dist/plugins/dashboard/schema/types.mjs.map +1 -1
  263. package/dist/plugins/dashboard/schema.mjs.map +1 -1
  264. package/dist/plugins/dashboard/server.mjs.map +1 -1
  265. package/dist/plugins/deepbook/codegen.d.mts +12 -11
  266. package/dist/plugins/deepbook/codegen.mjs +108 -11
  267. package/dist/plugins/deepbook/codegen.mjs.map +1 -1
  268. package/dist/plugins/deepbook/deploy.mjs +7 -14
  269. package/dist/plugins/deepbook/deploy.mjs.map +1 -1
  270. package/dist/plugins/deepbook/errors.mjs +4 -2
  271. package/dist/plugins/deepbook/errors.mjs.map +1 -1
  272. package/dist/plugins/deepbook/faucet-strategy.mjs +4 -10
  273. package/dist/plugins/deepbook/faucet-strategy.mjs.map +1 -1
  274. package/dist/plugins/deepbook/hash.mjs.map +1 -1
  275. package/dist/plugins/deepbook/index.d.mts +38 -34
  276. package/dist/plugins/deepbook/index.mjs +133 -64
  277. package/dist/plugins/deepbook/index.mjs.map +1 -1
  278. package/dist/plugins/deepbook/plugin-key.mjs.map +1 -1
  279. package/dist/plugins/deepbook/pyth/index.mjs +4 -8
  280. package/dist/plugins/deepbook/pyth/index.mjs.map +1 -1
  281. package/dist/plugins/deepbook/snapshot.mjs +0 -1
  282. package/dist/plugins/deepbook/snapshot.mjs.map +1 -1
  283. package/dist/plugins/deepbook/type-strings.mjs.map +1 -1
  284. package/dist/plugins/deepbook/types.d.mts +2 -1
  285. package/dist/plugins/deepbook/types.mjs.map +1 -1
  286. package/dist/plugins/faucet/dispatcher.d.mts +3 -3
  287. package/dist/plugins/faucet/dispatcher.mjs.map +1 -1
  288. package/dist/plugins/faucet/errors.mjs.map +1 -1
  289. package/dist/plugins/faucet/http.mjs +2 -10
  290. package/dist/plugins/faucet/http.mjs.map +1 -1
  291. package/dist/plugins/faucet/index.mjs +0 -1
  292. package/dist/plugins/faucet/index.mjs.map +1 -1
  293. package/dist/plugins/host-service/errors.d.mts +1 -3
  294. package/dist/plugins/host-service/errors.mjs +1 -2
  295. package/dist/plugins/host-service/errors.mjs.map +1 -1
  296. package/dist/plugins/host-service/index.d.mts +1 -2
  297. package/dist/plugins/host-service/index.mjs +11 -11
  298. package/dist/plugins/host-service/index.mjs.map +1 -1
  299. package/dist/plugins/host-service/routable.mjs.map +1 -1
  300. package/dist/plugins/host-service/service.mjs +13 -13
  301. package/dist/plugins/host-service/service.mjs.map +1 -1
  302. package/dist/plugins/internal/acquire-on-chain-artifact.mjs.map +1 -1
  303. package/dist/plugins/internal/codegen-helpers.mjs +2 -0
  304. package/dist/plugins/internal/codegen-helpers.mjs.map +1 -1
  305. package/dist/plugins/internal/funding-failure-error.mjs.map +1 -1
  306. package/dist/plugins/{postgres → internal/postgres-sidecar}/connection.mjs +1 -1
  307. package/dist/plugins/internal/postgres-sidecar/connection.mjs.map +1 -0
  308. package/dist/plugins/{postgres → internal/postgres-sidecar}/db-ensure.mjs +30 -16
  309. package/dist/plugins/internal/postgres-sidecar/db-ensure.mjs.map +1 -0
  310. package/dist/plugins/internal/postgres-sidecar/errors.mjs +21 -0
  311. package/dist/plugins/internal/postgres-sidecar/errors.mjs.map +1 -0
  312. package/dist/{substrate/runtime/scoped-multimap → plugins/internal/postgres-sidecar}/index.mjs +1 -0
  313. package/dist/plugins/internal/postgres-sidecar/service.mjs +121 -0
  314. package/dist/plugins/internal/postgres-sidecar/service.mjs.map +1 -0
  315. package/dist/plugins/package/build.mjs +2 -2
  316. package/dist/plugins/package/build.mjs.map +1 -1
  317. package/dist/plugins/package/codegen.d.mts +7 -14
  318. package/dist/plugins/package/codegen.mjs +187 -59
  319. package/dist/plugins/package/codegen.mjs.map +1 -1
  320. package/dist/plugins/package/dep-resolution.mjs +51 -3
  321. package/dist/plugins/package/dep-resolution.mjs.map +1 -1
  322. package/dist/plugins/package/errors.mjs +1 -4
  323. package/dist/plugins/package/errors.mjs.map +1 -1
  324. package/dist/plugins/package/git-source.d.mts +18 -0
  325. package/dist/plugins/package/git-source.mjs +119 -0
  326. package/dist/plugins/package/git-source.mjs.map +1 -0
  327. package/dist/plugins/package/index.d.mts +28 -19
  328. package/dist/plugins/package/index.mjs +107 -32
  329. package/dist/plugins/package/index.mjs.map +1 -1
  330. package/dist/plugins/package/mode-known.mjs +2 -2
  331. package/dist/plugins/package/mode-known.mjs.map +1 -1
  332. package/dist/plugins/package/mode-local.mjs +18 -35
  333. package/dist/plugins/package/mode-local.mjs.map +1 -1
  334. package/dist/plugins/package/publish-executor.mjs +13 -8
  335. package/dist/plugins/package/publish-executor.mjs.map +1 -1
  336. package/dist/plugins/package/publish-output.d.mts +1 -11
  337. package/dist/plugins/package/publish-output.mjs +1 -9
  338. package/dist/plugins/package/publish-output.mjs.map +1 -1
  339. package/dist/plugins/package/registry.d.mts +1 -1
  340. package/dist/plugins/package/registry.mjs +26 -14
  341. package/dist/plugins/package/registry.mjs.map +1 -1
  342. package/dist/plugins/package/service.mjs.map +1 -1
  343. package/dist/plugins/package/snapshot.mjs.map +1 -1
  344. package/dist/plugins/router-entrypoints.mjs +1 -3
  345. package/dist/plugins/router-entrypoints.mjs.map +1 -1
  346. package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs +1 -2
  347. package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs.map +1 -1
  348. package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs +1 -6
  349. package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs.map +1 -1
  350. package/dist/plugins/seal/codegen.d.mts +1 -5
  351. package/dist/plugins/seal/codegen.mjs +83 -12
  352. package/dist/plugins/seal/codegen.mjs.map +1 -1
  353. package/dist/plugins/seal/config-render.mjs +2 -6
  354. package/dist/plugins/seal/config-render.mjs.map +1 -1
  355. package/dist/plugins/seal/deploy.mjs +18 -29
  356. package/dist/plugins/seal/deploy.mjs.map +1 -1
  357. package/dist/plugins/seal/errors.d.mts +4 -5
  358. package/dist/plugins/seal/errors.mjs +1 -8
  359. package/dist/plugins/seal/errors.mjs.map +1 -1
  360. package/dist/plugins/seal/index.d.mts +7 -28
  361. package/dist/plugins/seal/index.mjs +69 -47
  362. package/dist/plugins/seal/index.mjs.map +1 -1
  363. package/dist/plugins/seal/key-manager.mjs.map +1 -1
  364. package/dist/plugins/seal/key-server.mjs +1 -6
  365. package/dist/plugins/seal/key-server.mjs.map +1 -1
  366. package/dist/plugins/seal/keygen.mjs +2 -3
  367. package/dist/plugins/seal/keygen.mjs.map +1 -1
  368. package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
  369. package/dist/plugins/seal/mode/live.mjs +1 -1
  370. package/dist/plugins/seal/mode/live.mjs.map +1 -1
  371. package/dist/plugins/seal/mode/local-keygen.mjs +4 -10
  372. package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
  373. package/dist/plugins/seal/plugin-key.mjs.map +1 -1
  374. package/dist/plugins/seal/registry-publish.mjs.map +1 -1
  375. package/dist/plugins/seal/routable.mjs.map +1 -1
  376. package/dist/plugins/seal/service.mjs.map +1 -1
  377. package/dist/plugins/seal/snapshot.mjs +1 -2
  378. package/dist/plugins/seal/snapshot.mjs.map +1 -1
  379. package/dist/plugins/sui/auto-tick.mjs +5 -5
  380. package/dist/plugins/sui/auto-tick.mjs.map +1 -1
  381. package/dist/plugins/sui/chain-build-container.mjs +2 -2
  382. package/dist/plugins/sui/chain-build-container.mjs.map +1 -1
  383. package/dist/plugins/sui/chain-probe.mjs +4 -4
  384. package/dist/plugins/sui/chain-probe.mjs.map +1 -1
  385. package/dist/plugins/sui/codegen.d.mts +9 -7
  386. package/dist/plugins/sui/codegen.mjs +49 -46
  387. package/dist/plugins/sui/codegen.mjs.map +1 -1
  388. package/dist/plugins/sui/errors.d.mts +1 -1
  389. package/dist/plugins/sui/errors.mjs +1 -9
  390. package/dist/plugins/sui/errors.mjs.map +1 -1
  391. package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/index.d.mts +1 -1
  392. package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/index.mjs +3 -7
  393. package/dist/plugins/sui/exec/index.mjs.map +1 -0
  394. package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/sign-and-dispatch.mjs +1 -1
  395. package/dist/plugins/sui/exec/sign-and-dispatch.mjs.map +1 -0
  396. package/dist/plugins/sui/fork-faucet-strategy.mjs +2 -6
  397. package/dist/plugins/sui/fork-faucet-strategy.mjs.map +1 -1
  398. package/dist/plugins/sui/fork-orchestration.mjs +13 -23
  399. package/dist/plugins/sui/fork-orchestration.mjs.map +1 -1
  400. package/dist/plugins/sui/fork-transaction.mjs.map +1 -1
  401. package/dist/plugins/sui/index.d.mts +34 -83
  402. package/dist/plugins/sui/index.mjs +200 -68
  403. package/dist/plugins/sui/index.mjs.map +1 -1
  404. package/dist/{substrate/runtime/sui-ledger → plugins/sui/ledger}/object-ref.mjs +1 -1
  405. package/dist/plugins/sui/ledger/object-ref.mjs.map +1 -0
  406. package/dist/plugins/sui/local-faucet-strategy.mjs +1 -5
  407. package/dist/plugins/sui/local-faucet-strategy.mjs.map +1 -1
  408. package/dist/plugins/sui/log-attrs.mjs +11 -0
  409. package/dist/plugins/sui/log-attrs.mjs.map +1 -0
  410. package/dist/plugins/sui/mode/external.mjs +4 -12
  411. package/dist/plugins/sui/mode/external.mjs.map +1 -1
  412. package/dist/plugins/sui/mode/fork.mjs +19 -16
  413. package/dist/plugins/sui/mode/fork.mjs.map +1 -1
  414. package/dist/plugins/sui/mode/live.mjs +4 -17
  415. package/dist/plugins/sui/mode/live.mjs.map +1 -1
  416. package/dist/plugins/sui/mode/local.d.mts +1 -0
  417. package/dist/plugins/sui/mode/local.mjs +203 -61
  418. package/dist/plugins/sui/mode/local.mjs.map +1 -1
  419. package/dist/plugins/sui/mode/shared-boot.mjs +18 -23
  420. package/dist/plugins/sui/mode/shared-boot.mjs.map +1 -1
  421. package/dist/plugins/sui/mode/shared.d.mts +6 -7
  422. package/dist/plugins/sui/mode/shared.mjs.map +1 -1
  423. package/dist/plugins/sui/mode/spec.d.mts +23 -6
  424. package/dist/{substrate/runtime/sui-move-build → plugins/sui/move}/index.mjs +28 -30
  425. package/dist/plugins/sui/move/index.mjs.map +1 -0
  426. package/dist/plugins/sui/move-summary-runner.mjs +8 -4
  427. package/dist/plugins/sui/move-summary-runner.mjs.map +1 -1
  428. package/dist/{substrate/network.d.mts → plugins/sui/network-config.d.mts} +11 -10
  429. package/dist/plugins/sui/network-resolver.d.mts +6 -6
  430. package/dist/plugins/sui/routable.mjs.map +1 -1
  431. package/dist/plugins/sui/service.mjs +10 -6
  432. package/dist/plugins/sui/service.mjs.map +1 -1
  433. package/dist/plugins/sui/snapshot.mjs +10 -5
  434. package/dist/plugins/sui/snapshot.mjs.map +1 -1
  435. package/dist/plugins/wallet/codegen.d.mts +8 -6
  436. package/dist/plugins/wallet/codegen.mjs +4 -10
  437. package/dist/plugins/wallet/codegen.mjs.map +1 -1
  438. package/dist/plugins/wallet/errors.mjs +1 -4
  439. package/dist/plugins/wallet/errors.mjs.map +1 -1
  440. package/dist/plugins/wallet/index.d.mts +3 -6
  441. package/dist/plugins/wallet/index.mjs +16 -21
  442. package/dist/plugins/wallet/index.mjs.map +1 -1
  443. package/dist/plugins/wallet/origin-policy.mjs +3 -3
  444. package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
  445. package/dist/plugins/wallet/pairing.mjs +6 -24
  446. package/dist/plugins/wallet/pairing.mjs.map +1 -1
  447. package/dist/plugins/wallet/protocol.mjs.map +1 -1
  448. package/dist/plugins/wallet/routable.mjs.map +1 -1
  449. package/dist/plugins/wallet/server.mjs +17 -24
  450. package/dist/plugins/wallet/server.mjs.map +1 -1
  451. package/dist/plugins/wallet/service.d.mts +2 -2
  452. package/dist/plugins/wallet/service.mjs +4 -15
  453. package/dist/plugins/wallet/service.mjs.map +1 -1
  454. package/dist/plugins/wallet/snapshot.mjs.map +1 -1
  455. package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs +1 -5
  456. package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs.map +1 -1
  457. package/dist/plugins/walrus/codegen.d.mts +2 -13
  458. package/dist/plugins/walrus/codegen.mjs +93 -28
  459. package/dist/plugins/walrus/codegen.mjs.map +1 -1
  460. package/dist/plugins/walrus/deploy-paths.mjs +2 -3
  461. package/dist/plugins/walrus/deploy-paths.mjs.map +1 -1
  462. package/dist/plugins/walrus/deploy.mjs +26 -19
  463. package/dist/plugins/walrus/deploy.mjs.map +1 -1
  464. package/dist/plugins/walrus/errors.mjs +1 -8
  465. package/dist/plugins/walrus/errors.mjs.map +1 -1
  466. package/dist/plugins/walrus/faucet-strategy.mjs +15 -1
  467. package/dist/plugins/walrus/faucet-strategy.mjs.map +1 -1
  468. package/dist/plugins/walrus/index.d.mts +13 -44
  469. package/dist/plugins/walrus/index.mjs +119 -122
  470. package/dist/plugins/walrus/index.mjs.map +1 -1
  471. package/dist/plugins/walrus/mode/known-deploy.mjs +6 -6
  472. package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
  473. package/dist/plugins/walrus/mode/local-cluster.d.mts +2 -2
  474. package/dist/plugins/walrus/mode/local-cluster.mjs +2 -10
  475. package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
  476. package/dist/plugins/walrus/plugin-key.mjs.map +1 -1
  477. package/dist/plugins/walrus/registry-publish.mjs.map +1 -1
  478. package/dist/plugins/walrus/routable.mjs.map +1 -1
  479. package/dist/plugins/walrus/service.mjs.map +1 -1
  480. package/dist/plugins/walrus/snapshot.mjs +3 -3
  481. package/dist/plugins/walrus/snapshot.mjs.map +1 -1
  482. package/dist/plugins/walrus/storage-nodes.mjs +53 -14
  483. package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
  484. package/dist/plugins/walrus/wal-swap.mjs +4 -8
  485. package/dist/plugins/walrus/wal-swap.mjs.map +1 -1
  486. package/dist/primitives/artifact-publisher.mjs.map +1 -1
  487. package/dist/primitives/cache.d.mts +1 -1
  488. package/dist/runtime/docker/client.mjs +47 -3
  489. package/dist/runtime/docker/client.mjs.map +1 -1
  490. package/dist/runtime/docker/container.mjs +37 -57
  491. package/dist/runtime/docker/container.mjs.map +1 -1
  492. package/dist/runtime/docker/errors.mjs.map +1 -1
  493. package/dist/runtime/docker/exec.mjs +2 -2
  494. package/dist/runtime/docker/exec.mjs.map +1 -1
  495. package/dist/runtime/docker/image.mjs +10 -11
  496. package/dist/runtime/docker/image.mjs.map +1 -1
  497. package/dist/runtime/docker/index.mjs +0 -1
  498. package/dist/runtime/docker/inspect-and-decode.mjs.map +1 -1
  499. package/dist/runtime/docker/inventory.mjs +9 -9
  500. package/dist/runtime/docker/inventory.mjs.map +1 -1
  501. package/dist/runtime/docker/labels.mjs +12 -1
  502. package/dist/runtime/docker/labels.mjs.map +1 -1
  503. package/dist/runtime/docker/network.mjs +6 -6
  504. package/dist/runtime/docker/network.mjs.map +1 -1
  505. package/dist/runtime/docker/render-run-args.mjs.map +1 -1
  506. package/dist/runtime/docker/service.mjs +31 -45
  507. package/dist/runtime/docker/service.mjs.map +1 -1
  508. package/dist/runtime/docker/sweep.mjs +14 -64
  509. package/dist/runtime/docker/sweep.mjs.map +1 -1
  510. package/dist/runtime/docker/volume.mjs +1 -1
  511. package/dist/runtime/docker/volume.mjs.map +1 -1
  512. package/dist/runtime/docker/wrap.mjs +14 -2
  513. package/dist/runtime/docker/wrap.mjs.map +1 -1
  514. package/dist/substrate/brand.d.mts +2 -5
  515. package/dist/substrate/brand.mjs +3 -2
  516. package/dist/substrate/brand.mjs.map +1 -1
  517. package/dist/substrate/cross-process.mjs +2 -16
  518. package/dist/substrate/cross-process.mjs.map +1 -1
  519. package/dist/substrate/event-time.mjs +0 -2
  520. package/dist/substrate/event-time.mjs.map +1 -1
  521. package/dist/substrate/events.d.mts +46 -16
  522. package/dist/substrate/identity.d.mts +11 -5
  523. package/dist/substrate/manifest.d.mts +33 -26
  524. package/dist/substrate/manifest.mjs +8 -3
  525. package/dist/substrate/manifest.mjs.map +1 -1
  526. package/dist/substrate/options.d.mts +33 -4
  527. package/dist/substrate/plugin-ctx.d.mts +70 -0
  528. package/dist/substrate/plugin-ctx.mjs +66 -0
  529. package/dist/substrate/plugin-ctx.mjs.map +1 -0
  530. package/dist/substrate/plugin.d.mts +46 -38
  531. package/dist/substrate/plugin.mjs +13 -9
  532. package/dist/substrate/plugin.mjs.map +1 -1
  533. package/dist/substrate/projection.d.mts +1 -1
  534. package/dist/substrate/runtime/atomic-write.mjs +3 -4
  535. package/dist/substrate/runtime/atomic-write.mjs.map +1 -1
  536. package/dist/substrate/runtime/cache/schema.mjs.map +1 -1
  537. package/dist/substrate/runtime/cache/service.mjs +50 -20
  538. package/dist/substrate/runtime/cache/service.mjs.map +1 -1
  539. package/dist/substrate/runtime/config-validation.mjs.map +1 -1
  540. package/dist/substrate/runtime/control-plane/domain.mjs +34 -12
  541. package/dist/substrate/runtime/control-plane/domain.mjs.map +1 -1
  542. package/dist/substrate/runtime/control-plane/service.mjs.map +1 -1
  543. package/dist/substrate/runtime/cross-process/command-channel/channel.mjs +14 -17
  544. package/dist/substrate/runtime/cross-process/command-channel/channel.mjs.map +1 -1
  545. package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs.map +1 -1
  546. package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs.map +1 -1
  547. package/dist/substrate/runtime/cross-process/command-channel/runtime-control-lock.mjs.map +1 -1
  548. package/dist/substrate/runtime/cross-process/index.mjs +0 -2
  549. package/dist/substrate/runtime/cross-process/live-clock.mjs +1 -1
  550. package/dist/substrate/runtime/cross-process/live-clock.mjs.map +1 -1
  551. package/dist/substrate/runtime/cross-process/liveness.mjs +11 -18
  552. package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
  553. package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs +2 -2
  554. package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs.map +1 -1
  555. package/dist/substrate/runtime/cross-process/roster.mjs +17 -127
  556. package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
  557. package/dist/substrate/runtime/cross-process/self-pid.mjs.map +1 -1
  558. package/dist/substrate/runtime/cross-process/stack-lock.mjs +29 -28
  559. package/dist/substrate/runtime/cross-process/stack-lock.mjs.map +1 -1
  560. package/dist/substrate/runtime/current-plugin.mjs.map +1 -1
  561. package/dist/substrate/runtime/errors.mjs +1 -13
  562. package/dist/substrate/runtime/errors.mjs.map +1 -1
  563. package/dist/substrate/runtime/format-unknown-error.mjs.map +1 -1
  564. package/dist/substrate/runtime/host-bind-mount-owner.mjs.map +1 -1
  565. package/dist/substrate/runtime/host-gateway.mjs.map +1 -1
  566. package/dist/substrate/runtime/host-tree-tar/index.mjs +15 -163
  567. package/dist/substrate/runtime/host-tree-tar/index.mjs.map +1 -1
  568. package/dist/substrate/runtime/http-probe.d.mts +1 -3
  569. package/dist/substrate/runtime/http-probe.mjs.map +1 -1
  570. package/dist/substrate/runtime/index.mjs +1 -6
  571. package/dist/substrate/runtime/lease-broker/service.mjs +5 -23
  572. package/dist/substrate/runtime/lease-broker/service.mjs.map +1 -1
  573. package/dist/substrate/runtime/lifecycle/dep-graph.mjs +3 -3
  574. package/dist/substrate/runtime/lifecycle/dep-graph.mjs.map +1 -1
  575. package/dist/substrate/runtime/lifecycle/graph-input-id.d.mts +1 -0
  576. package/dist/substrate/runtime/lifecycle/graph-input-id.mjs +133 -0
  577. package/dist/substrate/runtime/lifecycle/graph-input-id.mjs.map +1 -0
  578. package/dist/substrate/runtime/lifecycle/index.mjs +2 -2
  579. package/dist/substrate/runtime/lifecycle/plugin-registry.d.mts +1 -1
  580. package/dist/substrate/runtime/lifecycle/plugin-registry.mjs +22 -1
  581. package/dist/substrate/runtime/lifecycle/plugin-registry.mjs.map +1 -1
  582. package/dist/substrate/runtime/lifecycle/ready-gate.mjs +2 -15
  583. package/dist/substrate/runtime/lifecycle/ready-gate.mjs.map +1 -1
  584. package/dist/substrate/runtime/lifecycle/selective-restart.mjs +29 -25
  585. package/dist/substrate/runtime/lifecycle/selective-restart.mjs.map +1 -1
  586. package/dist/substrate/runtime/lifecycle/signals.mjs +1 -1
  587. package/dist/substrate/runtime/lifecycle/signals.mjs.map +1 -1
  588. package/dist/substrate/runtime/lifecycle/state-machine.mjs.map +1 -1
  589. package/dist/substrate/runtime/lifecycle/watch-attribution.d.mts +1 -1
  590. package/dist/substrate/runtime/lifecycle/watch-attribution.mjs +0 -1
  591. package/dist/substrate/runtime/lifecycle/watch-attribution.mjs.map +1 -1
  592. package/dist/substrate/runtime/managed-container.d.mts +5 -1
  593. package/dist/substrate/runtime/managed-container.mjs +8 -10
  594. package/dist/substrate/runtime/managed-container.mjs.map +1 -1
  595. package/dist/substrate/runtime/manifest/manifest.mjs +8 -30
  596. package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
  597. package/dist/substrate/runtime/mode-errors.d.mts +1 -2
  598. package/dist/substrate/runtime/mode-errors.mjs.map +1 -1
  599. package/dist/substrate/runtime/observability/cascade-formatter.mjs +1 -11
  600. package/dist/substrate/runtime/observability/cascade-formatter.mjs.map +1 -1
  601. package/dist/substrate/runtime/observability/ignore-with-log.mjs.map +1 -1
  602. package/dist/substrate/runtime/observability/index.d.mts +1 -1
  603. package/dist/substrate/runtime/observability/index.mjs +4 -6
  604. package/dist/substrate/runtime/observability/log-attrs.mjs +46 -0
  605. package/dist/substrate/runtime/observability/log-attrs.mjs.map +1 -0
  606. package/dist/substrate/runtime/observability/log-store.mjs.map +1 -1
  607. package/dist/substrate/runtime/observability/logger.mjs +3 -3
  608. package/dist/substrate/runtime/observability/logger.mjs.map +1 -1
  609. package/dist/substrate/runtime/observability/output-truncate.mjs.map +1 -1
  610. package/dist/substrate/runtime/observability/pretty-error.mjs.map +1 -1
  611. package/dist/substrate/runtime/observability/process-lines.mjs.map +1 -1
  612. package/dist/substrate/runtime/observability/redaction.mjs.map +1 -1
  613. package/dist/substrate/runtime/observability/subprocess-capture.mjs +2 -2
  614. package/dist/substrate/runtime/observability/subprocess-capture.mjs.map +1 -1
  615. package/dist/substrate/runtime/passthrough-or-wrap.mjs.map +1 -1
  616. package/dist/substrate/runtime/paths.mjs +27 -9
  617. package/dist/substrate/runtime/paths.mjs.map +1 -1
  618. package/dist/substrate/runtime/phase-preserving-produce.mjs.map +1 -1
  619. package/dist/substrate/runtime/port-broker/service.mjs +10 -27
  620. package/dist/substrate/runtime/port-broker/service.mjs.map +1 -1
  621. package/dist/substrate/runtime/post-acquire-tasks.mjs.map +1 -1
  622. package/dist/substrate/runtime/probes.d.mts +2 -3
  623. package/dist/substrate/runtime/probes.mjs.map +1 -1
  624. package/dist/substrate/runtime/process-supervisor.mjs.map +1 -1
  625. package/dist/substrate/runtime/projection/operational-endpoints.mjs.map +1 -1
  626. package/dist/substrate/runtime/projection/state-ref.mjs.map +1 -1
  627. package/dist/substrate/runtime/projection/update.mjs +87 -11
  628. package/dist/substrate/runtime/projection/update.mjs.map +1 -1
  629. package/dist/substrate/runtime/random-suffix.mjs.map +1 -1
  630. package/dist/substrate/runtime/reconcile/fs-plan.mjs +146 -0
  631. package/dist/substrate/runtime/reconcile/fs-plan.mjs.map +1 -0
  632. package/dist/substrate/runtime/reconcile/graph.mjs +19 -0
  633. package/dist/substrate/runtime/reconcile/graph.mjs.map +1 -0
  634. package/dist/substrate/runtime/reconcile/index.mjs +5 -0
  635. package/dist/substrate/runtime/reconcile/label.mjs +41 -0
  636. package/dist/substrate/runtime/reconcile/label.mjs.map +1 -0
  637. package/dist/substrate/runtime/reconcile/spec.mjs +15 -0
  638. package/dist/substrate/runtime/reconcile/spec.mjs.map +1 -0
  639. package/dist/substrate/runtime/retry-policy.d.mts +2 -3
  640. package/dist/substrate/runtime/retry-policy.mjs.map +1 -1
  641. package/dist/substrate/runtime/routed-url.mjs.map +1 -1
  642. package/dist/substrate/runtime/runtime-decode.mjs.map +1 -1
  643. package/dist/substrate/runtime/scoped-http-server.mjs.map +1 -1
  644. package/dist/substrate/runtime/stage-and-swap/index.mjs +49 -12
  645. package/dist/substrate/runtime/stage-and-swap/index.mjs.map +1 -1
  646. package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs.map +1 -1
  647. package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs.map +1 -1
  648. package/dist/substrate/runtime/strategy-registry/service.mjs +29 -17
  649. package/dist/substrate/runtime/strategy-registry/service.mjs.map +1 -1
  650. package/dist/substrate/runtime/subnet-broker.mjs.map +1 -1
  651. package/dist/substrate/runtime/supervisor/acquire-node.mjs +188 -56
  652. package/dist/substrate/runtime/supervisor/acquire-node.mjs.map +1 -1
  653. package/dist/substrate/runtime/supervisor/background-tasks.mjs +79 -65
  654. package/dist/substrate/runtime/supervisor/background-tasks.mjs.map +1 -1
  655. package/dist/substrate/runtime/supervisor/command-loop.mjs +82 -23
  656. package/dist/substrate/runtime/supervisor/command-loop.mjs.map +1 -1
  657. package/dist/substrate/runtime/supervisor/contribution-dispatcher.mjs +16 -0
  658. package/dist/substrate/runtime/supervisor/contribution-dispatcher.mjs.map +1 -0
  659. package/dist/substrate/runtime/supervisor/errors.mjs +11 -2
  660. package/dist/substrate/runtime/supervisor/errors.mjs.map +1 -1
  661. package/dist/substrate/runtime/supervisor/index.mjs +2 -0
  662. package/dist/substrate/runtime/supervisor/shutdown.mjs +6 -6
  663. package/dist/substrate/runtime/supervisor/shutdown.mjs.map +1 -1
  664. package/dist/substrate/runtime/supervisor/start-supervisor.mjs +60 -70
  665. package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -1
  666. package/dist/substrate/runtime/supervisor/state.d.mts +1 -1
  667. package/dist/substrate/runtime/supervisor/state.mjs +1 -1
  668. package/dist/substrate/runtime/supervisor/state.mjs.map +1 -1
  669. package/dist/substrate/runtime/supervisor/teardown.mjs +10 -9
  670. package/dist/substrate/runtime/supervisor/teardown.mjs.map +1 -1
  671. package/dist/substrate/runtime/supervisor/wiring.mjs +6 -20
  672. package/dist/substrate/runtime/supervisor/wiring.mjs.map +1 -1
  673. package/dist/substrate/runtime/tar/reader.mjs +216 -0
  674. package/dist/substrate/runtime/tar/reader.mjs.map +1 -0
  675. package/dist/substrate/runtime/typed-env.mjs.map +1 -1
  676. package/dist/substrate/versioned-doc-schema.mjs.map +1 -1
  677. package/dist/substrate/versioned-doc-sync.mjs +5 -5
  678. package/dist/substrate/versioned-doc-sync.mjs.map +1 -1
  679. package/dist/surfaces/cli/command-tree.mjs +49 -7
  680. package/dist/surfaces/cli/command-tree.mjs.map +1 -1
  681. package/dist/surfaces/cli/commands/config.mjs +1 -1
  682. package/dist/surfaces/cli/commands/config.mjs.map +1 -1
  683. package/dist/surfaces/cli/commands/confirm-node.mjs.map +1 -1
  684. package/dist/surfaces/cli/commands/confirm.mjs.map +1 -1
  685. package/dist/surfaces/cli/commands/doctor.mjs +1 -1
  686. package/dist/surfaces/cli/commands/doctor.mjs.map +1 -1
  687. package/dist/surfaces/cli/commands/prune-picker-entry.mjs.map +1 -1
  688. package/dist/surfaces/cli/commands/prune-picker.mjs.map +1 -1
  689. package/dist/surfaces/cli/commands/prune.mjs +1 -1
  690. package/dist/surfaces/cli/commands/prune.mjs.map +1 -1
  691. package/dist/surfaces/cli/commands/snapshot.mjs +1 -1
  692. package/dist/surfaces/cli/commands/snapshot.mjs.map +1 -1
  693. package/dist/surfaces/cli/commands/status.mjs +1 -1
  694. package/dist/surfaces/cli/commands/status.mjs.map +1 -1
  695. package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
  696. package/dist/surfaces/cli/commands/wipe.mjs +3 -4
  697. package/dist/surfaces/cli/commands/wipe.mjs.map +1 -1
  698. package/dist/surfaces/cli/envelope.mjs.map +1 -1
  699. package/dist/surfaces/cli/errors.mjs.map +1 -1
  700. package/dist/surfaces/cli/flags.mjs.map +1 -1
  701. package/dist/surfaces/cli/index.mjs +45 -14
  702. package/dist/surfaces/cli/index.mjs.map +1 -1
  703. package/dist/surfaces/cli/output.mjs.map +1 -1
  704. package/dist/surfaces/cli/sysexits.mjs.map +1 -1
  705. package/dist/surfaces/tui/app.mjs +0 -21
  706. package/dist/surfaces/tui/app.mjs.map +1 -1
  707. package/dist/surfaces/tui/dashboard.mjs +0 -48
  708. package/dist/surfaces/tui/dashboard.mjs.map +1 -1
  709. package/dist/surfaces/tui/display-derivation.mjs +80 -16
  710. package/dist/surfaces/tui/display-derivation.mjs.map +1 -1
  711. package/dist/surfaces/tui/errors.mjs.map +1 -1
  712. package/dist/surfaces/tui/event-log.mjs +0 -9
  713. package/dist/surfaces/tui/event-log.mjs.map +1 -1
  714. package/dist/surfaces/tui/heartbeat.mjs.map +1 -1
  715. package/dist/surfaces/tui/index.mjs +2 -2
  716. package/dist/surfaces/tui/index.mjs.map +1 -1
  717. package/dist/surfaces/tui/input.mjs.map +1 -1
  718. package/dist/surfaces/tui/mode-detect.mjs.map +1 -1
  719. package/dist/surfaces/tui/mount-ink.mjs.map +1 -1
  720. package/dist/surfaces/tui/plain-renderer.mjs +26 -19
  721. package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
  722. package/dist/surfaces/tui/resource-table.mjs +8 -10
  723. package/dist/surfaces/tui/resource-table.mjs.map +1 -1
  724. package/images/postgres/Dockerfile +0 -1
  725. package/images/sui/Dockerfile +27 -54
  726. package/images/sui/entrypoint.sh +17 -179
  727. package/package.json +23 -15
  728. package/dashboard-ui/assets/index-Bmi1UtAg.js +0 -1356
  729. package/dashboard-ui/assets/index-D5EShVt4.js +0 -3
  730. package/dashboard-ui/assets/index-Deml9drg.css +0 -1
  731. package/dist/api/plugin-errors.d.mts +0 -7
  732. package/dist/api/plugin-errors.mjs +0 -10
  733. package/dist/api/plugin-errors.mjs.map +0 -1
  734. package/dist/cli/wirings/build-verb-layers.mjs +0 -42
  735. package/dist/cli/wirings/build-verb-layers.mjs.map +0 -1
  736. package/dist/contracts/capability-decl.d.mts +0 -40
  737. package/dist/orchestrators/built-in-plugin-layers.mjs +0 -54
  738. package/dist/orchestrators/built-in-plugin-layers.mjs.map +0 -1
  739. package/dist/orchestrators/run.mjs +0 -91
  740. package/dist/orchestrators/run.mjs.map +0 -1
  741. package/dist/orchestrators/runtime-composition.d.mts +0 -10
  742. package/dist/orchestrators/runtime-composition.mjs +0 -252
  743. package/dist/orchestrators/runtime-composition.mjs.map +0 -1
  744. package/dist/orchestrators/snapshot/capture-command.d.mts +0 -1
  745. package/dist/orchestrators/snapshot/capture-command.mjs +0 -25
  746. package/dist/orchestrators/snapshot/capture-command.mjs.map +0 -1
  747. package/dist/orchestrators/snapshot/pending-marker.mjs +0 -99
  748. package/dist/orchestrators/snapshot/pending-marker.mjs.map +0 -1
  749. package/dist/orchestrators/snapshot/recover-pending.mjs +0 -223
  750. package/dist/orchestrators/snapshot/recover-pending.mjs.map +0 -1
  751. package/dist/orchestrators/snapshot/state-document.d.mts +0 -1
  752. package/dist/orchestrators/snapshot/state-document.mjs +0 -58
  753. package/dist/orchestrators/snapshot/state-document.mjs.map +0 -1
  754. package/dist/plugins/account/spans.mjs +0 -16
  755. package/dist/plugins/account/spans.mjs.map +0 -1
  756. package/dist/plugins/account/variants/env.mjs +0 -24
  757. package/dist/plugins/account/variants/env.mjs.map +0 -1
  758. package/dist/plugins/account/variants/inline.mjs +0 -13
  759. package/dist/plugins/account/variants/inline.mjs.map +0 -1
  760. package/dist/plugins/account/variants/keystore.mjs +0 -91
  761. package/dist/plugins/account/variants/keystore.mjs.map +0 -1
  762. package/dist/plugins/action/spans.mjs +0 -11
  763. package/dist/plugins/action/spans.mjs.map +0 -1
  764. package/dist/plugins/coin/spans.mjs +0 -20
  765. package/dist/plugins/coin/spans.mjs.map +0 -1
  766. package/dist/plugins/deepbook/spans.mjs +0 -18
  767. package/dist/plugins/deepbook/spans.mjs.map +0 -1
  768. package/dist/plugins/faucet/spans.mjs +0 -12
  769. package/dist/plugins/faucet/spans.mjs.map +0 -1
  770. package/dist/plugins/package/spans.mjs +0 -14
  771. package/dist/plugins/package/spans.mjs.map +0 -1
  772. package/dist/plugins/postgres/codegen.mjs +0 -42
  773. package/dist/plugins/postgres/codegen.mjs.map +0 -1
  774. package/dist/plugins/postgres/connection.d.mts +0 -33
  775. package/dist/plugins/postgres/connection.mjs.map +0 -1
  776. package/dist/plugins/postgres/db-ensure.mjs.map +0 -1
  777. package/dist/plugins/postgres/errors.d.mts +0 -60
  778. package/dist/plugins/postgres/errors.mjs +0 -29
  779. package/dist/plugins/postgres/errors.mjs.map +0 -1
  780. package/dist/plugins/postgres/index.d.mts +0 -37
  781. package/dist/plugins/postgres/index.mjs +0 -68
  782. package/dist/plugins/postgres/index.mjs.map +0 -1
  783. package/dist/plugins/postgres/routable.mjs +0 -33
  784. package/dist/plugins/postgres/routable.mjs.map +0 -1
  785. package/dist/plugins/postgres/service.d.mts +0 -50
  786. package/dist/plugins/postgres/service.mjs +0 -179
  787. package/dist/plugins/postgres/service.mjs.map +0 -1
  788. package/dist/plugins/postgres/snapshot.mjs +0 -32
  789. package/dist/plugins/postgres/snapshot.mjs.map +0 -1
  790. package/dist/plugins/postgres/spans.mjs +0 -11
  791. package/dist/plugins/postgres/spans.mjs.map +0 -1
  792. package/dist/plugins/seal/spans.mjs +0 -18
  793. package/dist/plugins/seal/spans.mjs.map +0 -1
  794. package/dist/plugins/sui/spans.mjs +0 -17
  795. package/dist/plugins/sui/spans.mjs.map +0 -1
  796. package/dist/plugins/wallet/spans.mjs +0 -22
  797. package/dist/plugins/wallet/spans.mjs.map +0 -1
  798. package/dist/plugins/walrus/registry-publish.d.mts +0 -24
  799. package/dist/plugins/walrus/spans.mjs +0 -18
  800. package/dist/plugins/walrus/spans.mjs.map +0 -1
  801. package/dist/runtime/docker/logs.d.mts +0 -1
  802. package/dist/runtime/docker/logs.mjs +0 -34
  803. package/dist/runtime/docker/logs.mjs.map +0 -1
  804. package/dist/substrate/runtime/artifact-publisher/index.mjs +0 -86
  805. package/dist/substrate/runtime/artifact-publisher/index.mjs.map +0 -1
  806. package/dist/substrate/runtime/capability-sinks/index.d.mts +0 -1
  807. package/dist/substrate/runtime/capability-sinks/index.mjs +0 -3
  808. package/dist/substrate/runtime/capability-sinks/layer.d.mts +0 -1
  809. package/dist/substrate/runtime/capability-sinks/layer.mjs +0 -31
  810. package/dist/substrate/runtime/capability-sinks/layer.mjs.map +0 -1
  811. package/dist/substrate/runtime/capability-sinks/service.d.mts +0 -95
  812. package/dist/substrate/runtime/capability-sinks/service.mjs +0 -69
  813. package/dist/substrate/runtime/capability-sinks/service.mjs.map +0 -1
  814. package/dist/substrate/runtime/cross-process/lock.d.mts +0 -1
  815. package/dist/substrate/runtime/cross-process/lock.mjs +0 -23
  816. package/dist/substrate/runtime/cross-process/lock.mjs.map +0 -1
  817. package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs +0 -113
  818. package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs.map +0 -1
  819. package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs +0 -31
  820. package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs.map +0 -1
  821. package/dist/substrate/runtime/observability/formatter-registry.d.mts +0 -1
  822. package/dist/substrate/runtime/observability/formatter-registry.mjs +0 -48
  823. package/dist/substrate/runtime/observability/formatter-registry.mjs.map +0 -1
  824. package/dist/substrate/runtime/observability/span-store.d.mts +0 -1
  825. package/dist/substrate/runtime/observability/span-store.mjs +0 -110
  826. package/dist/substrate/runtime/observability/span-store.mjs.map +0 -1
  827. package/dist/substrate/runtime/observability/spans.d.mts +0 -1
  828. package/dist/substrate/runtime/observability/spans.mjs +0 -87
  829. package/dist/substrate/runtime/observability/spans.mjs.map +0 -1
  830. package/dist/substrate/runtime/projection/index.mjs +0 -4
  831. package/dist/substrate/runtime/projection/persisted.mjs +0 -213
  832. package/dist/substrate/runtime/projection/persisted.mjs.map +0 -1
  833. package/dist/substrate/runtime/scoped-multimap/service.mjs +0 -52
  834. package/dist/substrate/runtime/scoped-multimap/service.mjs.map +0 -1
  835. package/dist/substrate/runtime/scoped-ref-map/index.mjs +0 -2
  836. package/dist/substrate/runtime/scoped-ref-map/service.mjs +0 -83
  837. package/dist/substrate/runtime/scoped-ref-map/service.mjs.map +0 -1
  838. package/dist/substrate/runtime/state-store/index.mjs +0 -3
  839. package/dist/substrate/runtime/state-store/schema.d.mts +0 -1
  840. package/dist/substrate/runtime/state-store/schema.mjs +0 -41
  841. package/dist/substrate/runtime/state-store/schema.mjs.map +0 -1
  842. package/dist/substrate/runtime/state-store/service.d.mts +0 -1
  843. package/dist/substrate/runtime/state-store/service.mjs +0 -145
  844. package/dist/substrate/runtime/state-store/service.mjs.map +0 -1
  845. package/dist/substrate/runtime/sui-execute/index.mjs.map +0 -1
  846. package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs.map +0 -1
  847. package/dist/substrate/runtime/sui-ledger/object-ref.mjs.map +0 -1
  848. package/dist/substrate/runtime/sui-move-build/index.mjs.map +0 -1
  849. package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs +0 -151
  850. package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs.map +0 -1
  851. package/dist/substrate/state-store.d.mts +0 -1
  852. /package/dist/orchestrators/{snapshot/pending-marker.d.mts → codegen/id-config.d.mts} +0 -0
  853. /package/dist/orchestrators/snapshot/{recover-pending.d.mts → interrupted-restore.d.mts} +0 -0
  854. /package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/sign-and-dispatch.d.mts +0 -0
  855. /package/dist/plugins/{postgres/snapshot.d.mts → sui/ledger/object-ref.d.mts} +0 -0
  856. /package/dist/{substrate/runtime/sui-move-build → plugins/sui/move}/index.d.mts +0 -0
  857. /package/dist/substrate/runtime/{state-store/index.d.mts → reconcile/graph.d.mts} +0 -0
  858. /package/dist/substrate/runtime/{cross-process/snapshot-reservation.d.mts → supervisor/contribution-dispatcher.d.mts} +0 -0
@@ -1,4 +1,65 @@
1
1
  //#region src/surfaces/tui/display-derivation.ts
2
+ /**
3
+ * Derive the single canonical surface-neutral ViewModel from a snapshot.
4
+ * Pure. No surface coupling — substrate types only.
5
+ */
6
+ const deriveStackViewModel = (state) => {
7
+ let readyRows = 0;
8
+ let activeRows = 0;
9
+ let failedRows = 0;
10
+ let waitingRows = 0;
11
+ let stoppedRows = 0;
12
+ for (const row of state.rows) switch (row.status) {
13
+ case "ready":
14
+ case "done":
15
+ readyRows += 1;
16
+ break;
17
+ case "acquiring":
18
+ case "stopping":
19
+ activeRows += 1;
20
+ break;
21
+ case "failed":
22
+ failedRows += 1;
23
+ break;
24
+ case "pending":
25
+ waitingRows += 1;
26
+ break;
27
+ case "stopped":
28
+ stoppedRows += 1;
29
+ break;
30
+ default: row.status;
31
+ }
32
+ return {
33
+ total: state.rows.length,
34
+ readyRows,
35
+ activeRows,
36
+ failedRows,
37
+ waitingRows,
38
+ stoppedRows,
39
+ endpointCount: visibleEndpointCount(state.rows, state.endpoints),
40
+ accountCount: state.accounts.length,
41
+ packageCount: state.packages.length,
42
+ errorCount: state.errors.length
43
+ };
44
+ };
45
+ /**
46
+ * Resolve the canonical counts to a health enum under a NAMED policy.
47
+ * The two policies are documented divergent and kept separate:
48
+ *
49
+ * policy='tui': total===0 → empty; failed>0 → blocked;
50
+ * (active>0 || waiting>0 || stopped>0) → active; else ready.
51
+ * (Preserves the TUI operator signal where anything not-yet-settled
52
+ * — including a stopped row — reads as `active`.)
53
+ *
54
+ * policy='web': failed>0 → blocked; active>0 → active;
55
+ * total>0 && (ready+stopped)===total → ready; else empty.
56
+ * (Preserves the web GraphQL contract where `stopped` folds into the
57
+ * ready denominator and a pending-only stack reads as `empty`.)
58
+ */
59
+ const deriveHealth = (counts, policy) => {
60
+ if (policy === "tui") return counts.total === 0 ? "empty" : counts.failedRows > 0 ? "blocked" : counts.activeRows > 0 || counts.waitingRows > 0 || counts.stoppedRows > 0 ? "active" : "ready";
61
+ return counts.failedRows > 0 ? "blocked" : counts.activeRows > 0 ? "active" : counts.total > 0 && counts.readyRows + counts.stoppedRows === counts.total ? "ready" : "empty";
62
+ };
2
63
  /** Pure: status → ANSI glyph. */
3
64
  const statusGlyph = (status) => {
4
65
  switch (status) {
@@ -165,23 +226,26 @@ const groupRows = (rows, endpoints = []) => {
165
226
  rows: buckets.get(section) ?? []
166
227
  }));
167
228
  };
229
+ /**
230
+ * TUI dashboard summary. Delegates to the canonical `deriveStackViewModel`
231
+ * + `deriveHealth(counts, 'tui')`. The `waitingRows` field folds the
232
+ * discrete `stoppedRows` back in (the TUI counts stopped rows as
233
+ * "waiting") so `dashboardSummaryLine` and its consumers get the TUI's
234
+ * expected shape.
235
+ */
168
236
  const deriveDashboardSummary = (state) => {
169
- const readyRows = state.rows.filter((row) => row.status === "ready" || row.status === "done").length;
170
- const activeRows = state.rows.filter((row) => row.status === "acquiring" || row.status === "stopping").length;
171
- const failedRows = state.rows.filter((row) => row.status === "failed").length;
172
- const waitingRows = state.rows.filter((row) => row.status === "pending" || row.status === "stopped").length;
173
- const health = state.rows.length === 0 ? "empty" : failedRows > 0 ? "blocked" : activeRows > 0 || waitingRows > 0 ? "active" : "ready";
237
+ const vm = deriveStackViewModel(state);
174
238
  return {
175
- totalRows: state.rows.length,
176
- readyRows,
177
- activeRows,
178
- failedRows,
179
- waitingRows,
180
- endpointCount: visibleEndpointCount(state.rows, state.endpoints),
181
- accountCount: state.accounts.length,
182
- packageCount: state.packages.length,
183
- errorCount: state.errors.length,
184
- health
239
+ totalRows: vm.total,
240
+ readyRows: vm.readyRows,
241
+ activeRows: vm.activeRows,
242
+ failedRows: vm.failedRows,
243
+ waitingRows: vm.waitingRows + vm.stoppedRows,
244
+ endpointCount: vm.endpointCount,
245
+ accountCount: vm.accountCount,
246
+ packageCount: vm.packageCount,
247
+ errorCount: vm.errorCount,
248
+ health: deriveHealth(vm, "tui")
185
249
  };
186
250
  };
187
251
  const visibleEndpointCount = (rows, endpoints) => {
@@ -339,6 +403,6 @@ const isOperationalEndpoint = (rowKey, endpoint) => {
339
403
  return OPERATIONAL_ENDPOINT_FIELDS.has(field);
340
404
  };
341
405
  //#endregion
342
- export { accountCells, accountLine, dashboardSummaryLine, deriveDashboardSummary, deriveDisplayCells, endpointLine, errorSummaryFor, groupRows, labelForRow, packageCells, packageLine, roleLabel, sectionColor, statusLabel, visibleEndpointsForRow };
406
+ export { accountCells, accountLine, dashboardSummaryLine, deriveDashboardSummary, deriveDisplayCells, deriveHealth, deriveStackViewModel, endpointLine, errorSummaryFor, groupRows, labelForRow, packageCells, packageLine, roleLabel, sectionColor, statusLabel, visibleEndpointsForRow };
343
407
 
344
408
  //# sourceMappingURL=display-derivation.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"display-derivation.mjs","names":[],"sources":["../../../src/surfaces/tui/display-derivation.ts"],"sourcesContent":["// INVARIANT: this module is Ink-free, React-free, and DOM-free. Its\n// only imports are `type`-only from `substrate/`. Every export returns\n// a plain TS value (string, ColorToken, plain object) — never a JSX or\n// Ink component. This is what lets a future web dashboard reuse the\n// same status→glyph/label/color derivation a browser renderer would\n// otherwise have to duplicate. Do not import from `ink`, `react`, or\n// any sibling `*.tsx` here.\n//\n// Display derivation — the load-bearing transducer that makes the\n// \"no display vocab in projection\" invariant testable.\n//\n// The renderer consumes ONLY the typed projection (`Row`, `Endpoint`,\n// `StructuredError`, `LifecycleStatus`, `PluginRole`, ...) from\n// `substrate/projection.ts`. Every visible glyph, color, label, and\n// narration string is computed HERE from `row.role`, `row.status`,\n// `row.phase`, `row.lastError`, etc.\n//\n// HARD INVARIANT: this module never reads a field named `title`,\n// `primary`, or `extras` — those fields don't exist on the projection\n// (the substrate's `__ProjectionFieldsClosed` guard fails to compile\n// if they did). The TUI's test of that invariant is: every visible\n// cell rendered by `resource-table.tsx` flows through one of these pure\n// functions.\n//\n// Pure. No Effect, no IO, no clocks. Input typed projection → output\n// strings + enum-y \"color tokens\" (resolved to terminal colors by the\n// Ink layer via `Text color={…}`).\n\nimport type { LifecycleStatus, PhaseNarration, PluginRole } from '../../substrate/lifecycle.ts';\nimport type {\n\tAccountProjection,\n\tEndpoint,\n\tPackageProjection,\n\tRow,\n\tRowSection,\n\tStructuredError,\n\tSubscribableState,\n} from '../../substrate/projection.ts';\n\nexport type { RowSection } from '../../substrate/projection.ts';\n\n// -----------------------------------------------------------------------------\n// Color tokens\n// -----------------------------------------------------------------------------\n\n/**\n * Closed set of color tokens the TUI uses. Ink resolves these to ANSI\n * sequences. NO_COLOR / FORCE_COLOR / TERM=dumb are honored by Ink's\n * defaults (Tension: TUI must not override user color preferences).\n */\nexport type ColorToken = 'yellow' | 'green' | 'red' | 'magenta' | 'cyan' | 'blueBright' | 'white';\n\n// -----------------------------------------------------------------------------\n// Display cells — the public output shape of this module\n// -----------------------------------------------------------------------------\n\n/**\n * Per-row display cells the row renderer consumes. None of these\n * fields are read from the projection — they are computed from\n * `Row.role` + `Row.status` + `Row.phase` + `Row.lastError`.\n *\n * Naming note: we DELIBERATELY avoid the words `title`, `primary`,\n * `extras` in this type's field names to make the invariant grep-able.\n * `label` is computed from `key`; `narration` from `phase`;\n * `summary` from `lastError`.\n */\nexport interface DisplayCells {\n\t/** Status glyph (single char). */\n\treadonly statusGlyph: string;\n\t/** Status color token. */\n\treadonly statusColor: ColorToken;\n\t/** Status label (short verbal, e.g. \"ready\"). */\n\treadonly statusLabel: string;\n\t/** Role glyph (single char). */\n\treadonly roleGlyph: string;\n\t/** Role label (e.g. \"service\"). */\n\treadonly roleLabel: string;\n\t/** Computed row label — derived from `row.key`, not from any\n\t * projection-side `title`. */\n\treadonly label: string;\n\t/** Narration line — formatted from `row.phase`. */\n\treadonly narration: string;\n\t/** Error summary — formatted from `row.lastError`. */\n\treadonly errorSummary: string;\n\t/** Color the row's label should render in (role-derived,\n\t * optionally tinted by status). */\n\treadonly labelColor: ColorToken;\n\t/** Group bucket for dashboard sectioning. */\n\treadonly section: RowSection;\n\t/** Plugin-owner chip derived from the row key. */\n\treadonly owner: string;\n\t/** Main row value: usually the first endpoint or latest useful line. */\n\treadonly headline: string;\n\t/** Secondary details visible in the detail pane. */\n\treadonly secondary: ReadonlyArray<string>;\n}\n\nexport interface DisplayRow {\n\treadonly row: Row;\n\treadonly cells: DisplayCells;\n}\n\nexport interface DisplaySection {\n\treadonly key: RowSection;\n\treadonly label: string;\n\treadonly rows: ReadonlyArray<DisplayRow>;\n}\n\nexport interface DashboardSummary {\n\treadonly totalRows: number;\n\treadonly readyRows: number;\n\treadonly activeRows: number;\n\treadonly failedRows: number;\n\treadonly waitingRows: number;\n\treadonly endpointCount: number;\n\treadonly accountCount: number;\n\treadonly packageCount: number;\n\treadonly errorCount: number;\n\treadonly health: 'ready' | 'active' | 'blocked' | 'empty';\n}\n\n// -----------------------------------------------------------------------------\n// Status → glyph / color / label\n// -----------------------------------------------------------------------------\n\n/** Pure: status → ANSI glyph. */\nexport const statusGlyph = (status: LifecycleStatus): string => {\n\tswitch (status) {\n\t\tcase 'pending':\n\t\t\treturn '○';\n\t\tcase 'acquiring':\n\t\t\treturn '◐';\n\t\tcase 'ready':\n\t\t\treturn '●';\n\t\tcase 'failed':\n\t\t\treturn '✖';\n\t\tcase 'stopping':\n\t\t\treturn '◑';\n\t\tcase 'stopped':\n\t\t\treturn '◌';\n\t\tcase 'done':\n\t\t\treturn '✓';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = status;\n\t\t\tvoid _exhaustive;\n\t\t\treturn '?';\n\t\t}\n\t}\n};\n\n/** Pure: status → color token. */\nexport const statusColor = (status: LifecycleStatus): ColorToken => {\n\tswitch (status) {\n\t\tcase 'pending':\n\t\t\treturn 'white';\n\t\tcase 'acquiring':\n\t\t\treturn 'yellow';\n\t\tcase 'ready':\n\t\t\treturn 'green';\n\t\tcase 'failed':\n\t\t\treturn 'red';\n\t\tcase 'stopping':\n\t\t\treturn 'yellow';\n\t\tcase 'stopped':\n\t\t\treturn 'white';\n\t\tcase 'done':\n\t\t\treturn 'green';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = status;\n\t\t\tvoid _exhaustive;\n\t\t\treturn 'white';\n\t\t}\n\t}\n};\n\n/** Pure: status → short operator-facing label. */\nexport const statusLabel = (status: LifecycleStatus): string => {\n\tswitch (status) {\n\t\tcase 'pending':\n\t\t\treturn 'pending';\n\t\tcase 'acquiring':\n\t\t\treturn 'starting';\n\t\tcase 'ready':\n\t\t\treturn 'ready';\n\t\tcase 'failed':\n\t\t\treturn 'failed';\n\t\tcase 'stopping':\n\t\t\treturn 'stopping';\n\t\tcase 'stopped':\n\t\t\treturn 'stopped';\n\t\tcase 'done':\n\t\t\treturn 'done';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = status;\n\t\t\tvoid _exhaustive;\n\t\t\treturn 'unknown';\n\t\t}\n\t}\n};\n\n// -----------------------------------------------------------------------------\n// Role -> glyph / label / color\n// -----------------------------------------------------------------------------\n\n/** Pure: role -> ANSI glyph. */\nexport const roleGlyph = (role: PluginRole): string => {\n\tswitch (role) {\n\t\tcase 'service':\n\t\t\treturn '⚙';\n\t\tcase 'task':\n\t\t\treturn '⚡';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = role;\n\t\t\tvoid _exhaustive;\n\t\t\treturn '?';\n\t\t}\n\t}\n};\n\n/** Pure: role -> short label. */\nexport const roleLabel = (role: PluginRole): string => {\n\tswitch (role) {\n\t\tcase 'service':\n\t\t\treturn 'service';\n\t\tcase 'task':\n\t\t\treturn 'task';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = role;\n\t\t\tvoid _exhaustive;\n\t\t\treturn 'unknown';\n\t\t}\n\t}\n};\n\n/** Pure: role -> label color token. */\nexport const roleLabelColor = (role: PluginRole): ColorToken => {\n\tswitch (role) {\n\t\tcase 'service':\n\t\t\treturn 'cyan';\n\t\tcase 'task':\n\t\t\treturn 'magenta';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = role;\n\t\t\tvoid _exhaustive;\n\t\t\treturn 'white';\n\t\t}\n\t}\n};\n\n// -----------------------------------------------------------------------------\n// Row label — derived from `key`, not from any pre-baked title\n// -----------------------------------------------------------------------------\n\n/**\n * Compute a human-friendly row label from the opaque branded plugin\n * key. The substrate keeps `key` an opaque digest; the renderer is\n * the layer that interprets it. We strip a few conventional prefixes\n * (`devstack:`, `app:`) plus instance counters so common cases read\n * cleanly.\n *\n * The function consumes `key` only — never `row.title`, which doesn't\n * exist in the projection.\n */\nexport const labelForRow = (key: string): string => {\n\tconst normalized = normalizeKey(key);\n\tconst parts = normalized.split(/[/:]/).filter((part) => part.length > 0);\n\tif (parts.length >= 2 && isSectionish(parts[0]!)) return humanizeToken(parts[parts.length - 1]!);\n\treturn humanizeToken(parts[parts.length - 1] ?? normalized);\n};\n\nexport const ownerForRow = (key: string): string => {\n\tconst normalized = normalizeKey(key);\n\tconst first = normalized.split(/[/:._#-]/).find((part) => part.length > 0);\n\treturn first === undefined ? 'plugin' : humanizeToken(first);\n};\n\n/** Pure: pick the row's dashboard section. Reads `row.section`\n * directly; if the plugin declared an `endpointSection` AND the row\n * owns a routed endpoint, the endpoint variant wins. NEVER pattern-\n * matches on `row.key`; the classifier is plugin-declared at\n * `definePlugin({ section, endpointSection })` time and stamped by\n * the supervisor. */\nexport const sectionForRow = (\n\trow: Pick<Row, 'key' | 'endpoints' | 'section' | 'endpointSection'>,\n\tendpoints: ReadonlyArray<Endpoint> = [],\n): RowSection => {\n\tconst ownsEndpoint = endpointsForRow(row, endpoints).length > 0 || row.endpoints.length > 0;\n\treturn ownsEndpoint && row.endpointSection !== row.section ? row.endpointSection : row.section;\n};\n\n/** Pure: section -> scope-chip color token used by the event log /\n * activity stream. Coloring is driven by `RowSection` so the renderer\n * doesn't pattern-match on plugin names. */\nexport const sectionColor = (section: RowSection): ColorToken => {\n\tswitch (section) {\n\t\tcase 'service':\n\t\t\treturn 'cyan';\n\t\tcase 'package':\n\t\t\treturn 'blueBright';\n\t\tcase 'account':\n\t\t\treturn 'magenta';\n\t\tcase 'action':\n\t\t\treturn 'magenta';\n\t\tcase 'app':\n\t\t\treturn 'white';\n\t\tcase 'other':\n\t\t\treturn 'cyan';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = section;\n\t\t\tvoid _exhaustive;\n\t\t\treturn 'cyan';\n\t\t}\n\t}\n};\n\nexport const sectionLabel = (section: RowSection): string => {\n\tswitch (section) {\n\t\tcase 'service':\n\t\t\treturn 'Services';\n\t\tcase 'package':\n\t\t\treturn 'Packages';\n\t\tcase 'account':\n\t\t\treturn 'Accounts';\n\t\tcase 'action':\n\t\t\treturn 'Actions';\n\t\tcase 'app':\n\t\t\treturn 'App';\n\t\tcase 'other':\n\t\t\treturn 'Other';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = section;\n\t\t\tvoid _exhaustive;\n\t\t\treturn 'Other';\n\t\t}\n\t}\n};\n\nexport const endpointsForRow = (\n\trow: Pick<Row, 'key' | 'endpoints'>,\n\tendpoints: ReadonlyArray<Endpoint>,\n): ReadonlyArray<Endpoint> =>\n\tendpoints.filter(\n\t\t(endpoint) => row.endpoints.includes(endpoint.endpointKey) || endpoint.pluginKey === row.key,\n\t);\n\nconst OPERATIONAL_ENDPOINT_FIELDS = new Set(['url', 'rpcUrl', 'faucetUrl', 'graphqlUrl']);\n\nexport const visibleEndpointsForRow = (\n\trow: Pick<Row, 'key' | 'endpoints'>,\n\tendpoints: ReadonlyArray<Endpoint>,\n): ReadonlyArray<Endpoint> => {\n\tconst rowEndpoints = endpointsForRow(row, endpoints);\n\tconst routed = rowEndpoints.filter((endpoint) => !isOperationalEndpoint(row.key, endpoint));\n\treturn routed.length > 0 ? routed : rowEndpoints;\n};\n\nexport const headlineForRow = (row: Row, endpoints: ReadonlyArray<Endpoint> = []): string => {\n\tconst rowEndpoints = visibleEndpointsForRow(row, endpoints);\n\tif (rowEndpoints.length > 0) return endpointLine(rowEndpoints[0]!);\n\tif (row.lastError !== null && row.status === 'failed') return errorSummaryFor(row.lastError);\n\treturn narrationFor(row.phase, row.status);\n};\n\nexport const secondaryForRow = (\n\trow: Row,\n\tendpoints: ReadonlyArray<Endpoint> = [],\n): ReadonlyArray<string> => {\n\tconst out: Array<string> = [];\n\tconst rowEndpoints = endpointsForRow(row, endpoints);\n\tfor (const endpoint of rowEndpoints.slice(1)) out.push(endpointLine(endpoint));\n\tif (row.logTail.truncated) out.push('log tail truncated');\n\treturn out;\n};\n\nexport const groupRows = (\n\trows: ReadonlyArray<Row>,\n\tendpoints: ReadonlyArray<Endpoint> = [],\n): ReadonlyArray<DisplaySection> => {\n\tconst buckets = new Map<RowSection, Array<DisplayRow>>();\n\tfor (const row of rows) {\n\t\tconst cells = deriveDisplayCells(row, endpoints);\n\t\tconst list = buckets.get(cells.section) ?? [];\n\t\tlist.push({ row, cells });\n\t\tbuckets.set(cells.section, list);\n\t}\n\tconst order: ReadonlyArray<RowSection> = [\n\t\t'service',\n\t\t'package',\n\t\t'account',\n\t\t'action',\n\t\t'app',\n\t\t'other',\n\t];\n\treturn order\n\t\t.filter((section) => buckets.has(section))\n\t\t.map((section) => ({\n\t\t\tkey: section,\n\t\t\tlabel: sectionLabel(section),\n\t\t\trows: buckets.get(section) ?? [],\n\t\t}));\n};\n\nexport const deriveDashboardSummary = (\n\tstate: Pick<SubscribableState, 'rows' | 'endpoints' | 'accounts' | 'packages' | 'errors'>,\n): DashboardSummary => {\n\tconst readyRows = state.rows.filter(\n\t\t(row) => row.status === 'ready' || row.status === 'done',\n\t).length;\n\tconst activeRows = state.rows.filter(\n\t\t(row) => row.status === 'acquiring' || row.status === 'stopping',\n\t).length;\n\tconst failedRows = state.rows.filter((row) => row.status === 'failed').length;\n\tconst waitingRows = state.rows.filter(\n\t\t(row) => row.status === 'pending' || row.status === 'stopped',\n\t).length;\n\tconst health =\n\t\tstate.rows.length === 0\n\t\t\t? 'empty'\n\t\t\t: failedRows > 0\n\t\t\t\t? 'blocked'\n\t\t\t\t: activeRows > 0 || waitingRows > 0\n\t\t\t\t\t? 'active'\n\t\t\t\t\t: 'ready';\n\treturn {\n\t\ttotalRows: state.rows.length,\n\t\treadyRows,\n\t\tactiveRows,\n\t\tfailedRows,\n\t\twaitingRows,\n\t\tendpointCount: visibleEndpointCount(state.rows, state.endpoints),\n\t\taccountCount: state.accounts.length,\n\t\tpackageCount: state.packages.length,\n\t\terrorCount: state.errors.length,\n\t\thealth,\n\t};\n};\n\nconst visibleEndpointCount = (\n\trows: ReadonlyArray<Row>,\n\tendpoints: ReadonlyArray<Endpoint>,\n): number => {\n\tconst rowOwned = new Set<Endpoint['endpointKey']>();\n\tconst visible = new Set<Endpoint['endpointKey']>();\n\tfor (const row of rows) {\n\t\tfor (const endpoint of endpointsForRow(row, endpoints)) {\n\t\t\trowOwned.add(endpoint.endpointKey);\n\t\t}\n\t\tfor (const endpoint of visibleEndpointsForRow(row, endpoints)) {\n\t\t\tvisible.add(endpoint.endpointKey);\n\t\t}\n\t}\n\tfor (const endpoint of endpoints) {\n\t\tif (!rowOwned.has(endpoint.endpointKey)) visible.add(endpoint.endpointKey);\n\t}\n\treturn visible.size;\n};\n\nexport const dashboardSummaryLine = (summary: DashboardSummary): string =>\n\t[\n\t\t`${summary.readyRows}/${summary.totalRows} ready`,\n\t\tsummary.activeRows > 0 ? `${summary.activeRows} active` : null,\n\t\tsummary.waitingRows > 0 ? `${summary.waitingRows} waiting` : null,\n\t\tsummary.failedRows > 0 ? `${summary.failedRows} failed` : null,\n\t\t`${summary.endpointCount} urls`,\n\t\t`${summary.accountCount} accounts`,\n\t\tsummary.packageCount > 0 ? `${summary.packageCount} packages` : null,\n\t\tsummary.errorCount === 0 ? 'no errors' : `${summary.errorCount} errors`,\n\t]\n\t\t.filter((part): part is string => part !== null)\n\t\t.join(' ');\n\n// -----------------------------------------------------------------------------\n// Phase narration formatting\n// -----------------------------------------------------------------------------\n\n/**\n * Pure: phase narration string → renderable verb form. Phase is\n * free-form upstream (Tension 14). The renderer's job is to make it\n * legible; we keep this minimal — single-line, no fancy verb tense\n * inference. The truncation cap protects against pathological long\n * narrations.\n */\nexport const narrationFor = (phase: PhaseNarration | null, status: LifecycleStatus): string => {\n\tif (phase === null || phase.trim().length === 0) {\n\t\t// No phase set; surface a status-derived default for `acquiring`,\n\t\t// stay silent otherwise (Tension: don't manufacture text the\n\t\t// engine didn't ask for).\n\t\treturn status === 'acquiring' ? 'starting…' : '';\n\t}\n\tconst trimmed = phase.trim();\n\tconst TRUNC = 80;\n\treturn trimmed.length > TRUNC ? `${trimmed.slice(0, TRUNC - 1)}…` : trimmed;\n};\n\n// -----------------------------------------------------------------------------\n// Error summary formatting\n// -----------------------------------------------------------------------------\n\nconst ERR_SUMMARY_TRUNC = 120;\n\n/**\n * Pure: `Row.lastError` → renderable in-row summary. Keep it short;\n * detailed error context belongs in the activity/log stream.\n */\nexport const errorSummaryFor = (error: StructuredError | null): string => {\n\tif (error === null) return '';\n\tconst head = `${error.tag}: ${error.summary}`;\n\tconst detail = error.chain.find((line) => line !== head && line !== error.tag);\n\tconst compactDetail =\n\t\tdetail === undefined ? undefined : detail.replace(/^[A-Za-z0-9_[\\]-]+:\\s*/, '');\n\tconst summary = compactDetail === undefined ? head : `${head}: ${compactDetail}`;\n\treturn summary.length > ERR_SUMMARY_TRUNC\n\t\t? `${summary.slice(0, ERR_SUMMARY_TRUNC - 1)}…`\n\t\t: summary;\n};\n\n// -----------------------------------------------------------------------------\n// Endpoint formatting\n// -----------------------------------------------------------------------------\n\n/**\n * Pure: `Endpoint` → renderable line. Renderers prefer `displayUrl`\n * when set (codegen-friendly variant), falling back to `url`.\n */\nexport const endpointLine = (endpoint: Endpoint): string => {\n\tconst target = endpoint.displayUrl ?? endpoint.url;\n\tconst backing =\n\t\tendpoint.displayUrl !== null && endpoint.displayUrl !== endpoint.url\n\t\t\t? ` -> ${endpoint.url}`\n\t\t\t: '';\n\tconst protocol = endpoint.wireProtocol === 'http' ? '' : ` [${endpoint.wireProtocol}]`;\n\treturn `${endpoint.name}: ${target}${backing}${protocol}`;\n};\n\nexport const endpointsSummaryForRow = (\n\trow: Pick<Row, 'key' | 'endpoints'>,\n\tendpoints: ReadonlyArray<Endpoint>,\n): string => {\n\tconst rowEndpoints = visibleEndpointsForRow(row, endpoints);\n\tif (rowEndpoints.length === 0) return '';\n\treturn rowEndpoints.map(endpointLine).join(' | ');\n};\n\nexport const accountLine = (account: AccountProjection): string => {\n\tconst address = account.address ?? '<pending>';\n\tconst scheme = account.scheme ?? 'scheme pending';\n\tconst source = account.source ?? 'source pending';\n\tconst funding = accountFundingLine(account.funding);\n\tconst wallet = account.walletVisible ? 'wallet' : null;\n\treturn [humanizeToken(account.name), address, scheme, source, funding, wallet]\n\t\t.filter((part): part is string => part !== null && part.length > 0)\n\t\t.join(' ');\n};\n\nexport const packageLine = (pkg: PackageProjection): string => {\n\treturn [humanizeToken(pkg.name), pkg.packageId, pkg.mvrPlaceholder, pkg.kind]\n\t\t.filter((part): part is string => part !== null && part.length > 0)\n\t\t.join(' ');\n};\n\nexport interface AccountCells {\n\treadonly name: string;\n\treadonly address: string;\n\treadonly scheme: string;\n\treadonly source: string;\n\treadonly funding: string;\n}\n\nexport interface PackageCells {\n\treadonly name: string;\n\treadonly packageId: string;\n\treadonly mvr: string;\n\treadonly kind: string;\n\treadonly detail: string;\n}\n\nexport const accountCells = (account: AccountProjection): AccountCells => ({\n\tname: humanizeToken(account.name),\n\taddress: account.address ?? '<pending>',\n\tscheme: account.scheme ?? 'pending',\n\tsource: account.source ?? 'pending',\n\tfunding: accountFundingLine(account.funding),\n});\n\nconst accountFundingLine = (funding: AccountProjection['funding']): string => {\n\tconst entries = funding.entries ?? [];\n\tif (entries.length > 0) {\n\t\tconst rendered = entries\n\t\t\t.map(\n\t\t\t\t(entry) => `${entry.coin}:${entry.amount}${entry.status === 'skipped' ? ' skipped' : ''}`,\n\t\t\t)\n\t\t\t.join(', ');\n\t\treturn funding.status === 'funded' ? `funded ${rendered}` : `funding ${rendered}`;\n\t}\n\tswitch (funding.status) {\n\t\tcase 'pending':\n\t\t\treturn funding.requestedMist === null\n\t\t\t\t? 'funding pending'\n\t\t\t\t: `funding ${funding.requestedMist}`;\n\t\tcase 'funded':\n\t\t\treturn funding.balanceMist !== null\n\t\t\t\t? `funded ${funding.balanceMist}`\n\t\t\t\t: funding.requestedMist !== null\n\t\t\t\t\t? `funded ${funding.requestedMist}`\n\t\t\t\t\t: 'funded';\n\t\tcase 'skipped':\n\t\t\treturn 'funding skipped';\n\t\tcase 'failed':\n\t\t\treturn 'funding failed';\n\t\tcase 'unknown':\n\t\t\treturn 'funding unknown';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = funding.status;\n\t\t\tvoid _exhaustive;\n\t\t\treturn '';\n\t\t}\n\t}\n};\n\nexport const packageCells = (pkg: PackageProjection): PackageCells => ({\n\tname: humanizeToken(pkg.name),\n\tpackageId: pkg.packageId,\n\tmvr: pkg.mvrPlaceholder,\n\tkind: pkg.kind,\n\tdetail: pkg.kind,\n});\n\n// -----------------------------------------------------------------------------\n// Top-level entry point\n// -----------------------------------------------------------------------------\n\n/**\n * Derive every visible display cell for a single row.\n *\n * Consumes ONLY `row.key`, `row.role`, `row.status`, `row.phase`,\n * `row.lastError`, `row.endpoints` (rendered separately by\n * `resource-table.tsx`), or `row.logTail` (kept in the projection\n * for renderer variants that need tails, but not used as primary\n * dashboard state because generic lifecycle log lines duplicate the\n * table).\n */\nexport const deriveDisplayCells = (\n\trow: Row,\n\tendpoints: ReadonlyArray<Endpoint> = [],\n): DisplayCells => ({\n\tstatusGlyph: statusGlyph(row.status),\n\tstatusColor: statusColor(row.status),\n\tstatusLabel: statusLabel(row.status),\n\troleGlyph: roleGlyph(row.role),\n\troleLabel: roleLabel(row.role),\n\tlabel: labelForRow(row.key),\n\tnarration: narrationFor(row.phase, row.status),\n\terrorSummary: errorSummaryFor(row.lastError),\n\tlabelColor: roleLabelColor(row.role),\n\tsection: sectionForRow(row, endpoints),\n\towner: ownerForRow(row.key),\n\theadline: headlineForRow(row, endpoints),\n\tsecondary: secondaryForRow(row, endpoints),\n});\n\nconst normalizeKey = (key: string): string =>\n\tkey\n\t\t.replace(/^@devstack\\//, '')\n\t\t.replace(/^(devstack:|app:)/, '')\n\t\t.replace(/#\\d+$/, '')\n\t\t.replace(/\\/\\d+$/, '');\n\nconst humanizeToken = (token: string): string =>\n\ttoken.replace(/[-_]+/g, ' ').replace(/^\\w/, (head) => head.toUpperCase());\n\nconst containsKeyPart = (key: string, parts: ReadonlyArray<string>): boolean => {\n\tconst tokens = key.split(/[/:._#-]/).filter((part) => part.length > 0);\n\treturn tokens.some((token) => parts.includes(token));\n};\n\nconst isSectionish = (token: string): boolean =>\n\tcontainsKeyPart(token.toLowerCase(), ['service', 'package', 'account', 'action', 'app']);\n\nconst isOperationalEndpoint = (rowKey: Pick<Row, 'key'>['key'], endpoint: Endpoint): boolean => {\n\tif (endpoint.pluginKey !== rowKey) return false;\n\tconst prefix = `${rowKey}:`;\n\tif (!endpoint.endpointKey.startsWith(prefix)) return false;\n\tconst field = endpoint.endpointKey.slice(prefix.length);\n\treturn OPERATIONAL_ENDPOINT_FIELDS.has(field);\n};\n\n// -----------------------------------------------------------------------------\n// Compile-time invariant\n// -----------------------------------------------------------------------------\n//\n// Renderer code must never see a `title`/`primary`/`extras` field on\n// `Row`. If the projection ever sprouts one, the substrate's\n// `__ProjectionFieldsClosed` guard fails first; this module-local\n// guard exists as a second layer that explicitly mentions the\n// forbidden field names so a future renderer-side regression is\n// caught at the renderer boundary, not at the substrate boundary.\n\ntype _NoForbiddenOnRow = 'title' extends keyof Row\n\t? never\n\t: 'primary' extends keyof Row\n\t\t? never\n\t\t: 'extras' extends keyof Row\n\t\t\t? never\n\t\t\t: true;\n\n/** Renderer-side compile-time guard. If this resolves to `never`,\n * the renderer is reading a forbidden display-vocabulary field from\n * the projection. */\nexport type __TuiDisplayVocabClean = _NoForbiddenOnRow extends true ? true : never;\n"],"mappings":"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"}
1
+ {"version":3,"file":"display-derivation.mjs","names":[],"sources":["../../../src/surfaces/tui/display-derivation.ts"],"sourcesContent":["// INVARIANT: this module is Ink-free, React-free, and DOM-free. Its\n// only imports are `type`-only from `substrate/`. Every export returns\n// a plain TS value (string, ColorToken, plain object) — never a JSX or\n// Ink component. This is what lets a future web dashboard reuse the\n// same status→glyph/label/color derivation a browser renderer would\n// otherwise have to duplicate. Do not import from `ink`, `react`, or\n// any sibling `*.tsx` here.\n//\n// Display derivation — the load-bearing transducer that makes the\n// \"no display vocab in projection\" invariant testable.\n//\n// The renderer consumes ONLY the typed projection (`Row`, `Endpoint`,\n// `StructuredError`, `LifecycleStatus`, `PluginRole`, ...) from\n// `substrate/projection.ts`. Every visible glyph, color, label, and\n// narration string is computed HERE from `row.role`, `row.status`,\n// `row.phase`, `row.lastError`, etc.\n//\n// HARD INVARIANT: this module never reads a field named `title`,\n// `primary`, or `extras` — those fields don't exist on the projection\n// (the substrate's `__ProjectionFieldsClosed` guard fails to compile\n// if they did). The TUI's test of that invariant is: every visible\n// cell rendered by `resource-table.tsx` flows through one of these pure\n// functions.\n//\n// Pure. No Effect, no IO, no clocks. Input typed projection → output\n// strings + enum-y \"color tokens\" (resolved to terminal colors by the\n// Ink layer via `Text color={…}`).\n\nimport type { LifecycleStatus, PhaseNarration, PluginRole } from '../../substrate/lifecycle.ts';\nimport type {\n\tAccountProjection,\n\tEndpoint,\n\tPackageProjection,\n\tRow,\n\tRowSection,\n\tStructuredError,\n\tSubscribableState,\n} from '../../substrate/projection.ts';\n\nexport type { RowSection } from '../../substrate/projection.ts';\n\n// -----------------------------------------------------------------------------\n// Color tokens\n// -----------------------------------------------------------------------------\n\n/**\n * Closed set of color tokens the TUI uses. Ink resolves these to ANSI\n * sequences. NO_COLOR / FORCE_COLOR / TERM=dumb are honored by Ink's\n * defaults (Tension: TUI must not override user color preferences).\n */\nexport type ColorToken = 'yellow' | 'green' | 'red' | 'magenta' | 'cyan' | 'blueBright' | 'white';\n\n// -----------------------------------------------------------------------------\n// Display cells — the public output shape of this module\n// -----------------------------------------------------------------------------\n\n/**\n * Per-row display cells the row renderer consumes. None of these\n * fields are read from the projection — they are computed from\n * `Row.role` + `Row.status` + `Row.phase` + `Row.lastError`.\n *\n * Naming note: we DELIBERATELY avoid the words `title`, `primary`,\n * `extras` in this type's field names to make the invariant grep-able.\n * `label` is computed from `key`; `narration` from `phase`;\n * `summary` from `lastError`.\n */\nexport interface DisplayCells {\n\t/** Status glyph (single char). */\n\treadonly statusGlyph: string;\n\t/** Status color token. */\n\treadonly statusColor: ColorToken;\n\t/** Status label (short verbal, e.g. \"ready\"). */\n\treadonly statusLabel: string;\n\t/** Role glyph (single char). */\n\treadonly roleGlyph: string;\n\t/** Role label (e.g. \"service\"). */\n\treadonly roleLabel: string;\n\t/** Computed row label — derived from `row.key`, not from any\n\t * projection-side `title`. */\n\treadonly label: string;\n\t/** Narration line — formatted from `row.phase`. */\n\treadonly narration: string;\n\t/** Error summary — formatted from `row.lastError`. */\n\treadonly errorSummary: string;\n\t/** Color the row's label should render in (role-derived,\n\t * optionally tinted by status). */\n\treadonly labelColor: ColorToken;\n\t/** Group bucket for dashboard sectioning. */\n\treadonly section: RowSection;\n\t/** Plugin-owner chip derived from the row key. */\n\treadonly owner: string;\n\t/** Main row value: usually the first endpoint or latest useful line. */\n\treadonly headline: string;\n\t/** Secondary details visible in the detail pane. */\n\treadonly secondary: ReadonlyArray<string>;\n}\n\nexport interface DisplayRow {\n\treadonly row: Row;\n\treadonly cells: DisplayCells;\n}\n\nexport interface DisplaySection {\n\treadonly key: RowSection;\n\treadonly label: string;\n\treadonly rows: ReadonlyArray<DisplayRow>;\n}\n\nexport interface DashboardSummary {\n\treadonly totalRows: number;\n\treadonly readyRows: number;\n\treadonly activeRows: number;\n\treadonly failedRows: number;\n\treadonly waitingRows: number;\n\treadonly endpointCount: number;\n\treadonly accountCount: number;\n\treadonly packageCount: number;\n\treadonly errorCount: number;\n\treadonly health: HealthState;\n}\n\n// -----------------------------------------------------------------------------\n// Surface-neutral stack ViewModel — the single canonical bucketing\n// -----------------------------------------------------------------------------\n\n/**\n * The one canonical health-derived enum. The same four states are shared\n * by both surface policies; only the *bucketing rules* differ (see\n * `deriveHealth`).\n */\nexport type HealthState = 'ready' | 'active' | 'blocked' | 'empty';\n\n/**\n * Surface-neutral roll-up of a projection snapshot. ONE canonical\n * bucketing that every surface (TUI dashboard, web GraphQL\n * `StackState.summary`, CLI `status`) derives from — no surface gets to\n * re-count rows on its own.\n *\n * The `stopped` status is surfaced here as its OWN discrete count rather\n * than folded into either `ready` or `waiting`. No surface loses\n * information: `deriveHealth(counts, policy)` re-applies each surface's documented\n * rule, and the web adapter folds `stopped` into its GraphQL `ready`\n * contract INSIDE the web adapter — never in this shared shape.\n *\n * ready/active/failed/waiting follow the web switch (was\n * `computeHealthSummary`) with `stopped` pulled out separately:\n * - readyRows: ready | done\n * - activeRows: acquiring | stopping\n * - failedRows: failed\n * - waitingRows: pending\n * - stoppedRows: stopped\n */\nexport interface StackViewModel {\n\treadonly total: number;\n\treadonly readyRows: number;\n\treadonly activeRows: number;\n\treadonly failedRows: number;\n\treadonly waitingRows: number;\n\treadonly stoppedRows: number;\n\treadonly endpointCount: number;\n\treadonly accountCount: number;\n\treadonly packageCount: number;\n\treadonly errorCount: number;\n}\n\n/** Health bucketing policy. `'tui'` preserves the operator-facing TUI\n * rule (pending/stopped/mid-flight all read as \"system not settled\" →\n * `active`); `'web'` preserves the GraphQL contract (stopped folds into\n * ready, pending-only reads as `empty`). The two policies are kept\n * NAMED and SEPARATE — never unified — so neither surface regresses. */\nexport type HealthPolicy = 'tui' | 'web';\n\n/**\n * Derive the single canonical surface-neutral ViewModel from a snapshot.\n * Pure. No surface coupling — substrate types only.\n */\nexport const deriveStackViewModel = (\n\tstate: Pick<SubscribableState, 'rows' | 'endpoints' | 'accounts' | 'packages' | 'errors'>,\n): StackViewModel => {\n\tlet readyRows = 0;\n\tlet activeRows = 0;\n\tlet failedRows = 0;\n\tlet waitingRows = 0;\n\tlet stoppedRows = 0;\n\tfor (const row of state.rows) {\n\t\tswitch (row.status) {\n\t\t\tcase 'ready':\n\t\t\tcase 'done':\n\t\t\t\treadyRows += 1;\n\t\t\t\tbreak;\n\t\t\tcase 'acquiring':\n\t\t\tcase 'stopping':\n\t\t\t\tactiveRows += 1;\n\t\t\t\tbreak;\n\t\t\tcase 'failed':\n\t\t\t\tfailedRows += 1;\n\t\t\t\tbreak;\n\t\t\tcase 'pending':\n\t\t\t\twaitingRows += 1;\n\t\t\t\tbreak;\n\t\t\tcase 'stopped':\n\t\t\t\tstoppedRows += 1;\n\t\t\t\tbreak;\n\t\t\tdefault: {\n\t\t\t\tconst _exhaustive: never = row.status;\n\t\t\t\tvoid _exhaustive;\n\t\t\t}\n\t\t}\n\t}\n\treturn {\n\t\ttotal: state.rows.length,\n\t\treadyRows,\n\t\tactiveRows,\n\t\tfailedRows,\n\t\twaitingRows,\n\t\tstoppedRows,\n\t\tendpointCount: visibleEndpointCount(state.rows, state.endpoints),\n\t\taccountCount: state.accounts.length,\n\t\tpackageCount: state.packages.length,\n\t\terrorCount: state.errors.length,\n\t};\n};\n\n/**\n * Resolve the canonical counts to a health enum under a NAMED policy.\n * The two policies are documented divergent and kept separate:\n *\n * policy='tui': total===0 → empty; failed>0 → blocked;\n * (active>0 || waiting>0 || stopped>0) → active; else ready.\n * (Preserves the TUI operator signal where anything not-yet-settled\n * — including a stopped row — reads as `active`.)\n *\n * policy='web': failed>0 → blocked; active>0 → active;\n * total>0 && (ready+stopped)===total → ready; else empty.\n * (Preserves the web GraphQL contract where `stopped` folds into the\n * ready denominator and a pending-only stack reads as `empty`.)\n */\nexport const deriveHealth = (\n\tcounts: Pick<\n\t\tStackViewModel,\n\t\t'total' | 'readyRows' | 'activeRows' | 'failedRows' | 'waitingRows' | 'stoppedRows'\n\t>,\n\tpolicy: HealthPolicy,\n): HealthState => {\n\tif (policy === 'tui') {\n\t\treturn counts.total === 0\n\t\t\t? 'empty'\n\t\t\t: counts.failedRows > 0\n\t\t\t\t? 'blocked'\n\t\t\t\t: counts.activeRows > 0 || counts.waitingRows > 0 || counts.stoppedRows > 0\n\t\t\t\t\t? 'active'\n\t\t\t\t\t: 'ready';\n\t}\n\treturn counts.failedRows > 0\n\t\t? 'blocked'\n\t\t: counts.activeRows > 0\n\t\t\t? 'active'\n\t\t\t: counts.total > 0 && counts.readyRows + counts.stoppedRows === counts.total\n\t\t\t\t? 'ready'\n\t\t\t\t: 'empty';\n};\n\n// -----------------------------------------------------------------------------\n// Status → glyph / color / label\n// -----------------------------------------------------------------------------\n\n/** Pure: status → ANSI glyph. */\nexport const statusGlyph = (status: LifecycleStatus): string => {\n\tswitch (status) {\n\t\tcase 'pending':\n\t\t\treturn '○';\n\t\tcase 'acquiring':\n\t\t\treturn '◐';\n\t\tcase 'ready':\n\t\t\treturn '●';\n\t\tcase 'failed':\n\t\t\treturn '✖';\n\t\tcase 'stopping':\n\t\t\treturn '◑';\n\t\tcase 'stopped':\n\t\t\treturn '◌';\n\t\tcase 'done':\n\t\t\treturn '✓';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = status;\n\t\t\tvoid _exhaustive;\n\t\t\treturn '?';\n\t\t}\n\t}\n};\n\n/** Pure: status → color token. */\nexport const statusColor = (status: LifecycleStatus): ColorToken => {\n\tswitch (status) {\n\t\tcase 'pending':\n\t\t\treturn 'white';\n\t\tcase 'acquiring':\n\t\t\treturn 'yellow';\n\t\tcase 'ready':\n\t\t\treturn 'green';\n\t\tcase 'failed':\n\t\t\treturn 'red';\n\t\tcase 'stopping':\n\t\t\treturn 'yellow';\n\t\tcase 'stopped':\n\t\t\treturn 'white';\n\t\tcase 'done':\n\t\t\treturn 'green';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = status;\n\t\t\tvoid _exhaustive;\n\t\t\treturn 'white';\n\t\t}\n\t}\n};\n\n/** Pure: status → short operator-facing label. */\nexport const statusLabel = (status: LifecycleStatus): string => {\n\tswitch (status) {\n\t\tcase 'pending':\n\t\t\treturn 'pending';\n\t\tcase 'acquiring':\n\t\t\treturn 'starting';\n\t\tcase 'ready':\n\t\t\treturn 'ready';\n\t\tcase 'failed':\n\t\t\treturn 'failed';\n\t\tcase 'stopping':\n\t\t\treturn 'stopping';\n\t\tcase 'stopped':\n\t\t\treturn 'stopped';\n\t\tcase 'done':\n\t\t\treturn 'done';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = status;\n\t\t\tvoid _exhaustive;\n\t\t\treturn 'unknown';\n\t\t}\n\t}\n};\n\n// -----------------------------------------------------------------------------\n// Role -> glyph / label / color\n// -----------------------------------------------------------------------------\n\n/** Pure: role -> ANSI glyph. */\nexport const roleGlyph = (role: PluginRole): string => {\n\tswitch (role) {\n\t\tcase 'service':\n\t\t\treturn '⚙';\n\t\tcase 'task':\n\t\t\treturn '⚡';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = role;\n\t\t\tvoid _exhaustive;\n\t\t\treturn '?';\n\t\t}\n\t}\n};\n\n/** Pure: role -> short label. */\nexport const roleLabel = (role: PluginRole): string => {\n\tswitch (role) {\n\t\tcase 'service':\n\t\t\treturn 'service';\n\t\tcase 'task':\n\t\t\treturn 'task';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = role;\n\t\t\tvoid _exhaustive;\n\t\t\treturn 'unknown';\n\t\t}\n\t}\n};\n\n/** Pure: role -> label color token. */\nexport const roleLabelColor = (role: PluginRole): ColorToken => {\n\tswitch (role) {\n\t\tcase 'service':\n\t\t\treturn 'cyan';\n\t\tcase 'task':\n\t\t\treturn 'magenta';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = role;\n\t\t\tvoid _exhaustive;\n\t\t\treturn 'white';\n\t\t}\n\t}\n};\n\n// -----------------------------------------------------------------------------\n// Row label — derived from `key`, not from any pre-baked title\n// -----------------------------------------------------------------------------\n\n/**\n * Compute a human-friendly row label from the opaque branded plugin\n * key. The substrate keeps `key` an opaque digest; the renderer is\n * the layer that interprets it. We strip a few conventional prefixes\n * (`devstack:`, `app:`) plus instance counters so common cases read\n * cleanly.\n *\n * The function consumes `key` only — never `row.title`, which doesn't\n * exist in the projection.\n */\nexport const labelForRow = (key: string): string => {\n\tconst normalized = normalizeKey(key);\n\tconst parts = normalized.split(/[/:]/).filter((part) => part.length > 0);\n\tif (parts.length >= 2 && isSectionish(parts[0]!)) return humanizeToken(parts[parts.length - 1]!);\n\treturn humanizeToken(parts[parts.length - 1] ?? normalized);\n};\n\nexport const ownerForRow = (key: string): string => {\n\tconst normalized = normalizeKey(key);\n\tconst first = normalized.split(/[/:._#-]/).find((part) => part.length > 0);\n\treturn first === undefined ? 'plugin' : humanizeToken(first);\n};\n\n/** Pure: pick the row's dashboard section. Reads `row.section`\n * directly; if the plugin declared an `endpointSection` AND the row\n * owns a routed endpoint, the endpoint variant wins. NEVER pattern-\n * matches on `row.key`; the classifier is plugin-declared at\n * `definePlugin({ section, endpointSection })` time and stamped by\n * the supervisor. */\nexport const sectionForRow = (\n\trow: Pick<Row, 'key' | 'endpoints' | 'section' | 'endpointSection'>,\n\tendpoints: ReadonlyArray<Endpoint> = [],\n): RowSection => {\n\tconst ownsEndpoint = endpointsForRow(row, endpoints).length > 0 || row.endpoints.length > 0;\n\treturn ownsEndpoint && row.endpointSection !== row.section ? row.endpointSection : row.section;\n};\n\n/** Pure: section -> scope-chip color token used by the event log /\n * activity stream. Coloring is driven by `RowSection` so the renderer\n * doesn't pattern-match on plugin names. */\nexport const sectionColor = (section: RowSection): ColorToken => {\n\tswitch (section) {\n\t\tcase 'service':\n\t\t\treturn 'cyan';\n\t\tcase 'package':\n\t\t\treturn 'blueBright';\n\t\tcase 'account':\n\t\t\treturn 'magenta';\n\t\tcase 'action':\n\t\t\treturn 'magenta';\n\t\tcase 'app':\n\t\t\treturn 'white';\n\t\tcase 'other':\n\t\t\treturn 'cyan';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = section;\n\t\t\tvoid _exhaustive;\n\t\t\treturn 'cyan';\n\t\t}\n\t}\n};\n\nexport const sectionLabel = (section: RowSection): string => {\n\tswitch (section) {\n\t\tcase 'service':\n\t\t\treturn 'Services';\n\t\tcase 'package':\n\t\t\treturn 'Packages';\n\t\tcase 'account':\n\t\t\treturn 'Accounts';\n\t\tcase 'action':\n\t\t\treturn 'Actions';\n\t\tcase 'app':\n\t\t\treturn 'App';\n\t\tcase 'other':\n\t\t\treturn 'Other';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = section;\n\t\t\tvoid _exhaustive;\n\t\t\treturn 'Other';\n\t\t}\n\t}\n};\n\nexport const endpointsForRow = (\n\trow: Pick<Row, 'key' | 'endpoints'>,\n\tendpoints: ReadonlyArray<Endpoint>,\n): ReadonlyArray<Endpoint> =>\n\tendpoints.filter(\n\t\t(endpoint) => row.endpoints.includes(endpoint.endpointKey) || endpoint.pluginKey === row.key,\n\t);\n\nconst OPERATIONAL_ENDPOINT_FIELDS = new Set(['url', 'rpcUrl', 'faucetUrl', 'graphqlUrl']);\n\nexport const visibleEndpointsForRow = (\n\trow: Pick<Row, 'key' | 'endpoints'>,\n\tendpoints: ReadonlyArray<Endpoint>,\n): ReadonlyArray<Endpoint> => {\n\tconst rowEndpoints = endpointsForRow(row, endpoints);\n\tconst routed = rowEndpoints.filter((endpoint) => !isOperationalEndpoint(row.key, endpoint));\n\treturn routed.length > 0 ? routed : rowEndpoints;\n};\n\nexport const headlineForRow = (row: Row, endpoints: ReadonlyArray<Endpoint> = []): string => {\n\tconst rowEndpoints = visibleEndpointsForRow(row, endpoints);\n\tif (rowEndpoints.length > 0) return endpointLine(rowEndpoints[0]!);\n\tif (row.lastError !== null && row.status === 'failed') return errorSummaryFor(row.lastError);\n\treturn narrationFor(row.phase, row.status);\n};\n\nexport const secondaryForRow = (\n\trow: Row,\n\tendpoints: ReadonlyArray<Endpoint> = [],\n): ReadonlyArray<string> => {\n\tconst out: Array<string> = [];\n\tconst rowEndpoints = endpointsForRow(row, endpoints);\n\tfor (const endpoint of rowEndpoints.slice(1)) out.push(endpointLine(endpoint));\n\tif (row.logTail.truncated) out.push('log tail truncated');\n\treturn out;\n};\n\nexport const groupRows = (\n\trows: ReadonlyArray<Row>,\n\tendpoints: ReadonlyArray<Endpoint> = [],\n): ReadonlyArray<DisplaySection> => {\n\tconst buckets = new Map<RowSection, Array<DisplayRow>>();\n\tfor (const row of rows) {\n\t\tconst cells = deriveDisplayCells(row, endpoints);\n\t\tconst list = buckets.get(cells.section) ?? [];\n\t\tlist.push({ row, cells });\n\t\tbuckets.set(cells.section, list);\n\t}\n\tconst order: ReadonlyArray<RowSection> = [\n\t\t'service',\n\t\t'package',\n\t\t'account',\n\t\t'action',\n\t\t'app',\n\t\t'other',\n\t];\n\treturn order\n\t\t.filter((section) => buckets.has(section))\n\t\t.map((section) => ({\n\t\t\tkey: section,\n\t\t\tlabel: sectionLabel(section),\n\t\t\trows: buckets.get(section) ?? [],\n\t\t}));\n};\n\n/**\n * TUI dashboard summary. Delegates to the canonical `deriveStackViewModel`\n * + `deriveHealth(counts, 'tui')`. The `waitingRows` field folds the\n * discrete `stoppedRows` back in (the TUI counts stopped rows as\n * \"waiting\") so `dashboardSummaryLine` and its consumers get the TUI's\n * expected shape.\n */\nexport const deriveDashboardSummary = (\n\tstate: Pick<SubscribableState, 'rows' | 'endpoints' | 'accounts' | 'packages' | 'errors'>,\n): DashboardSummary => {\n\tconst vm = deriveStackViewModel(state);\n\treturn {\n\t\ttotalRows: vm.total,\n\t\treadyRows: vm.readyRows,\n\t\tactiveRows: vm.activeRows,\n\t\tfailedRows: vm.failedRows,\n\t\t// TUI's `waitingRows` counts pending + stopped together.\n\t\twaitingRows: vm.waitingRows + vm.stoppedRows,\n\t\tendpointCount: vm.endpointCount,\n\t\taccountCount: vm.accountCount,\n\t\tpackageCount: vm.packageCount,\n\t\terrorCount: vm.errorCount,\n\t\thealth: deriveHealth(vm, 'tui'),\n\t};\n};\n\nconst visibleEndpointCount = (\n\trows: ReadonlyArray<Row>,\n\tendpoints: ReadonlyArray<Endpoint>,\n): number => {\n\tconst rowOwned = new Set<Endpoint['endpointKey']>();\n\tconst visible = new Set<Endpoint['endpointKey']>();\n\tfor (const row of rows) {\n\t\tfor (const endpoint of endpointsForRow(row, endpoints)) {\n\t\t\trowOwned.add(endpoint.endpointKey);\n\t\t}\n\t\tfor (const endpoint of visibleEndpointsForRow(row, endpoints)) {\n\t\t\tvisible.add(endpoint.endpointKey);\n\t\t}\n\t}\n\tfor (const endpoint of endpoints) {\n\t\tif (!rowOwned.has(endpoint.endpointKey)) visible.add(endpoint.endpointKey);\n\t}\n\treturn visible.size;\n};\n\nexport const dashboardSummaryLine = (summary: DashboardSummary): string =>\n\t[\n\t\t`${summary.readyRows}/${summary.totalRows} ready`,\n\t\tsummary.activeRows > 0 ? `${summary.activeRows} active` : null,\n\t\tsummary.waitingRows > 0 ? `${summary.waitingRows} waiting` : null,\n\t\tsummary.failedRows > 0 ? `${summary.failedRows} failed` : null,\n\t\t`${summary.endpointCount} urls`,\n\t\t`${summary.accountCount} accounts`,\n\t\tsummary.packageCount > 0 ? `${summary.packageCount} packages` : null,\n\t\tsummary.errorCount === 0 ? 'no errors' : `${summary.errorCount} errors`,\n\t]\n\t\t.filter((part): part is string => part !== null)\n\t\t.join(' ');\n\n// -----------------------------------------------------------------------------\n// Phase narration formatting\n// -----------------------------------------------------------------------------\n\n/**\n * Pure: phase narration string → renderable verb form. Phase is\n * free-form upstream (Tension 14). The renderer's job is to make it\n * legible; we keep this minimal — single-line, no fancy verb tense\n * inference. The truncation cap protects against pathological long\n * narrations.\n */\nexport const narrationFor = (phase: PhaseNarration | null, status: LifecycleStatus): string => {\n\tif (phase === null || phase.trim().length === 0) {\n\t\t// No phase set; surface a status-derived default for `acquiring`,\n\t\t// stay silent otherwise (Tension: don't manufacture text the\n\t\t// engine didn't ask for).\n\t\treturn status === 'acquiring' ? 'starting…' : '';\n\t}\n\tconst trimmed = phase.trim();\n\tconst TRUNC = 80;\n\treturn trimmed.length > TRUNC ? `${trimmed.slice(0, TRUNC - 1)}…` : trimmed;\n};\n\n// -----------------------------------------------------------------------------\n// Error summary formatting\n// -----------------------------------------------------------------------------\n\nconst ERR_SUMMARY_TRUNC = 120;\n\n/**\n * Pure: `Row.lastError` → renderable in-row summary. Keep it short;\n * detailed error context belongs in the activity/log stream.\n */\nexport const errorSummaryFor = (error: StructuredError | null): string => {\n\tif (error === null) return '';\n\tconst head = `${error.tag}: ${error.summary}`;\n\tconst detail = error.chain.find((line) => line !== head && line !== error.tag);\n\tconst compactDetail =\n\t\tdetail === undefined ? undefined : detail.replace(/^[A-Za-z0-9_[\\]-]+:\\s*/, '');\n\tconst summary = compactDetail === undefined ? head : `${head}: ${compactDetail}`;\n\treturn summary.length > ERR_SUMMARY_TRUNC\n\t\t? `${summary.slice(0, ERR_SUMMARY_TRUNC - 1)}…`\n\t\t: summary;\n};\n\n// -----------------------------------------------------------------------------\n// Endpoint formatting\n// -----------------------------------------------------------------------------\n\n/**\n * Pure: `Endpoint` → renderable line. Renderers prefer `displayUrl`\n * when set (codegen-friendly variant), falling back to `url`.\n */\nexport const endpointLine = (endpoint: Endpoint): string => {\n\tconst target = endpoint.displayUrl ?? endpoint.url;\n\tconst backing =\n\t\tendpoint.displayUrl !== null && endpoint.displayUrl !== endpoint.url\n\t\t\t? ` -> ${endpoint.url}`\n\t\t\t: '';\n\tconst protocol = endpoint.wireProtocol === 'http' ? '' : ` [${endpoint.wireProtocol}]`;\n\treturn `${endpoint.name}: ${target}${backing}${protocol}`;\n};\n\nexport const accountLine = (account: AccountProjection): string => {\n\tconst address = account.address ?? '<pending>';\n\tconst scheme = account.scheme ?? 'scheme pending';\n\tconst source = account.source ?? 'source pending';\n\tconst funding = accountFundingLine(account.funding);\n\tconst wallet = account.walletVisible ? 'wallet' : null;\n\treturn [humanizeToken(account.name), address, scheme, source, funding, wallet]\n\t\t.filter((part): part is string => part !== null && part.length > 0)\n\t\t.join(' ');\n};\n\nexport const packageLine = (pkg: PackageProjection): string => {\n\treturn [humanizeToken(pkg.name), pkg.packageId, pkg.mvrPlaceholder, pkg.kind]\n\t\t.filter((part): part is string => part !== null && part.length > 0)\n\t\t.join(' ');\n};\n\nexport interface AccountCells {\n\treadonly name: string;\n\treadonly address: string;\n\treadonly scheme: string;\n\treadonly source: string;\n\treadonly funding: string;\n}\n\nexport interface PackageCells {\n\treadonly name: string;\n\treadonly packageId: string;\n\treadonly mvr: string;\n\treadonly kind: string;\n\treadonly detail: string;\n}\n\nexport const accountCells = (account: AccountProjection): AccountCells => ({\n\tname: humanizeToken(account.name),\n\taddress: account.address ?? '<pending>',\n\tscheme: account.scheme ?? 'pending',\n\tsource: account.source ?? 'pending',\n\tfunding: accountFundingLine(account.funding),\n});\n\nconst accountFundingLine = (funding: AccountProjection['funding']): string => {\n\tconst entries = funding.entries ?? [];\n\tif (entries.length > 0) {\n\t\tconst rendered = entries\n\t\t\t.map(\n\t\t\t\t(entry) => `${entry.coin}:${entry.amount}${entry.status === 'skipped' ? ' skipped' : ''}`,\n\t\t\t)\n\t\t\t.join(', ');\n\t\treturn funding.status === 'funded' ? `funded ${rendered}` : `funding ${rendered}`;\n\t}\n\tswitch (funding.status) {\n\t\tcase 'pending':\n\t\t\treturn funding.requestedMist === null\n\t\t\t\t? 'funding pending'\n\t\t\t\t: `funding ${funding.requestedMist}`;\n\t\tcase 'funded':\n\t\t\treturn funding.balanceMist !== null\n\t\t\t\t? `funded ${funding.balanceMist}`\n\t\t\t\t: funding.requestedMist !== null\n\t\t\t\t\t? `funded ${funding.requestedMist}`\n\t\t\t\t\t: 'funded';\n\t\tcase 'skipped':\n\t\t\treturn 'funding skipped';\n\t\tcase 'failed':\n\t\t\treturn 'funding failed';\n\t\tcase 'unknown':\n\t\t\treturn 'funding unknown';\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = funding.status;\n\t\t\tvoid _exhaustive;\n\t\t\treturn '';\n\t\t}\n\t}\n};\n\nexport const packageCells = (pkg: PackageProjection): PackageCells => ({\n\tname: humanizeToken(pkg.name),\n\tpackageId: pkg.packageId,\n\tmvr: pkg.mvrPlaceholder,\n\tkind: pkg.kind,\n\tdetail: pkg.kind,\n});\n\n// -----------------------------------------------------------------------------\n// Top-level entry point\n// -----------------------------------------------------------------------------\n\n/**\n * Derive every visible display cell for a single row.\n *\n * Consumes ONLY `row.key`, `row.role`, `row.status`, `row.phase`,\n * `row.lastError`, `row.endpoints` (rendered separately by\n * `resource-table.tsx`), or `row.logTail` (kept in the projection\n * for renderer variants that need tails, but not used as primary\n * dashboard state because generic lifecycle log lines duplicate the\n * table).\n */\nexport const deriveDisplayCells = (\n\trow: Row,\n\tendpoints: ReadonlyArray<Endpoint> = [],\n): DisplayCells => ({\n\tstatusGlyph: statusGlyph(row.status),\n\tstatusColor: statusColor(row.status),\n\tstatusLabel: statusLabel(row.status),\n\troleGlyph: roleGlyph(row.role),\n\troleLabel: roleLabel(row.role),\n\tlabel: labelForRow(row.key),\n\tnarration: narrationFor(row.phase, row.status),\n\terrorSummary: errorSummaryFor(row.lastError),\n\tlabelColor: roleLabelColor(row.role),\n\tsection: sectionForRow(row, endpoints),\n\towner: ownerForRow(row.key),\n\theadline: headlineForRow(row, endpoints),\n\tsecondary: secondaryForRow(row, endpoints),\n});\n\nconst normalizeKey = (key: string): string =>\n\tkey\n\t\t.replace(/^@devstack\\//, '')\n\t\t.replace(/^(devstack:|app:)/, '')\n\t\t.replace(/#\\d+$/, '')\n\t\t.replace(/\\/\\d+$/, '');\n\nconst humanizeToken = (token: string): string =>\n\ttoken.replace(/[-_]+/g, ' ').replace(/^\\w/, (head) => head.toUpperCase());\n\nconst containsKeyPart = (key: string, parts: ReadonlyArray<string>): boolean => {\n\tconst tokens = key.split(/[/:._#-]/).filter((part) => part.length > 0);\n\treturn tokens.some((token) => parts.includes(token));\n};\n\nconst isSectionish = (token: string): boolean =>\n\tcontainsKeyPart(token.toLowerCase(), ['service', 'package', 'account', 'action', 'app']);\n\nconst isOperationalEndpoint = (rowKey: Pick<Row, 'key'>['key'], endpoint: Endpoint): boolean => {\n\tif (endpoint.pluginKey !== rowKey) return false;\n\tconst prefix = `${rowKey}:`;\n\tif (!endpoint.endpointKey.startsWith(prefix)) return false;\n\tconst field = endpoint.endpointKey.slice(prefix.length);\n\treturn OPERATIONAL_ENDPOINT_FIELDS.has(field);\n};\n\n// -----------------------------------------------------------------------------\n// Compile-time invariant\n// -----------------------------------------------------------------------------\n//\n// Renderer code must never see a `title`/`primary`/`extras` field on\n// `Row`. If the projection ever sprouts one, the substrate's\n// `__ProjectionFieldsClosed` guard fails first; this module-local\n// guard exists as a second layer that explicitly mentions the\n// forbidden field names so a future renderer-side regression is\n// caught at the renderer boundary, not at the substrate boundary.\n\ntype _NoForbiddenOnRow = 'title' extends keyof Row\n\t? never\n\t: 'primary' extends keyof Row\n\t\t? never\n\t\t: 'extras' extends keyof Row\n\t\t\t? never\n\t\t\t: true;\n\n/** Renderer-side compile-time guard. If this resolves to `never`,\n * the renderer is reading a forbidden display-vocabulary field from\n * the projection. */\nexport type __TuiDisplayVocabClean = _NoForbiddenOnRow extends true ? true : never;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"errors.mjs","names":[],"sources":["../../../src/surfaces/tui/errors.ts"],"sourcesContent":["// TUI surface errors.\n//\n// Architecture §11 (Renderer): the renderer surface satisfies the\n// `Renderer` contract from `contracts/renderer.ts`, which already\n// defines `RendererError`. The TUI surface adds NO new error tags of\n// its own beyond the contract's two reasons — `mount-failed` and\n// `subscription-lost`. Anything that would be a third reason is\n// either upstream (lifecycle event from the engine, projected into\n// `state.errors[]`) or a programmer-error defect (Effect.die).\n//\n// This module exists as a single import site for the TUI's error\n// constructors so the surface code never reaches across the substrate\n// boundary directly when fabricating its typed failures.\n\nimport type { RendererError } from '../../contracts/renderer.ts';\n\n/** Construct a `mount-failed` renderer error. */\nexport const mountFailed = (detail: string): RendererError => ({\n\t_tag: 'RendererError',\n\treason: 'mount-failed',\n\tdetail,\n});\n\n/** Construct a `subscription-lost` renderer error. */\nexport const subscriptionLost = (detail: string): RendererError => ({\n\t_tag: 'RendererError',\n\treason: 'subscription-lost',\n\tdetail,\n});\n\n// Re-export the shared contract type so callers in this surface only\n// need to import from one place.\nexport type { RendererError };\n"],"mappings":";;AAiBA,MAAa,eAAe,YAAmC;CAC9D,MAAM;CACN,QAAQ;CACR;CACA"}
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../../../src/surfaces/tui/errors.ts"],"sourcesContent":["// TUI surface errors.\n//\n// Architecture §11 (Renderer): the renderer surface satisfies the\n// `Renderer` contract from `contracts/renderer.ts`, which already\n// defines `RendererError`. The TUI surface adds NO new error tags of\n// its own beyond the contract's two reasons — `mount-failed` and\n// `subscription-lost`. Anything that would be a third reason is\n// either upstream (lifecycle event from the engine, projected into\n// `state.errors[]`) or a programmer-error defect (Effect.die).\n//\n// This module exists as a single import site for the TUI's error\n// constructors so the surface code never reaches across the substrate\n// boundary directly when fabricating its typed failures.\n\nimport type { RendererError } from '../../contracts/renderer.ts';\n\n/** Construct a `mount-failed` renderer error. */\nexport const mountFailed = (detail: string): RendererError => ({\n\t_tag: 'RendererError',\n\treason: 'mount-failed',\n\tdetail,\n});\n\n// Re-export the shared contract type so callers in this surface only\n// need to import from one place.\nexport type { RendererError };\n"],"mappings":";;AAiBA,MAAa,eAAe,YAAmC;CAC9D,MAAM;CACN,QAAQ;CACR;AACD"}
@@ -52,15 +52,6 @@ const eventLogLineFromEvent = (event, seq, sectionLookup = () => void 0) => {
52
52
  scopeColor: "white",
53
53
  message: `shutdown hard-kill escalated by ${event.signal} (exit ${event.exitCode})`
54
54
  });
55
- case "snapshot.captureStarted": return line({
56
- id,
57
- level: "info",
58
- at,
59
- scope: "Snapshot",
60
- scopeColor: "blueBright",
61
- message: event.name === void 0 ? "capture started" : `capture started for ${event.name}`
62
- });
63
- case "snapshot.captureProgress": return null;
64
55
  case "snapshot.captureSkipped": return line({
65
56
  id,
66
57
  level: "warn",
@@ -1 +1 @@
1
- {"version":3,"file":"event-log.mjs","names":[],"sources":["../../../src/surfaces/tui/event-log.ts"],"sourcesContent":["import type { EngineEvent } from '../../substrate/events.ts';\nimport type { RowSection } from '../../substrate/projection.ts';\nimport {\n\terrorSummaryFor,\n\tlabelForRow,\n\tsectionColor,\n\ttype ColorToken,\n} from './display-derivation.ts';\nimport { eventAt } from '../../substrate/event-time.ts';\n\nexport interface EventLogLine {\n\treadonly id: string;\n\treadonly time: string;\n\treadonly scope: string;\n\treadonly scopeColor: ColorToken;\n\treadonly message: string;\n\treadonly text: string;\n\treadonly level: 'info' | 'warn' | 'error';\n}\n\nexport const MAX_EVENT_LOG_LINES = 200;\n\n/** Pure: lookup a row's plugin-declared section by `pluginKey`. The\n * event log accepts this as a parameter rather than computing it\n * itself — the substrate forbids the renderer from pattern-matching\n * plugin-name substrings. Callers (e.g. `app.tsx`) build the lookup\n * from the live `state.rows` projection. */\nexport type SectionLookup = (pluginKey: string) => RowSection | undefined;\n\nexport const eventLogLineFromEvent = (\n\tevent: EngineEvent,\n\tseq: number,\n\tsectionLookup: SectionLookup = () => undefined,\n): EventLogLine | null => {\n\tconst at = eventAt(event);\n\tconst id = `${at}-${seq}-${event.tag}`;\n\tconst scopeColorFor = (pluginKey: string): ColorToken =>\n\t\tsectionColor(sectionLookup(pluginKey) ?? 'other');\n\tswitch (event.tag) {\n\t\tcase 'log.appended':\n\t\t\tif (event.level === 'info' || isRedundantPluginLog(event.line)) return null;\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tat,\n\t\t\t\tlevel: event.level,\n\t\t\t\tscope: labelForRow(event.pluginKey),\n\t\t\t\tscopeColor: scopeColorFor(event.pluginKey),\n\t\t\t\tmessage: event.line,\n\t\t\t});\n\t\tcase 'error.reported':\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: event.error.severity === 'warn' ? 'warn' : 'error',\n\t\t\t\tat,\n\t\t\t\tscope: event.error.pluginKey === null ? 'Stack' : labelForRow(event.error.pluginKey),\n\t\t\t\tscopeColor: event.error.pluginKey === null ? 'white' : scopeColorFor(event.error.pluginKey),\n\t\t\t\tmessage: `failed: ${errorSummaryFor(event.error)}`,\n\t\t\t});\n\t\tcase 'build.statusChanged':\n\t\tcase 'lifecycle.statusChanged':\n\t\tcase 'lifecycle.phaseSet':\n\t\tcase 'endpoint.registered':\n\t\tcase 'projection.updated':\n\t\t\treturn null;\n\t\tcase 'restart.requested':\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: 'warn',\n\t\t\t\tat,\n\t\t\t\tscope: 'Stack',\n\t\t\t\tscopeColor: 'white',\n\t\t\t\tmessage: `restart requested for ${targetLabel(event.target)}`,\n\t\t\t});\n\t\tcase 'restart.completed':\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: 'info',\n\t\t\t\tat,\n\t\t\t\tscope: 'Stack',\n\t\t\t\tscopeColor: 'white',\n\t\t\t\tmessage: `restart completed for ${targetLabel(event.target)}`,\n\t\t\t});\n\t\tcase 'shutdown.escalated':\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: 'warn',\n\t\t\t\tat,\n\t\t\t\tscope: 'Stack',\n\t\t\t\tscopeColor: 'white',\n\t\t\t\tmessage: `shutdown hard-kill escalated by ${event.signal} (exit ${event.exitCode})`,\n\t\t\t});\n\t\tcase 'snapshot.captureStarted':\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: 'info',\n\t\t\t\tat,\n\t\t\t\tscope: 'Snapshot',\n\t\t\t\tscopeColor: 'blueBright',\n\t\t\t\tmessage: event.name === undefined ? 'capture started' : `capture started for ${event.name}`,\n\t\t\t});\n\t\tcase 'snapshot.captureProgress':\n\t\t\treturn null;\n\t\tcase 'snapshot.captureSkipped':\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: 'warn',\n\t\t\t\tat,\n\t\t\t\tscope: 'Snapshot',\n\t\t\t\tscopeColor: 'blueBright',\n\t\t\t\tmessage: 'capture already running',\n\t\t\t});\n\t\tcase 'snapshot.captureFailed':\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: 'error',\n\t\t\t\tat,\n\t\t\t\tscope: 'Snapshot',\n\t\t\t\tscopeColor: 'blueBright',\n\t\t\t\tmessage:\n\t\t\t\t\tevent.name === undefined\n\t\t\t\t\t\t? `capture failed: ${event.summary}`\n\t\t\t\t\t\t: `capture failed for ${event.name}: ${event.summary}`,\n\t\t\t});\n\t\tcase 'snapshot.captured':\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: 'info',\n\t\t\t\tat,\n\t\t\t\tscope: 'Snapshot',\n\t\t\t\tscopeColor: 'blueBright',\n\t\t\t\tmessage:\n\t\t\t\t\tevent.name === undefined\n\t\t\t\t\t\t? `captured ${event.snapshotId}`\n\t\t\t\t\t\t: `captured ${event.name} (${event.snapshotId})`,\n\t\t\t});\n\t\tcase 'snapshot.restored':\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: 'info',\n\t\t\t\tat,\n\t\t\t\tscope: 'Snapshot',\n\t\t\t\tscopeColor: 'blueBright',\n\t\t\t\tmessage: `restored ${event.snapshotId}`,\n\t\t\t});\n\t\tcase 'engine.orchestrator.dispatchFailed':\n\t\t\t// A capability sink (e.g. `routable`) rejected, but the plugin is\n\t\t\t// left `ready` on purpose (non-fatal sink — see\n\t\t\t// dispatch-contributions.ts). Previously suppressed here, which\n\t\t\t// meant a live-dashboard operator saw a green stack with dead\n\t\t\t// RPC/wallet routing. Surface it as a warning so the failure is\n\t\t\t// visible; lead with the cause `_tag` when present so the line\n\t\t\t// names WHICH orchestrator broke.\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: 'warn',\n\t\t\t\tat,\n\t\t\t\tscope: labelForRow(event.pluginKey),\n\t\t\t\tscopeColor: scopeColorFor(event.pluginKey),\n\t\t\t\tmessage:\n\t\t\t\t\tevent.causeType === undefined\n\t\t\t\t\t\t? `routing sink '${event.kind}' failed: ${event.message}`\n\t\t\t\t\t\t: `routing sink '${event.kind}' failed (${event.causeType}): ${event.message}`,\n\t\t\t});\n\t\tcase 'endpoint.released':\n\t\tcase 'strategy.registered':\n\t\tcase 'strategy.unregistered':\n\t\tcase 'manifest.flushed':\n\t\tcase 'codegen.emitted':\n\t\t\treturn null;\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = event;\n\t\t\tvoid _exhaustive;\n\t\t\treturn null;\n\t\t}\n\t}\n};\n\nexport const shutdownRequestedLine = (at: number, seq: number): EventLogLine => ({\n\tid: `${at}-${seq}-shutdown.requested`,\n\ttime: time(at),\n\tscope: 'Stack',\n\tscopeColor: 'white',\n\tmessage: 'shutdown requested; waiting for graceful stop',\n\tlevel: 'warn',\n\ttext: `${time(at)} shutdown requested; waiting for graceful stop`,\n});\n\nexport const appendEventLogLine = (\n\tlines: ReadonlyArray<EventLogLine>,\n\tnext: EventLogLine | null,\n): ReadonlyArray<EventLogLine> => {\n\tif (next === null) return lines;\n\tconst appended = [...lines, next];\n\treturn appended.length > MAX_EVENT_LOG_LINES ? appended.slice(-MAX_EVENT_LOG_LINES) : appended;\n};\n\n/**\n * Batched variant of `appendEventLogLine`: append many lines, slice to\n * the bound ONCE. The per-event variant rebuilt the array on every\n * call; a 100-event burst therefore allocated 100 arrays and ran the\n * `.slice(-MAX_EVENT_LOG_LINES)` projection 100 times. The plural form\n * collapses that to one allocation + one bound check — matching the\n * microtask-batched dispatch path in `app.tsx`.\n *\n * `nexts` may contain `null` entries (the same null-pass-through\n * contract as the singular form) — they're filtered before append.\n * Returns the input array unchanged when no real lines remain so\n * `setEventLog`'s referential-equality short-circuit still fires.\n */\nexport const appendEventLogLines = (\n\tlines: ReadonlyArray<EventLogLine>,\n\tnexts: ReadonlyArray<EventLogLine | null>,\n): ReadonlyArray<EventLogLine> => {\n\tconst real: Array<EventLogLine> = [];\n\tfor (const line of nexts) {\n\t\tif (line !== null) real.push(line);\n\t}\n\tif (real.length === 0) return lines;\n\tconst appended = lines.length === 0 ? real : [...lines, ...real];\n\treturn appended.length > MAX_EVENT_LOG_LINES ? appended.slice(-MAX_EVENT_LOG_LINES) : appended;\n};\n\nconst time = (at: number): string => new Date(at).toISOString().slice(11, 19);\n\nconst targetLabel = (target: 'stack' | { readonly pluginKey: string }): string =>\n\ttarget === 'stack' ? 'stack' : target.pluginKey;\n\nconst line = (input: {\n\treadonly id: string;\n\treadonly at: number;\n\treadonly level: EventLogLine['level'];\n\treadonly scope: string;\n\treadonly scopeColor: ColorToken;\n\treadonly message: string;\n}): EventLogLine => {\n\tconst renderedTime = time(input.at);\n\treturn {\n\t\tid: input.id,\n\t\ttime: renderedTime,\n\t\tscope: input.scope,\n\t\tscopeColor: input.scopeColor,\n\t\tmessage: input.message,\n\t\tlevel: input.level,\n\t\ttext: `${renderedTime} ${input.scope}: ${input.message}`,\n\t};\n};\n\nconst isRedundantPluginLog = (message: string): boolean => {\n\tconst normalized = message.trim().toLowerCase();\n\treturn (\n\t\tnormalized === 'plugin acquire start' ||\n\t\tnormalized === 'plugin ready' ||\n\t\tnormalized === 'plugin acquire failed'\n\t);\n};\n\n// Scope-chip color is driven by `Row.section`, looked up via the\n// `sectionLookup` argument the host (app.tsx / tests) constructs from\n// the live projection. Keeps the renderer name-blind: it sees only the\n// closed `RowSection` vocabulary (`service` / `package` / `account` /\n// ...), never a substring of a plugin name. Plugin authors declare\n// their section once via `definePlugin({ section })`.\n"],"mappings":"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"}
1
+ {"version":3,"file":"event-log.mjs","names":[],"sources":["../../../src/surfaces/tui/event-log.ts"],"sourcesContent":["import type { EngineEvent } from '../../substrate/events.ts';\nimport type { RowSection } from '../../substrate/projection.ts';\nimport {\n\terrorSummaryFor,\n\tlabelForRow,\n\tsectionColor,\n\ttype ColorToken,\n} from './display-derivation.ts';\nimport { eventAt } from '../../substrate/event-time.ts';\n\nexport interface EventLogLine {\n\treadonly id: string;\n\treadonly time: string;\n\treadonly scope: string;\n\treadonly scopeColor: ColorToken;\n\treadonly message: string;\n\treadonly text: string;\n\treadonly level: 'info' | 'warn' | 'error';\n}\n\nexport const MAX_EVENT_LOG_LINES = 200;\n\n/** Pure: lookup a row's plugin-declared section by `pluginKey`. The\n * event log accepts this as a parameter rather than computing it\n * itself — the substrate forbids the renderer from pattern-matching\n * plugin-name substrings. Callers (e.g. `app.tsx`) build the lookup\n * from the live `state.rows` projection. */\nexport type SectionLookup = (pluginKey: string) => RowSection | undefined;\n\nexport const eventLogLineFromEvent = (\n\tevent: EngineEvent,\n\tseq: number,\n\tsectionLookup: SectionLookup = () => undefined,\n): EventLogLine | null => {\n\tconst at = eventAt(event);\n\tconst id = `${at}-${seq}-${event.tag}`;\n\tconst scopeColorFor = (pluginKey: string): ColorToken =>\n\t\tsectionColor(sectionLookup(pluginKey) ?? 'other');\n\tswitch (event.tag) {\n\t\tcase 'log.appended':\n\t\t\tif (event.level === 'info' || isRedundantPluginLog(event.line)) return null;\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tat,\n\t\t\t\tlevel: event.level,\n\t\t\t\tscope: labelForRow(event.pluginKey),\n\t\t\t\tscopeColor: scopeColorFor(event.pluginKey),\n\t\t\t\tmessage: event.line,\n\t\t\t});\n\t\tcase 'error.reported':\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: event.error.severity === 'warn' ? 'warn' : 'error',\n\t\t\t\tat,\n\t\t\t\tscope: event.error.pluginKey === null ? 'Stack' : labelForRow(event.error.pluginKey),\n\t\t\t\tscopeColor: event.error.pluginKey === null ? 'white' : scopeColorFor(event.error.pluginKey),\n\t\t\t\tmessage: `failed: ${errorSummaryFor(event.error)}`,\n\t\t\t});\n\t\tcase 'build.statusChanged':\n\t\tcase 'lifecycle.statusChanged':\n\t\tcase 'lifecycle.phaseSet':\n\t\tcase 'endpoint.registered':\n\t\tcase 'projection.updated':\n\t\t\treturn null;\n\t\tcase 'restart.requested':\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: 'warn',\n\t\t\t\tat,\n\t\t\t\tscope: 'Stack',\n\t\t\t\tscopeColor: 'white',\n\t\t\t\tmessage: `restart requested for ${targetLabel(event.target)}`,\n\t\t\t});\n\t\tcase 'restart.completed':\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: 'info',\n\t\t\t\tat,\n\t\t\t\tscope: 'Stack',\n\t\t\t\tscopeColor: 'white',\n\t\t\t\tmessage: `restart completed for ${targetLabel(event.target)}`,\n\t\t\t});\n\t\tcase 'shutdown.escalated':\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: 'warn',\n\t\t\t\tat,\n\t\t\t\tscope: 'Stack',\n\t\t\t\tscopeColor: 'white',\n\t\t\t\tmessage: `shutdown hard-kill escalated by ${event.signal} (exit ${event.exitCode})`,\n\t\t\t});\n\t\tcase 'snapshot.captureSkipped':\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: 'warn',\n\t\t\t\tat,\n\t\t\t\tscope: 'Snapshot',\n\t\t\t\tscopeColor: 'blueBright',\n\t\t\t\tmessage: 'capture already running',\n\t\t\t});\n\t\tcase 'snapshot.captureFailed':\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: 'error',\n\t\t\t\tat,\n\t\t\t\tscope: 'Snapshot',\n\t\t\t\tscopeColor: 'blueBright',\n\t\t\t\tmessage:\n\t\t\t\t\tevent.name === undefined\n\t\t\t\t\t\t? `capture failed: ${event.summary}`\n\t\t\t\t\t\t: `capture failed for ${event.name}: ${event.summary}`,\n\t\t\t});\n\t\tcase 'snapshot.captured':\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: 'info',\n\t\t\t\tat,\n\t\t\t\tscope: 'Snapshot',\n\t\t\t\tscopeColor: 'blueBright',\n\t\t\t\tmessage:\n\t\t\t\t\tevent.name === undefined\n\t\t\t\t\t\t? `captured ${event.snapshotId}`\n\t\t\t\t\t\t: `captured ${event.name} (${event.snapshotId})`,\n\t\t\t});\n\t\tcase 'snapshot.restored':\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: 'info',\n\t\t\t\tat,\n\t\t\t\tscope: 'Snapshot',\n\t\t\t\tscopeColor: 'blueBright',\n\t\t\t\tmessage: `restored ${event.snapshotId}`,\n\t\t\t});\n\t\tcase 'engine.orchestrator.dispatchFailed':\n\t\t\t// A buffered contribution (e.g. `routable`) failed when the\n\t\t\t// supervisor replayed it through the `ContributionDispatcher`, but\n\t\t\t// the plugin is left `ready` on purpose (non-fatal dispatch — see\n\t\t\t// `dispatchBufferedContributions` in acquire-node.ts). Previously\n\t\t\t// suppressed here, which meant a live-dashboard operator saw a\n\t\t\t// green stack with dead RPC/wallet routing. Surface it as a\n\t\t\t// warning so the failure is visible; lead with the cause `_tag`\n\t\t\t// when present so the line names WHICH orchestrator broke.\n\t\t\treturn line({\n\t\t\t\tid,\n\t\t\t\tlevel: 'warn',\n\t\t\t\tat,\n\t\t\t\tscope: labelForRow(event.pluginKey),\n\t\t\t\tscopeColor: scopeColorFor(event.pluginKey),\n\t\t\t\tmessage:\n\t\t\t\t\tevent.causeType === undefined\n\t\t\t\t\t\t? `routing sink '${event.kind}' failed: ${event.message}`\n\t\t\t\t\t\t: `routing sink '${event.kind}' failed (${event.causeType}): ${event.message}`,\n\t\t\t});\n\t\tcase 'endpoint.released':\n\t\tcase 'strategy.registered':\n\t\tcase 'strategy.unregistered':\n\t\tcase 'manifest.flushed':\n\t\tcase 'codegen.emitted':\n\t\t\treturn null;\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = event;\n\t\t\tvoid _exhaustive;\n\t\t\treturn null;\n\t\t}\n\t}\n};\n\nexport const shutdownRequestedLine = (at: number, seq: number): EventLogLine => ({\n\tid: `${at}-${seq}-shutdown.requested`,\n\ttime: time(at),\n\tscope: 'Stack',\n\tscopeColor: 'white',\n\tmessage: 'shutdown requested; waiting for graceful stop',\n\tlevel: 'warn',\n\ttext: `${time(at)} shutdown requested; waiting for graceful stop`,\n});\n\nexport const appendEventLogLine = (\n\tlines: ReadonlyArray<EventLogLine>,\n\tnext: EventLogLine | null,\n): ReadonlyArray<EventLogLine> => {\n\tif (next === null) return lines;\n\tconst appended = [...lines, next];\n\treturn appended.length > MAX_EVENT_LOG_LINES ? appended.slice(-MAX_EVENT_LOG_LINES) : appended;\n};\n\n/**\n * Batched variant of `appendEventLogLine`: append many lines, slice to\n * the bound ONCE. The per-event variant rebuilt the array on every\n * call; a 100-event burst therefore allocated 100 arrays and ran the\n * `.slice(-MAX_EVENT_LOG_LINES)` projection 100 times. The plural form\n * collapses that to one allocation + one bound check — matching the\n * microtask-batched dispatch path in `app.tsx`.\n *\n * `nexts` may contain `null` entries (the same null-pass-through\n * contract as the singular form) — they're filtered before append.\n * Returns the input array unchanged when no real lines remain so\n * `setEventLog`'s referential-equality short-circuit still fires.\n */\nexport const appendEventLogLines = (\n\tlines: ReadonlyArray<EventLogLine>,\n\tnexts: ReadonlyArray<EventLogLine | null>,\n): ReadonlyArray<EventLogLine> => {\n\tconst real: Array<EventLogLine> = [];\n\tfor (const line of nexts) {\n\t\tif (line !== null) real.push(line);\n\t}\n\tif (real.length === 0) return lines;\n\tconst appended = lines.length === 0 ? real : [...lines, ...real];\n\treturn appended.length > MAX_EVENT_LOG_LINES ? appended.slice(-MAX_EVENT_LOG_LINES) : appended;\n};\n\nconst time = (at: number): string => new Date(at).toISOString().slice(11, 19);\n\nconst targetLabel = (target: 'stack' | { readonly pluginKey: string }): string =>\n\ttarget === 'stack' ? 'stack' : target.pluginKey;\n\nconst line = (input: {\n\treadonly id: string;\n\treadonly at: number;\n\treadonly level: EventLogLine['level'];\n\treadonly scope: string;\n\treadonly scopeColor: ColorToken;\n\treadonly message: string;\n}): EventLogLine => {\n\tconst renderedTime = time(input.at);\n\treturn {\n\t\tid: input.id,\n\t\ttime: renderedTime,\n\t\tscope: input.scope,\n\t\tscopeColor: input.scopeColor,\n\t\tmessage: input.message,\n\t\tlevel: input.level,\n\t\ttext: `${renderedTime} ${input.scope}: ${input.message}`,\n\t};\n};\n\nconst isRedundantPluginLog = (message: string): boolean => {\n\tconst normalized = message.trim().toLowerCase();\n\treturn (\n\t\tnormalized === 'plugin acquire start' ||\n\t\tnormalized === 'plugin ready' ||\n\t\tnormalized === 'plugin acquire failed'\n\t);\n};\n\n// Scope-chip color is driven by `Row.section`, looked up via the\n// `sectionLookup` argument the host (app.tsx / tests) constructs from\n// the live projection. Keeps the renderer name-blind: it sees only the\n// closed `RowSection` vocabulary (`service` / `package` / `account` /\n// ...), never a substring of a plugin name. Plugin authors declare\n// their section once via `definePlugin({ section })`.\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"heartbeat.mjs","names":[],"sources":["../../../src/surfaces/tui/heartbeat.tsx"],"sourcesContent":["// Heartbeat indicator.\n//\n// Distilled/21-tui § Invariants: \"Frame stability: rendering must\n// short-circuit when nothing has changed.\" The heartbeat indicator\n// gives the operator a visible signal that the renderer is alive\n// even when the projection isn't ticking — without re-rendering the\n// whole tree. It's a single character that cycles through a small\n// alphabet on a clock; the dashboard places it in the header.\n//\n// We use `useEffect` + `setInterval` so the heartbeat doesn't depend\n// on the projection at all. When the projection IS ticking, frames\n// are driven by the SubscriptionRef subscription; the heartbeat\n// continues to spin to give long-quiet-stack feedback.\n\nimport { Text } from 'ink';\nimport type React from 'react';\nimport { useEffect, useState } from 'react';\n\nimport type { ColorToken } from './display-derivation.ts';\n\nconst FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] as const;\n\nexport interface HeartbeatProps {\n\t/** Tick interval in ms. Default 120ms. */\n\treadonly intervalMs?: number;\n\t/** Color token. Default terminal foreground. */\n\treadonly color?: ColorToken;\n}\n\nexport const Heartbeat = ({\n\tintervalMs = 120,\n\tcolor = 'white',\n}: HeartbeatProps): React.JSX.Element => {\n\tconst [frame, setFrame] = useState(0);\n\tuseEffect(() => {\n\t\tconst id = setInterval(() => {\n\t\t\tsetFrame((f) => (f + 1) % FRAMES.length);\n\t\t}, intervalMs);\n\t\treturn () => clearInterval(id);\n\t}, [intervalMs]);\n\treturn <Text color={color}>{FRAMES[frame]}</Text>;\n};\n"],"mappings":";;;;AAoBA,MAAM,SAAS;CAAC;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAI;AASjE,MAAa,aAAa,EACzB,aAAa,KACb,QAAQ,cACgC;CACxC,MAAM,CAAC,OAAO,YAAY,SAAS,EAAE;AACrC,iBAAgB;EACf,MAAM,KAAK,kBAAkB;AAC5B,aAAU,OAAO,IAAI,KAAK,OAAO,OAAO;KACtC,WAAW;AACd,eAAa,cAAc,GAAG;IAC5B,CAAC,WAAW,CAAC;AAChB,QAAO,oBAAC,MAAD;EAAa;YAAQ,OAAO;EAAc,CAAA"}
1
+ {"version":3,"file":"heartbeat.mjs","names":[],"sources":["../../../src/surfaces/tui/heartbeat.tsx"],"sourcesContent":["// Heartbeat indicator.\n//\n// Distilled/21-tui § Invariants: \"Frame stability: rendering must\n// short-circuit when nothing has changed.\" The heartbeat indicator\n// gives the operator a visible signal that the renderer is alive\n// even when the projection isn't ticking — without re-rendering the\n// whole tree. It's a single character that cycles through a small\n// alphabet on a clock; the dashboard places it in the header.\n//\n// We use `useEffect` + `setInterval` so the heartbeat doesn't depend\n// on the projection at all. When the projection IS ticking, frames\n// are driven by the SubscriptionRef subscription; the heartbeat\n// continues to spin to give long-quiet-stack feedback.\n\nimport { Text } from 'ink';\nimport type React from 'react';\nimport { useEffect, useState } from 'react';\n\nimport type { ColorToken } from './display-derivation.ts';\n\nconst FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] as const;\n\nexport interface HeartbeatProps {\n\t/** Tick interval in ms. Default 120ms. */\n\treadonly intervalMs?: number;\n\t/** Color token. Default terminal foreground. */\n\treadonly color?: ColorToken;\n}\n\nexport const Heartbeat = ({\n\tintervalMs = 120,\n\tcolor = 'white',\n}: HeartbeatProps): React.JSX.Element => {\n\tconst [frame, setFrame] = useState(0);\n\tuseEffect(() => {\n\t\tconst id = setInterval(() => {\n\t\t\tsetFrame((f) => (f + 1) % FRAMES.length);\n\t\t}, intervalMs);\n\t\treturn () => clearInterval(id);\n\t}, [intervalMs]);\n\treturn <Text color={color}>{FRAMES[frame]}</Text>;\n};\n"],"mappings":";;;;AAoBA,MAAM,SAAS;CAAC;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;AAAG;AAShE,MAAa,aAAa,EACzB,aAAa,KACb,QAAQ,cACgC;CACxC,MAAM,CAAC,OAAO,YAAY,SAAS,CAAC;CACpC,gBAAgB;EACf,MAAM,KAAK,kBAAkB;GAC5B,UAAU,OAAO,IAAI,KAAK,OAAO,MAAM;EACxC,GAAG,UAAU;EACb,aAAa,cAAc,EAAE;CAC9B,GAAG,CAAC,UAAU,CAAC;CACf,OAAO,oBAAC,MAAD;EAAa;YAAQ,OAAO;CAAa,CAAA;AACjD"}
@@ -1,6 +1,6 @@
1
+ import "./display-derivation.mjs";
1
2
  import { mountFailed } from "./errors.mjs";
2
3
  import { detectMode } from "./mode-detect.mjs";
3
- import "./display-derivation.mjs";
4
4
  import { makePlainRenderer } from "./plain-renderer.mjs";
5
5
  import { Effect, Stream, SubscriptionRef } from "effect";
6
6
  //#region src/surfaces/tui/index.ts
@@ -18,7 +18,7 @@ import { Effect, Stream, SubscriptionRef } from "effect";
18
18
  const makeTuiSurface = (options = {}) => {
19
19
  switch (detectMode(options.mode)) {
20
20
  case "silent": return makeSilentRenderer();
21
- case "plain": return makePlainRenderer();
21
+ case "plain": return makePlainRenderer({ quiet: options.quiet });
22
22
  case "ink": return makeInkRenderer({ publishCommand: options.publishCommand ?? (() => {}) });
23
23
  default: return makeSilentRenderer();
24
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/surfaces/tui/index.ts"],"sourcesContent":["// TUI surface entry point.\n//\n// Selects the renderer mode (ink / plain / silent), returns a\n// `Renderer`-contract-satisfying value the supervisor can mount once\n// per process. The supervisor wires the SubscriptionRef and event\n// stream in; this surface does the rest.\n//\n// Architecture §11 + distilled/21-tui:\n// - One mount per process (the engine cycle re-runs many times;\n// mount survives).\n// - No engine handle is captured here — only the SubscriptionRef +\n// event Stream + command publisher.\n// - Mode selection follows `mode-detect.ts`: explicit > TTY-detect.\n//\n// This file deliberately does NOT import `ink` at the top level so\n// non-TTY users (CI, scripts) don't pay the ink import cost. The ink\n// renderer is lazy-imported behind the `ink` mode branch via the\n// adjacent `mount-ink.tsx` helper (which owns the JSX).\n\nimport { Effect, Stream, SubscriptionRef } from 'effect';\n\nimport type { Renderer } from '../../contracts/renderer.ts';\nimport type { EngineCommand, EngineEvent } from '../../substrate/events.ts';\nimport type { SubscribableState } from '../../substrate/projection.ts';\nimport { mountFailed } from './errors.ts';\nimport { type RendererMode, detectMode } from './mode-detect.ts';\nimport { makePlainRenderer } from './plain-renderer.ts';\n\nexport interface TuiSurfaceOptions {\n\t/** Explicit mode override. `undefined` → auto-detect via stdout. */\n\treadonly mode?: RendererMode;\n\t/** Command publisher — every keypress turns into a typed\n\t * `EngineCommand` invoked here. Required for `ink` mode; ignored\n\t * for `plain` / `silent` (those modes are pure consumers). */\n\treadonly publishCommand?: (command: EngineCommand) => void;\n}\n\n/**\n * Build the TUI renderer. The returned `Renderer` is the contract the\n * supervisor mounts (once per process); the supervisor passes the\n * SubscriptionRef + event Stream into `renderer.mount`.\n *\n * Mode selection:\n * - `options.mode === 'silent'` → no-op renderer (no output).\n * - `options.mode === 'plain'` → structured stderr lines.\n * - `options.mode === 'ink'` → live Ink dashboard.\n * - `undefined` → `detectMode()` (TTY → ink else plain).\n */\nexport const makeTuiSurface = (options: TuiSurfaceOptions = {}): Renderer => {\n\tconst mode = detectMode(options.mode);\n\tswitch (mode) {\n\t\tcase 'silent':\n\t\t\treturn makeSilentRenderer();\n\t\tcase 'plain':\n\t\t\treturn makePlainRenderer();\n\t\tcase 'ink':\n\t\t\treturn makeInkRenderer({\n\t\t\t\tpublishCommand:\n\t\t\t\t\toptions.publishCommand ??\n\t\t\t\t\t(() => {\n\t\t\t\t\t\t/* no publisher wired → keypresses are silently dropped.\n\t\t\t\t\t\t * The CLI/supervisor wiring is expected to provide one;\n\t\t\t\t\t\t * we don't fail-fast on missing publisher so headless\n\t\t\t\t\t\t * test mounts work. */\n\t\t\t\t\t}),\n\t\t\t});\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = mode;\n\t\t\tvoid _exhaustive;\n\t\t\treturn makeSilentRenderer();\n\t\t}\n\t}\n};\n\n// -----------------------------------------------------------------------------\n// Silent renderer\n// -----------------------------------------------------------------------------\n\nconst makeSilentRenderer = (): Renderer => ({\n\tmount: (stateRef, events) =>\n\t\t// Drain the event stream so back-pressure doesn't accumulate;\n\t\t// emit nothing. Keep the SubscriptionRef alive (no-op read).\n\t\tEffect.gen(function* () {\n\t\t\tyield* SubscriptionRef.get(stateRef);\n\t\t\tyield* Stream.runDrain(events);\n\t\t}).pipe(\n\t\t\tEffect.catch((cause: unknown) =>\n\t\t\t\tEffect.fail(mountFailed(cause instanceof Error ? cause.message : String(cause))),\n\t\t\t),\n\t\t),\n\tflush: Effect.void,\n});\n\n// -----------------------------------------------------------------------------\n// Ink renderer (lazy-loaded; depends on `ink` + `react`)\n// -----------------------------------------------------------------------------\n\ninterface InkRendererOptions {\n\treadonly publishCommand: (command: EngineCommand) => void;\n}\n\nconst makeInkRenderer = (options: InkRendererOptions): Renderer => ({\n\tmount: (stateRef, events) =>\n\t\tEffect.gen(function* () {\n\t\t\t// Lazy-import the JSX mount helper. The dynamic import\n\t\t\t// keeps the static graph thin for non-TTY callers.\n\t\t\tconst { mountInkApp } = yield* Effect.tryPromise({\n\t\t\t\ttry: () => import('./mount-ink.tsx'),\n\t\t\t\tcatch: (cause) =>\n\t\t\t\t\tmountFailed(\n\t\t\t\t\t\t`ink lazy import failed: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t\t\t\t),\n\t\t\t});\n\n\t\t\tyield* mountInkApp({\n\t\t\t\tstateRef,\n\t\t\t\tevents,\n\t\t\t\tpublishCommand: options.publishCommand,\n\t\t\t});\n\t\t}),\n\tflush: Effect.void,\n});\n\n// -----------------------------------------------------------------------------\n// Public exports\n// -----------------------------------------------------------------------------\n\nexport type { Renderer } from '../../contracts/renderer.ts';\nexport type { RendererMode } from './mode-detect.ts';\nexport { detectMode, resolveMode } from './mode-detect.ts';\nexport { deriveDisplayCells } from './display-derivation.ts';\nexport { formatEventLine, formatHeartbeat } from './plain-renderer.ts';\nexport type { EngineCommand, EngineEvent, SubscribableState };\n"],"mappings":";;;;;;;;;;;;;;;;;AAgDA,MAAa,kBAAkB,UAA6B,EAAE,KAAe;AAE5E,SADa,WAAW,QAAQ,KACpB,EAAZ;EACC,KAAK,SACJ,QAAO,oBAAoB;EAC5B,KAAK,QACJ,QAAO,mBAAmB;EAC3B,KAAK,MACJ,QAAO,gBAAgB,EACtB,gBACC,QAAQ,yBACD,KAMR,CAAC;EACH,QAGC,QAAO,oBAAoB;;;AAS9B,MAAM,4BAAsC;CAC3C,QAAQ,UAAU,WAGjB,OAAO,IAAI,aAAa;AACvB,SAAO,gBAAgB,IAAI,SAAS;AACpC,SAAO,OAAO,SAAS,OAAO;GAC7B,CAAC,KACF,OAAO,OAAO,UACb,OAAO,KAAK,YAAY,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC,CAAC,CAChF,CACD;CACF,OAAO,OAAO;CACd;AAUD,MAAM,mBAAmB,aAA2C;CACnE,QAAQ,UAAU,WACjB,OAAO,IAAI,aAAa;EAGvB,MAAM,EAAE,gBAAgB,OAAO,OAAO,WAAW;GAChD,WAAW,OAAO;GAClB,QAAQ,UACP,YACC,2BAA2B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GACjF;GACF,CAAC;AAEF,SAAO,YAAY;GAClB;GACA;GACA,gBAAgB,QAAQ;GACxB,CAAC;GACD;CACH,OAAO,OAAO;CACd"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/surfaces/tui/index.ts"],"sourcesContent":["// TUI surface entry point.\n//\n// Selects the renderer mode (ink / plain / silent), returns a\n// `Renderer`-contract-satisfying value the supervisor can mount once\n// per process. The supervisor wires the SubscriptionRef and event\n// stream in; this surface does the rest.\n//\n// Architecture §11 + distilled/21-tui:\n// - One mount per process (the engine cycle re-runs many times;\n// mount survives).\n// - No engine handle is captured here — only the SubscriptionRef +\n// event Stream + command publisher.\n// - Mode selection follows `mode-detect.ts`: explicit > TTY-detect.\n//\n// This file deliberately does NOT import `ink` at the top level so\n// non-TTY users (CI, scripts) don't pay the ink import cost. The ink\n// renderer is lazy-imported behind the `ink` mode branch via the\n// adjacent `mount-ink.tsx` helper (which owns the JSX).\n\nimport { Effect, Stream, SubscriptionRef } from 'effect';\n\nimport type { Renderer } from '../../contracts/renderer.ts';\nimport type { EngineCommand, EngineEvent } from '../../substrate/events.ts';\nimport type { SubscribableState } from '../../substrate/projection.ts';\nimport { mountFailed } from './errors.ts';\nimport { type RendererMode, detectMode } from './mode-detect.ts';\nimport { makePlainRenderer } from './plain-renderer.ts';\n\nexport interface TuiSurfaceOptions {\n\t/** Explicit mode override. `undefined` → auto-detect via stdout. */\n\treadonly mode?: RendererMode;\n\t/** Command publisher — every keypress turns into a typed\n\t * `EngineCommand` invoked here. Required for `ink` mode; ignored\n\t * for `plain` / `silent` (those modes are pure consumers). */\n\treadonly publishCommand?: (command: EngineCommand) => void;\n\t/** Plain-mode quiet filter — emit only readiness/endpoint/codegen\n\t * milestones + warnings/errors instead of every event. Ignored for\n\t * `ink` / `silent`. See {@link makePlainRenderer}. */\n\treadonly quiet?: boolean;\n}\n\n/**\n * Build the TUI renderer. The returned `Renderer` is the contract the\n * supervisor mounts (once per process); the supervisor passes the\n * SubscriptionRef + event Stream into `renderer.mount`.\n *\n * Mode selection:\n * - `options.mode === 'silent'` → no-op renderer (no output).\n * - `options.mode === 'plain'` → structured stderr lines.\n * - `options.mode === 'ink'` → live Ink dashboard.\n * - `undefined` → `detectMode()` (TTY → ink else plain).\n */\nexport const makeTuiSurface = (options: TuiSurfaceOptions = {}): Renderer => {\n\tconst mode = detectMode(options.mode);\n\tswitch (mode) {\n\t\tcase 'silent':\n\t\t\treturn makeSilentRenderer();\n\t\tcase 'plain':\n\t\t\treturn makePlainRenderer({ quiet: options.quiet });\n\t\tcase 'ink':\n\t\t\treturn makeInkRenderer({\n\t\t\t\tpublishCommand:\n\t\t\t\t\toptions.publishCommand ??\n\t\t\t\t\t(() => {\n\t\t\t\t\t\t/* no publisher wired → keypresses are silently dropped.\n\t\t\t\t\t\t * The CLI/supervisor wiring is expected to provide one;\n\t\t\t\t\t\t * we don't fail-fast on missing publisher so headless\n\t\t\t\t\t\t * test mounts work. */\n\t\t\t\t\t}),\n\t\t\t});\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = mode;\n\t\t\tvoid _exhaustive;\n\t\t\treturn makeSilentRenderer();\n\t\t}\n\t}\n};\n\n// -----------------------------------------------------------------------------\n// Silent renderer\n// -----------------------------------------------------------------------------\n\nconst makeSilentRenderer = (): Renderer => ({\n\tmount: (stateRef, events) =>\n\t\t// Drain the event stream so back-pressure doesn't accumulate;\n\t\t// emit nothing. Keep the SubscriptionRef alive (no-op read).\n\t\tEffect.gen(function* () {\n\t\t\tyield* SubscriptionRef.get(stateRef);\n\t\t\tyield* Stream.runDrain(events);\n\t\t}).pipe(\n\t\t\tEffect.catch((cause: unknown) =>\n\t\t\t\tEffect.fail(mountFailed(cause instanceof Error ? cause.message : String(cause))),\n\t\t\t),\n\t\t),\n\tflush: Effect.void,\n});\n\n// -----------------------------------------------------------------------------\n// Ink renderer (lazy-loaded; depends on `ink` + `react`)\n// -----------------------------------------------------------------------------\n\ninterface InkRendererOptions {\n\treadonly publishCommand: (command: EngineCommand) => void;\n}\n\nconst makeInkRenderer = (options: InkRendererOptions): Renderer => ({\n\tmount: (stateRef, events) =>\n\t\tEffect.gen(function* () {\n\t\t\t// Lazy-import the JSX mount helper. The dynamic import\n\t\t\t// keeps the static graph thin for non-TTY callers.\n\t\t\tconst { mountInkApp } = yield* Effect.tryPromise({\n\t\t\t\ttry: () => import('./mount-ink.tsx'),\n\t\t\t\tcatch: (cause) =>\n\t\t\t\t\tmountFailed(\n\t\t\t\t\t\t`ink lazy import failed: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t\t\t\t),\n\t\t\t});\n\n\t\t\tyield* mountInkApp({\n\t\t\t\tstateRef,\n\t\t\t\tevents,\n\t\t\t\tpublishCommand: options.publishCommand,\n\t\t\t});\n\t\t}),\n\tflush: Effect.void,\n});\n\n// -----------------------------------------------------------------------------\n// Public exports\n// -----------------------------------------------------------------------------\n\nexport type { Renderer } from '../../contracts/renderer.ts';\nexport type { RendererMode } from './mode-detect.ts';\nexport { detectMode, resolveMode } from './mode-detect.ts';\nexport { deriveDisplayCells } from './display-derivation.ts';\nexport { formatEventLine, formatHeartbeat } from './plain-renderer.ts';\nexport type { EngineCommand, EngineEvent, SubscribableState };\n"],"mappings":";;;;;;;;;;;;;;;;;AAoDA,MAAa,kBAAkB,UAA6B,CAAC,MAAgB;CAE5E,QADa,WAAW,QAAQ,IACrB,GAAX;EACC,KAAK,UACJ,OAAO,mBAAmB;EAC3B,KAAK,SACJ,OAAO,kBAAkB,EAAE,OAAO,QAAQ,MAAM,CAAC;EAClD,KAAK,OACJ,OAAO,gBAAgB,EACtB,gBACC,QAAQ,yBACD,CAKP,GACF,CAAC;EACF,SAGC,OAAO,mBAAmB;CAE5B;AACD;AAMA,MAAM,4BAAsC;CAC3C,QAAQ,UAAU,WAGjB,OAAO,IAAI,aAAa;EACvB,OAAO,gBAAgB,IAAI,QAAQ;EACnC,OAAO,OAAO,SAAS,MAAM;CAC9B,CAAC,CAAC,CAAC,KACF,OAAO,OAAO,UACb,OAAO,KAAK,YAAY,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,CAAC,CAChF,CACD;CACD,OAAO,OAAO;AACf;AAUA,MAAM,mBAAmB,aAA2C;CACnE,QAAQ,UAAU,WACjB,OAAO,IAAI,aAAa;EAGvB,MAAM,EAAE,gBAAgB,OAAO,OAAO,WAAW;GAChD,WAAW,OAAO;GAClB,QAAQ,UACP,YACC,2BAA2B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GACjF;EACF,CAAC;EAED,OAAO,YAAY;GAClB;GACA;GACA,gBAAgB,QAAQ;EACzB,CAAC;CACF,CAAC;CACF,OAAO,OAAO;AACf"}
@@ -1 +1 @@
1
- {"version":3,"file":"input.mjs","names":[],"sources":["../../../src/surfaces/tui/input.tsx"],"sourcesContent":["// Keyboard input handler.\n//\n// HARD INVARIANT (distilled/21-tui § Learnings #3): keypress handlers\n// MUST NOT call engine methods directly. They publish typed\n// `EngineCommand` values onto a `CommandPublisher` callback the\n// surface receives at mount time. The engine consumes the channel on\n// the other end; the renderer has no awareness of supervisor\n// internals like Deferred / Queue / scope structure.\n//\n// Key map (architecture §11 + distilled/21-tui §Responsibilities):\n//\n// q | Q -> 'shutdown.requested'\n// r | R -> 'stack.restart'\n// ctrl-c -> first press 'shutdown.requested'; repeat\n// press escalates to\n// 'shutdown.hardKillRequested'. Ink handles\n// Ctrl-C in-process when `exitOnCtrlC:\n// false`, so this mirrors the process-level\n// signal handler for TTY users.\n// s | S -> open snapshot-name prompt\n//\n// Defensive: useInput is mounted at the dashboard root level, NOT in\n// per-row components, so a re-rendered row never re-registers a\n// handler.\n\nimport { useInput } from 'ink';\nimport { useRef } from 'react';\n\nimport type { EngineCommand } from '../../substrate/events.ts';\n\n/**\n * Command publisher. A pure callback the renderer invokes with a\n * typed `EngineCommand`. The surface layer wires this to whatever\n * channel the engine is consuming on (Effect Queue, EventEmitter,\n * etc.); the renderer doesn't care.\n */\nexport type CommandPublisher = (command: EngineCommand) => void;\n\nexport interface InputHandlerProps {\n\t/** Publish a typed EngineCommand. */\n\treadonly publish: CommandPublisher;\n\t/** `null` means normal keymap mode; string means snapshot-name prompt mode. */\n\treadonly snapshotPromptValue: string | null;\n\t/** Update the snapshot prompt. Pass `null` to close it. */\n\treadonly onSnapshotPromptChange: (value: string | null) => void;\n\t/** Disable input handling (e.g. shutting-down state); defaults\n\t * to false. */\n\treadonly disabled?: boolean;\n}\n\nexport interface CommandForKeyOptions {\n\treadonly shutdownAlreadyRequested?: boolean;\n\treadonly now?: () => number;\n}\n\nexport interface InputActionForKeyOptions extends CommandForKeyOptions {\n\treadonly snapshotPromptValue?: string | null;\n}\n\ninterface KeyFlags {\n\treadonly ctrl?: boolean;\n\treadonly return?: boolean;\n\treadonly escape?: boolean;\n\treadonly backspace?: boolean;\n\treadonly delete?: boolean;\n}\n\nexport type InputAction =\n\t| { readonly tag: 'publish'; readonly command: EngineCommand }\n\t| { readonly tag: 'snapshotPrompt.open' }\n\t| { readonly tag: 'snapshotPrompt.update'; readonly value: string }\n\t| { readonly tag: 'snapshotPrompt.cancel' };\n\nconst MAX_SNAPSHOT_NAME_LENGTH = 128;\n\nconst isPrintableSnapshotNameCharacter = (character: string): boolean => {\n\tconst codePoint = character.codePointAt(0);\n\treturn codePoint !== undefined && codePoint >= 0x20 && codePoint !== 0x7f;\n};\n\nconst printableSnapshotNameInput = (input: string): string =>\n\tArray.from(input)\n\t\t.filter(isPrintableSnapshotNameCharacter)\n\t\t.join('')\n\t\t.slice(0, MAX_SNAPSHOT_NAME_LENGTH);\n\n/**\n * Renderless component — mounts `useInput` at the dashboard root.\n * Returns `null` so it composes inside layout boxes without taking\n * up space.\n */\nexport const commandForKey = (\n\tinput: string,\n\tctrl: boolean,\n\toptions: CommandForKeyOptions = {},\n): EngineCommand | null => {\n\tif (ctrl && input === 'c') {\n\t\tif (options.shutdownAlreadyRequested === true) {\n\t\t\treturn {\n\t\t\t\ttag: 'shutdown.hardKillRequested',\n\t\t\t\tsignal: 'SIGINT',\n\t\t\t\texitCode: 130,\n\t\t\t\tat: options.now?.() ?? Date.now(),\n\t\t\t};\n\t\t}\n\t\treturn { tag: 'shutdown.requested' };\n\t}\n\tswitch (input) {\n\t\tcase 'q':\n\t\tcase 'Q':\n\t\t\treturn { tag: 'shutdown.requested' };\n\t\tcase 'r':\n\t\tcase 'R':\n\t\t\treturn { tag: 'stack.restart' };\n\t\tdefault:\n\t\t\treturn null;\n\t}\n};\n\nexport const inputActionForKey = (\n\tinput: string,\n\tkey: KeyFlags,\n\toptions: InputActionForKeyOptions = {},\n): InputAction | null => {\n\tif (options.snapshotPromptValue !== null && options.snapshotPromptValue !== undefined) {\n\t\tif (key.ctrl === true && input === 'c') {\n\t\t\tconst command = commandForKey(input, true, options);\n\t\t\treturn command === null ? null : { tag: 'publish', command };\n\t\t}\n\t\tif (key.escape === true) return { tag: 'snapshotPrompt.cancel' };\n\t\tif (key.return === true) {\n\t\t\tconst name = options.snapshotPromptValue.trim();\n\t\t\treturn {\n\t\t\t\ttag: 'publish',\n\t\t\t\tcommand:\n\t\t\t\t\tname.length === 0 ? { tag: 'snapshot.capture' } : { tag: 'snapshot.capture', name },\n\t\t\t};\n\t\t}\n\t\tif (key.backspace === true || key.delete === true) {\n\t\t\treturn {\n\t\t\t\ttag: 'snapshotPrompt.update',\n\t\t\t\tvalue: options.snapshotPromptValue.slice(0, -1),\n\t\t\t};\n\t\t}\n\t\tif (key.ctrl === true) return null;\n\t\tconst printable = printableSnapshotNameInput(input);\n\t\tif (printable.length === 0) return null;\n\t\treturn {\n\t\t\ttag: 'snapshotPrompt.update',\n\t\t\tvalue: `${options.snapshotPromptValue}${printable}`.slice(0, MAX_SNAPSHOT_NAME_LENGTH),\n\t\t};\n\t}\n\n\tif (input === 's' || input === 'S') return { tag: 'snapshotPrompt.open' };\n\tconst command = commandForKey(input, key.ctrl === true, options);\n\treturn command === null ? null : { tag: 'publish', command };\n};\n\nexport const InputHandler = ({\n\tpublish,\n\tsnapshotPromptValue,\n\tonSnapshotPromptChange,\n\tdisabled = false,\n}: InputHandlerProps): null => {\n\tconst shutdownRequested = useRef(false);\n\tuseInput(\n\t\t(input, key) => {\n\t\t\tif (disabled) return;\n\t\t\tconst action = inputActionForKey(input, key, {\n\t\t\t\tsnapshotPromptValue,\n\t\t\t\tshutdownAlreadyRequested: shutdownRequested.current,\n\t\t\t});\n\t\t\tswitch (action?.tag) {\n\t\t\t\tcase 'publish': {\n\t\t\t\t\tconst { command } = action;\n\t\t\t\t\tif (\n\t\t\t\t\t\tcommand.tag === 'shutdown.requested' ||\n\t\t\t\t\t\tcommand.tag === 'shutdown.hardKillRequested'\n\t\t\t\t\t) {\n\t\t\t\t\t\tonSnapshotPromptChange(null);\n\t\t\t\t\t\tshutdownRequested.current = true;\n\t\t\t\t\t}\n\t\t\t\t\tif (command.tag === 'snapshot.capture') {\n\t\t\t\t\t\tonSnapshotPromptChange(null);\n\t\t\t\t\t}\n\t\t\t\t\tpublish(command);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'snapshotPrompt.open':\n\t\t\t\t\tonSnapshotPromptChange('');\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'snapshotPrompt.update':\n\t\t\t\t\tonSnapshotPromptChange(action.value);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'snapshotPrompt.cancel':\n\t\t\t\t\tonSnapshotPromptChange(null);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t},\n\t\t{ isActive: !disabled },\n\t);\n\treturn null;\n};\n"],"mappings":";;;AAyEA,MAAM,2BAA2B;AAEjC,MAAM,oCAAoC,cAA+B;CACxE,MAAM,YAAY,UAAU,YAAY,EAAE;AAC1C,QAAO,cAAc,KAAA,KAAa,aAAa,MAAQ,cAAc;;AAGtE,MAAM,8BAA8B,UACnC,MAAM,KAAK,MAAM,CACf,OAAO,iCAAiC,CACxC,KAAK,GAAG,CACR,MAAM,GAAG,yBAAyB;;;;;;AAOrC,MAAa,iBACZ,OACA,MACA,UAAgC,EAAE,KACR;AAC1B,KAAI,QAAQ,UAAU,KAAK;AAC1B,MAAI,QAAQ,6BAA6B,KACxC,QAAO;GACN,KAAK;GACL,QAAQ;GACR,UAAU;GACV,IAAI,QAAQ,OAAO,IAAI,KAAK,KAAK;GACjC;AAEF,SAAO,EAAE,KAAK,sBAAsB;;AAErC,SAAQ,OAAR;EACC,KAAK;EACL,KAAK,IACJ,QAAO,EAAE,KAAK,sBAAsB;EACrC,KAAK;EACL,KAAK,IACJ,QAAO,EAAE,KAAK,iBAAiB;EAChC,QACC,QAAO;;;AAIV,MAAa,qBACZ,OACA,KACA,UAAoC,EAAE,KACd;AACxB,KAAI,QAAQ,wBAAwB,QAAQ,QAAQ,wBAAwB,KAAA,GAAW;AACtF,MAAI,IAAI,SAAS,QAAQ,UAAU,KAAK;GACvC,MAAM,UAAU,cAAc,OAAO,MAAM,QAAQ;AACnD,UAAO,YAAY,OAAO,OAAO;IAAE,KAAK;IAAW;IAAS;;AAE7D,MAAI,IAAI,WAAW,KAAM,QAAO,EAAE,KAAK,yBAAyB;AAChE,MAAI,IAAI,WAAW,MAAM;GACxB,MAAM,OAAO,QAAQ,oBAAoB,MAAM;AAC/C,UAAO;IACN,KAAK;IACL,SACC,KAAK,WAAW,IAAI,EAAE,KAAK,oBAAoB,GAAG;KAAE,KAAK;KAAoB;KAAM;IACpF;;AAEF,MAAI,IAAI,cAAc,QAAQ,IAAI,WAAW,KAC5C,QAAO;GACN,KAAK;GACL,OAAO,QAAQ,oBAAoB,MAAM,GAAG,GAAG;GAC/C;AAEF,MAAI,IAAI,SAAS,KAAM,QAAO;EAC9B,MAAM,YAAY,2BAA2B,MAAM;AACnD,MAAI,UAAU,WAAW,EAAG,QAAO;AACnC,SAAO;GACN,KAAK;GACL,OAAO,GAAG,QAAQ,sBAAsB,YAAY,MAAM,GAAG,yBAAyB;GACtF;;AAGF,KAAI,UAAU,OAAO,UAAU,IAAK,QAAO,EAAE,KAAK,uBAAuB;CACzE,MAAM,UAAU,cAAc,OAAO,IAAI,SAAS,MAAM,QAAQ;AAChE,QAAO,YAAY,OAAO,OAAO;EAAE,KAAK;EAAW;EAAS;;AAG7D,MAAa,gBAAgB,EAC5B,SACA,qBACA,wBACA,WAAW,YACmB;CAC9B,MAAM,oBAAoB,OAAO,MAAM;AACvC,WACE,OAAO,QAAQ;AACf,MAAI,SAAU;EACd,MAAM,SAAS,kBAAkB,OAAO,KAAK;GAC5C;GACA,0BAA0B,kBAAkB;GAC5C,CAAC;AACF,UAAQ,QAAQ,KAAhB;GACC,KAAK,WAAW;IACf,MAAM,EAAE,YAAY;AACpB,QACC,QAAQ,QAAQ,wBAChB,QAAQ,QAAQ,8BACf;AACD,4BAAuB,KAAK;AAC5B,uBAAkB,UAAU;;AAE7B,QAAI,QAAQ,QAAQ,mBACnB,wBAAuB,KAAK;AAE7B,YAAQ,QAAQ;AAChB;;GAED,KAAK;AACJ,2BAAuB,GAAG;AAC1B;GACD,KAAK;AACJ,2BAAuB,OAAO,MAAM;AACpC;GACD,KAAK;AACJ,2BAAuB,KAAK;AAC5B;GACD,QACC;;IAGH,EAAE,UAAU,CAAC,UAAU,CACvB;AACD,QAAO"}
1
+ {"version":3,"file":"input.mjs","names":[],"sources":["../../../src/surfaces/tui/input.tsx"],"sourcesContent":["// Keyboard input handler.\n//\n// HARD INVARIANT (distilled/21-tui § Learnings #3): keypress handlers\n// MUST NOT call engine methods directly. They publish typed\n// `EngineCommand` values onto a `CommandPublisher` callback the\n// surface receives at mount time. The engine consumes the channel on\n// the other end; the renderer has no awareness of supervisor\n// internals like Deferred / Queue / scope structure.\n//\n// Key map (architecture §11 + distilled/21-tui §Responsibilities):\n//\n// q | Q -> 'shutdown.requested'\n// r | R -> 'stack.restart'\n// ctrl-c -> first press 'shutdown.requested'; repeat\n// press escalates to\n// 'shutdown.hardKillRequested'. Ink handles\n// Ctrl-C in-process when `exitOnCtrlC:\n// false`, so this mirrors the process-level\n// signal handler for TTY users.\n// s | S -> open snapshot-name prompt\n//\n// Defensive: useInput is mounted at the dashboard root level, NOT in\n// per-row components, so a re-rendered row never re-registers a\n// handler.\n\nimport { useInput } from 'ink';\nimport { useRef } from 'react';\n\nimport type { EngineCommand } from '../../substrate/events.ts';\n\n/**\n * Command publisher. A pure callback the renderer invokes with a\n * typed `EngineCommand`. The surface layer wires this to whatever\n * channel the engine is consuming on (Effect Queue, EventEmitter,\n * etc.); the renderer doesn't care.\n */\nexport type CommandPublisher = (command: EngineCommand) => void;\n\nexport interface InputHandlerProps {\n\t/** Publish a typed EngineCommand. */\n\treadonly publish: CommandPublisher;\n\t/** `null` means normal keymap mode; string means snapshot-name prompt mode. */\n\treadonly snapshotPromptValue: string | null;\n\t/** Update the snapshot prompt. Pass `null` to close it. */\n\treadonly onSnapshotPromptChange: (value: string | null) => void;\n\t/** Disable input handling (e.g. shutting-down state); defaults\n\t * to false. */\n\treadonly disabled?: boolean;\n}\n\nexport interface CommandForKeyOptions {\n\treadonly shutdownAlreadyRequested?: boolean;\n\treadonly now?: () => number;\n}\n\nexport interface InputActionForKeyOptions extends CommandForKeyOptions {\n\treadonly snapshotPromptValue?: string | null;\n}\n\ninterface KeyFlags {\n\treadonly ctrl?: boolean;\n\treadonly return?: boolean;\n\treadonly escape?: boolean;\n\treadonly backspace?: boolean;\n\treadonly delete?: boolean;\n}\n\nexport type InputAction =\n\t| { readonly tag: 'publish'; readonly command: EngineCommand }\n\t| { readonly tag: 'snapshotPrompt.open' }\n\t| { readonly tag: 'snapshotPrompt.update'; readonly value: string }\n\t| { readonly tag: 'snapshotPrompt.cancel' };\n\nconst MAX_SNAPSHOT_NAME_LENGTH = 128;\n\nconst isPrintableSnapshotNameCharacter = (character: string): boolean => {\n\tconst codePoint = character.codePointAt(0);\n\treturn codePoint !== undefined && codePoint >= 0x20 && codePoint !== 0x7f;\n};\n\nconst printableSnapshotNameInput = (input: string): string =>\n\tArray.from(input)\n\t\t.filter(isPrintableSnapshotNameCharacter)\n\t\t.join('')\n\t\t.slice(0, MAX_SNAPSHOT_NAME_LENGTH);\n\n/**\n * Renderless component — mounts `useInput` at the dashboard root.\n * Returns `null` so it composes inside layout boxes without taking\n * up space.\n */\nexport const commandForKey = (\n\tinput: string,\n\tctrl: boolean,\n\toptions: CommandForKeyOptions = {},\n): EngineCommand | null => {\n\tif (ctrl && input === 'c') {\n\t\tif (options.shutdownAlreadyRequested === true) {\n\t\t\treturn {\n\t\t\t\ttag: 'shutdown.hardKillRequested',\n\t\t\t\tsignal: 'SIGINT',\n\t\t\t\texitCode: 130,\n\t\t\t\tat: options.now?.() ?? Date.now(),\n\t\t\t};\n\t\t}\n\t\treturn { tag: 'shutdown.requested' };\n\t}\n\tswitch (input) {\n\t\tcase 'q':\n\t\tcase 'Q':\n\t\t\treturn { tag: 'shutdown.requested' };\n\t\tcase 'r':\n\t\tcase 'R':\n\t\t\treturn { tag: 'stack.restart' };\n\t\tdefault:\n\t\t\treturn null;\n\t}\n};\n\nexport const inputActionForKey = (\n\tinput: string,\n\tkey: KeyFlags,\n\toptions: InputActionForKeyOptions = {},\n): InputAction | null => {\n\tif (options.snapshotPromptValue !== null && options.snapshotPromptValue !== undefined) {\n\t\tif (key.ctrl === true && input === 'c') {\n\t\t\tconst command = commandForKey(input, true, options);\n\t\t\treturn command === null ? null : { tag: 'publish', command };\n\t\t}\n\t\tif (key.escape === true) return { tag: 'snapshotPrompt.cancel' };\n\t\tif (key.return === true) {\n\t\t\tconst name = options.snapshotPromptValue.trim();\n\t\t\treturn {\n\t\t\t\ttag: 'publish',\n\t\t\t\tcommand:\n\t\t\t\t\tname.length === 0 ? { tag: 'snapshot.capture' } : { tag: 'snapshot.capture', name },\n\t\t\t};\n\t\t}\n\t\tif (key.backspace === true || key.delete === true) {\n\t\t\treturn {\n\t\t\t\ttag: 'snapshotPrompt.update',\n\t\t\t\tvalue: options.snapshotPromptValue.slice(0, -1),\n\t\t\t};\n\t\t}\n\t\tif (key.ctrl === true) return null;\n\t\tconst printable = printableSnapshotNameInput(input);\n\t\tif (printable.length === 0) return null;\n\t\treturn {\n\t\t\ttag: 'snapshotPrompt.update',\n\t\t\tvalue: `${options.snapshotPromptValue}${printable}`.slice(0, MAX_SNAPSHOT_NAME_LENGTH),\n\t\t};\n\t}\n\n\tif (input === 's' || input === 'S') return { tag: 'snapshotPrompt.open' };\n\tconst command = commandForKey(input, key.ctrl === true, options);\n\treturn command === null ? null : { tag: 'publish', command };\n};\n\nexport const InputHandler = ({\n\tpublish,\n\tsnapshotPromptValue,\n\tonSnapshotPromptChange,\n\tdisabled = false,\n}: InputHandlerProps): null => {\n\tconst shutdownRequested = useRef(false);\n\tuseInput(\n\t\t(input, key) => {\n\t\t\tif (disabled) return;\n\t\t\tconst action = inputActionForKey(input, key, {\n\t\t\t\tsnapshotPromptValue,\n\t\t\t\tshutdownAlreadyRequested: shutdownRequested.current,\n\t\t\t});\n\t\t\tswitch (action?.tag) {\n\t\t\t\tcase 'publish': {\n\t\t\t\t\tconst { command } = action;\n\t\t\t\t\tif (\n\t\t\t\t\t\tcommand.tag === 'shutdown.requested' ||\n\t\t\t\t\t\tcommand.tag === 'shutdown.hardKillRequested'\n\t\t\t\t\t) {\n\t\t\t\t\t\tonSnapshotPromptChange(null);\n\t\t\t\t\t\tshutdownRequested.current = true;\n\t\t\t\t\t}\n\t\t\t\t\tif (command.tag === 'snapshot.capture') {\n\t\t\t\t\t\tonSnapshotPromptChange(null);\n\t\t\t\t\t}\n\t\t\t\t\tpublish(command);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'snapshotPrompt.open':\n\t\t\t\t\tonSnapshotPromptChange('');\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'snapshotPrompt.update':\n\t\t\t\t\tonSnapshotPromptChange(action.value);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'snapshotPrompt.cancel':\n\t\t\t\t\tonSnapshotPromptChange(null);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t},\n\t\t{ isActive: !disabled },\n\t);\n\treturn null;\n};\n"],"mappings":";;;AAyEA,MAAM,2BAA2B;AAEjC,MAAM,oCAAoC,cAA+B;CACxE,MAAM,YAAY,UAAU,YAAY,CAAC;CACzC,OAAO,cAAc,KAAA,KAAa,aAAa,MAAQ,cAAc;AACtE;AAEA,MAAM,8BAA8B,UACnC,MAAM,KAAK,KAAK,CAAC,CACf,OAAO,gCAAgC,CAAC,CACxC,KAAK,EAAE,CAAC,CACR,MAAM,GAAG,wBAAwB;;;;;;AAOpC,MAAa,iBACZ,OACA,MACA,UAAgC,CAAC,MACP;CAC1B,IAAI,QAAQ,UAAU,KAAK;EAC1B,IAAI,QAAQ,6BAA6B,MACxC,OAAO;GACN,KAAK;GACL,QAAQ;GACR,UAAU;GACV,IAAI,QAAQ,MAAM,KAAK,KAAK,IAAI;EACjC;EAED,OAAO,EAAE,KAAK,qBAAqB;CACpC;CACA,QAAQ,OAAR;EACC,KAAK;EACL,KAAK,KACJ,OAAO,EAAE,KAAK,qBAAqB;EACpC,KAAK;EACL,KAAK,KACJ,OAAO,EAAE,KAAK,gBAAgB;EAC/B,SACC,OAAO;CACT;AACD;AAEA,MAAa,qBACZ,OACA,KACA,UAAoC,CAAC,MACb;CACxB,IAAI,QAAQ,wBAAwB,QAAQ,QAAQ,wBAAwB,KAAA,GAAW;EACtF,IAAI,IAAI,SAAS,QAAQ,UAAU,KAAK;GACvC,MAAM,UAAU,cAAc,OAAO,MAAM,OAAO;GAClD,OAAO,YAAY,OAAO,OAAO;IAAE,KAAK;IAAW;GAAQ;EAC5D;EACA,IAAI,IAAI,WAAW,MAAM,OAAO,EAAE,KAAK,wBAAwB;EAC/D,IAAI,IAAI,WAAW,MAAM;GACxB,MAAM,OAAO,QAAQ,oBAAoB,KAAK;GAC9C,OAAO;IACN,KAAK;IACL,SACC,KAAK,WAAW,IAAI,EAAE,KAAK,mBAAmB,IAAI;KAAE,KAAK;KAAoB;IAAK;GACpF;EACD;EACA,IAAI,IAAI,cAAc,QAAQ,IAAI,WAAW,MAC5C,OAAO;GACN,KAAK;GACL,OAAO,QAAQ,oBAAoB,MAAM,GAAG,EAAE;EAC/C;EAED,IAAI,IAAI,SAAS,MAAM,OAAO;EAC9B,MAAM,YAAY,2BAA2B,KAAK;EAClD,IAAI,UAAU,WAAW,GAAG,OAAO;EACnC,OAAO;GACN,KAAK;GACL,OAAO,GAAG,QAAQ,sBAAsB,YAAY,MAAM,GAAG,wBAAwB;EACtF;CACD;CAEA,IAAI,UAAU,OAAO,UAAU,KAAK,OAAO,EAAE,KAAK,sBAAsB;CACxE,MAAM,UAAU,cAAc,OAAO,IAAI,SAAS,MAAM,OAAO;CAC/D,OAAO,YAAY,OAAO,OAAO;EAAE,KAAK;EAAW;CAAQ;AAC5D;AAEA,MAAa,gBAAgB,EAC5B,SACA,qBACA,wBACA,WAAW,YACmB;CAC9B,MAAM,oBAAoB,OAAO,KAAK;CACtC,UACE,OAAO,QAAQ;EACf,IAAI,UAAU;EACd,MAAM,SAAS,kBAAkB,OAAO,KAAK;GAC5C;GACA,0BAA0B,kBAAkB;EAC7C,CAAC;EACD,QAAQ,QAAQ,KAAhB;GACC,KAAK,WAAW;IACf,MAAM,EAAE,YAAY;IACpB,IACC,QAAQ,QAAQ,wBAChB,QAAQ,QAAQ,8BACf;KACD,uBAAuB,IAAI;KAC3B,kBAAkB,UAAU;IAC7B;IACA,IAAI,QAAQ,QAAQ,oBACnB,uBAAuB,IAAI;IAE5B,QAAQ,OAAO;IACf;GACD;GACA,KAAK;IACJ,uBAAuB,EAAE;IACzB;GACD,KAAK;IACJ,uBAAuB,OAAO,KAAK;IACnC;GACD,KAAK;IACJ,uBAAuB,IAAI;IAC3B;GACD,SACC;EACF;CACD,GACA,EAAE,UAAU,CAAC,SAAS,CACvB;CACA,OAAO;AACR"}
@@ -1 +1 @@
1
- {"version":3,"file":"mode-detect.mjs","names":[],"sources":["../../../src/surfaces/tui/mode-detect.ts"],"sourcesContent":["// Renderer mode detection.\n//\n// Architecture §11 + distilled/21-tui §Renderers: three renderer\n// variants exist (ink dashboard, plain, silent). Selection rule:\n//\n// 1. Explicit `mode` argument wins (CLI flag or programmable\n// surface override).\n// 2. Otherwise, auto-resolve by inspecting `process.stdout.isTTY`\n// (TTY → ink; non-TTY → plain).\n// 3. `silent` is only selected when explicitly requested.\n//\n// NO_COLOR / FORCE_COLOR / TERM=dumb are handled by Ink/upstream —\n// we don't override here (architecture invariant: defer to upstream\n// color conventions).\n//\n// Pure module; the detection ITSELF doesn't run side effects — it\n// reads `process.stdout.isTTY` once at call time but the surface\n// machinery decides when to call it.\n\n/** Closed set of renderer modes. Adding a mode is an architecture\n * revision (see distilled/21-tui § Renderers). */\nexport type RendererMode = 'ink' | 'plain' | 'silent';\n\nexport interface ModeDetectInput {\n\t/** Explicit override from CLI flag / config / programmable\n\t * surface. `undefined` → auto-detect. */\n\treadonly requested?: RendererMode;\n\t/** Whether stdout is a TTY. Pass `process.stdout.isTTY` from the\n\t * caller; we keep this dependency-injectable for testability. */\n\treadonly stdoutIsTty: boolean;\n}\n\n/**\n * Resolve the active renderer mode.\n *\n * requested | stdoutIsTty | result\n * ---------------+-------------+--------\n * 'ink' | any | 'ink'\n * 'plain' | any | 'plain'\n * 'silent' | any | 'silent'\n * undefined | true | 'ink'\n * undefined | false | 'plain'\n *\n * Note: the `'ink'` explicit override on a non-TTY is allowed and is\n * the operator's problem (e.g. invoked under a TTY-emulating\n * wrapper). The auto path NEVER picks `'ink'` for a non-TTY.\n */\nexport const resolveMode = (input: ModeDetectInput): RendererMode => {\n\tif (input.requested) return input.requested;\n\treturn input.stdoutIsTty ? 'ink' : 'plain';\n};\n\n/**\n * Detect-from-process convenience for callers that don't need the\n * pure `resolveMode` separation. Reads `process.stdout.isTTY` once.\n */\nexport const detectMode = (requested?: RendererMode): RendererMode =>\n\tresolveMode({\n\t\trequested,\n\t\tstdoutIsTty: Boolean(process.stdout.isTTY),\n\t});\n"],"mappings":";;;;;;;;;;;;;;;;AA+CA,MAAa,eAAe,UAAyC;AACpE,KAAI,MAAM,UAAW,QAAO,MAAM;AAClC,QAAO,MAAM,cAAc,QAAQ;;;;;;AAOpC,MAAa,cAAc,cAC1B,YAAY;CACX;CACA,aAAa,QAAQ,QAAQ,OAAO,MAAM;CAC1C,CAAC"}
1
+ {"version":3,"file":"mode-detect.mjs","names":[],"sources":["../../../src/surfaces/tui/mode-detect.ts"],"sourcesContent":["// Renderer mode detection.\n//\n// Architecture §11 + distilled/21-tui §Renderers: three renderer\n// variants exist (ink dashboard, plain, silent). Selection rule:\n//\n// 1. Explicit `mode` argument wins (CLI flag or programmable\n// surface override).\n// 2. Otherwise, auto-resolve by inspecting `process.stdout.isTTY`\n// (TTY → ink; non-TTY → plain).\n// 3. `silent` is only selected when explicitly requested.\n//\n// NO_COLOR / FORCE_COLOR / TERM=dumb are handled by Ink/upstream —\n// we don't override here (architecture invariant: defer to upstream\n// color conventions).\n//\n// Pure module; the detection ITSELF doesn't run side effects — it\n// reads `process.stdout.isTTY` once at call time but the surface\n// machinery decides when to call it.\n\n/** Closed set of renderer modes. Adding a mode is an architecture\n * revision (see distilled/21-tui § Renderers). */\nexport type RendererMode = 'ink' | 'plain' | 'silent';\n\nexport interface ModeDetectInput {\n\t/** Explicit override from CLI flag / config / programmable\n\t * surface. `undefined` → auto-detect. */\n\treadonly requested?: RendererMode;\n\t/** Whether stdout is a TTY. Pass `process.stdout.isTTY` from the\n\t * caller; we keep this dependency-injectable for testability. */\n\treadonly stdoutIsTty: boolean;\n}\n\n/**\n * Resolve the active renderer mode.\n *\n * requested | stdoutIsTty | result\n * ---------------+-------------+--------\n * 'ink' | any | 'ink'\n * 'plain' | any | 'plain'\n * 'silent' | any | 'silent'\n * undefined | true | 'ink'\n * undefined | false | 'plain'\n *\n * Note: the `'ink'` explicit override on a non-TTY is allowed and is\n * the operator's problem (e.g. invoked under a TTY-emulating\n * wrapper). The auto path NEVER picks `'ink'` for a non-TTY.\n */\nexport const resolveMode = (input: ModeDetectInput): RendererMode => {\n\tif (input.requested) return input.requested;\n\treturn input.stdoutIsTty ? 'ink' : 'plain';\n};\n\n/**\n * Detect-from-process convenience for callers that don't need the\n * pure `resolveMode` separation. Reads `process.stdout.isTTY` once.\n */\nexport const detectMode = (requested?: RendererMode): RendererMode =>\n\tresolveMode({\n\t\trequested,\n\t\tstdoutIsTty: Boolean(process.stdout.isTTY),\n\t});\n"],"mappings":";;;;;;;;;;;;;;;;AA+CA,MAAa,eAAe,UAAyC;CACpE,IAAI,MAAM,WAAW,OAAO,MAAM;CAClC,OAAO,MAAM,cAAc,QAAQ;AACpC;;;;;AAMA,MAAa,cAAc,cAC1B,YAAY;CACX;CACA,aAAa,QAAQ,QAAQ,OAAO,KAAK;AAC1C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"mount-ink.mjs","names":[],"sources":["../../../src/surfaces/tui/mount-ink.tsx"],"sourcesContent":["// Ink mount helper.\n//\n// Owns the `<App/>` JSX expression so `index.ts` stays a plain `.ts`\n// file (no JSX). Lazy-imported from `index.ts` only when the `ink`\n// mode is selected, so CI / non-TTY callers don't pay the ink+react\n// import cost.\n//\n// The mount itself awaits `instance.waitUntilExit()` so the surface's\n// mount Effect remains open until the user exits the ink app or the\n// supervisor finalizes the scope.\n\nimport { Effect, Scope, Stream, SubscriptionRef } from 'effect';\nimport { render } from 'ink';\n\nimport type { EngineCommand, EngineEvent } from '../../substrate/events.ts';\nimport type { SubscribableState } from '../../substrate/projection.ts';\nimport { App } from './app.tsx';\nimport { mountFailed, type RendererError } from './errors.ts';\n\nexport interface MountInkAppInput {\n\treadonly stateRef: SubscriptionRef.SubscriptionRef<SubscribableState>;\n\treadonly events: Stream.Stream<EngineEvent, never>;\n\treadonly publishCommand: (command: EngineCommand) => void;\n}\n\nexport const INK_RENDER_OPTIONS = {\n\texitOnCtrlC: false,\n\tpatchConsole: true,\n} as const;\n\nexport const mountInkApp = (\n\tinput: MountInkAppInput,\n): Effect.Effect<void, RendererError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst instance = yield* Effect.try({\n\t\t\ttry: () =>\n\t\t\t\trender(\n\t\t\t\t\t<App stateRef={input.stateRef} events={input.events} publish={input.publishCommand} />,\n\t\t\t\t\tINK_RENDER_OPTIONS,\n\t\t\t\t),\n\t\t\tcatch: (cause) => mountFailed(cause instanceof Error ? cause.message : String(cause)),\n\t\t});\n\n\t\tyield* Effect.addFinalizer(() =>\n\t\t\tEffect.sync(() => {\n\t\t\t\tinstance.unmount();\n\t\t\t}),\n\t\t);\n\n\t\tyield* Effect.tryPromise({\n\t\t\ttry: () => instance.waitUntilExit(),\n\t\t\tcatch: (cause) =>\n\t\t\t\tmountFailed(\n\t\t\t\t\t`ink waitUntilExit failed: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t\t\t),\n\t\t});\n\t});\n"],"mappings":";;;;;;AAyBA,MAAa,qBAAqB;CACjC,aAAa;CACb,cAAc;CACd;AAED,MAAa,eACZ,UAEA,OAAO,IAAI,aAAa;CACvB,MAAM,WAAW,OAAO,OAAO,IAAI;EAClC,WACC,OACC,oBAAC,KAAD;GAAK,UAAU,MAAM;GAAU,QAAQ,MAAM;GAAQ,SAAS,MAAM;GAAkB,CAAA,EACtF,mBACA;EACF,QAAQ,UAAU,YAAY,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC;EACrF,CAAC;AAEF,QAAO,OAAO,mBACb,OAAO,WAAW;AACjB,WAAS,SAAS;GACjB,CACF;AAED,QAAO,OAAO,WAAW;EACxB,WAAW,SAAS,eAAe;EACnC,QAAQ,UACP,YACC,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GACnF;EACF,CAAC;EACD"}
1
+ {"version":3,"file":"mount-ink.mjs","names":[],"sources":["../../../src/surfaces/tui/mount-ink.tsx"],"sourcesContent":["// Ink mount helper.\n//\n// Owns the `<App/>` JSX expression so `index.ts` stays a plain `.ts`\n// file (no JSX). Lazy-imported from `index.ts` only when the `ink`\n// mode is selected, so CI / non-TTY callers don't pay the ink+react\n// import cost.\n//\n// The mount itself awaits `instance.waitUntilExit()` so the surface's\n// mount Effect remains open until the user exits the ink app or the\n// supervisor finalizes the scope.\n\nimport { Effect, Scope, Stream, SubscriptionRef } from 'effect';\nimport { render } from 'ink';\n\nimport type { EngineCommand, EngineEvent } from '../../substrate/events.ts';\nimport type { SubscribableState } from '../../substrate/projection.ts';\nimport { App } from './app.tsx';\nimport { mountFailed, type RendererError } from './errors.ts';\n\nexport interface MountInkAppInput {\n\treadonly stateRef: SubscriptionRef.SubscriptionRef<SubscribableState>;\n\treadonly events: Stream.Stream<EngineEvent, never>;\n\treadonly publishCommand: (command: EngineCommand) => void;\n}\n\nexport const INK_RENDER_OPTIONS = {\n\texitOnCtrlC: false,\n\tpatchConsole: true,\n} as const;\n\nexport const mountInkApp = (\n\tinput: MountInkAppInput,\n): Effect.Effect<void, RendererError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst instance = yield* Effect.try({\n\t\t\ttry: () =>\n\t\t\t\trender(\n\t\t\t\t\t<App stateRef={input.stateRef} events={input.events} publish={input.publishCommand} />,\n\t\t\t\t\tINK_RENDER_OPTIONS,\n\t\t\t\t),\n\t\t\tcatch: (cause) => mountFailed(cause instanceof Error ? cause.message : String(cause)),\n\t\t});\n\n\t\tyield* Effect.addFinalizer(() =>\n\t\t\tEffect.sync(() => {\n\t\t\t\tinstance.unmount();\n\t\t\t}),\n\t\t);\n\n\t\tyield* Effect.tryPromise({\n\t\t\ttry: () => instance.waitUntilExit(),\n\t\t\tcatch: (cause) =>\n\t\t\t\tmountFailed(\n\t\t\t\t\t`ink waitUntilExit failed: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t\t\t),\n\t\t});\n\t});\n"],"mappings":";;;;;;AAyBA,MAAa,qBAAqB;CACjC,aAAa;CACb,cAAc;AACf;AAEA,MAAa,eACZ,UAEA,OAAO,IAAI,aAAa;CACvB,MAAM,WAAW,OAAO,OAAO,IAAI;EAClC,WACC,OACC,oBAAC,KAAD;GAAK,UAAU,MAAM;GAAU,QAAQ,MAAM;GAAQ,SAAS,MAAM;EAAiB,CAAA,GACrF,kBACD;EACD,QAAQ,UAAU,YAAY,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;CACrF,CAAC;CAED,OAAO,OAAO,mBACb,OAAO,WAAW;EACjB,SAAS,QAAQ;CAClB,CAAC,CACF;CAEA,OAAO,OAAO,WAAW;EACxB,WAAW,SAAS,cAAc;EAClC,QAAQ,UACP,YACC,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GACnF;CACF,CAAC;AACF,CAAC"}