@north-light/crouter 0.3.170 → 0.3.172

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 (670) hide show
  1. package/dist/api/client.d.ts +37 -11
  2. package/dist/api/client.js +84 -15
  3. package/dist/api/dto/human.d.ts +53 -39
  4. package/dist/api/dto/human.js +2 -3
  5. package/dist/api/dto/inbox.d.ts +0 -1
  6. package/dist/api/dto/nodes.d.ts +12 -0
  7. package/dist/api/dto/review-comments.d.ts +137 -0
  8. package/dist/api/dto/review-comments.js +5 -0
  9. package/dist/api/dto/reviews.d.ts +109 -0
  10. package/dist/api/dto/reviews.js +5 -0
  11. package/dist/api/index.d.ts +2 -0
  12. package/dist/api/index.js +2 -0
  13. package/dist/api/routes.d.ts +17 -4
  14. package/dist/api/routes.js +21 -5
  15. package/dist/build-root.d.ts +1 -16
  16. package/dist/build-root.js +1 -25
  17. package/dist/builtin-memory/00-runtime-base.md +7 -5
  18. package/dist/builtin-memory/04-base-worker.md +1 -1
  19. package/dist/builtin-memory/04-orchestration-kernel.md +7 -7
  20. package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
  21. package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +1 -1
  22. package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -1
  23. package/dist/builtin-memory/05-kinds/explore/00-base.md +1 -1
  24. package/dist/builtin-memory/05-kinds/plan/00-base.md +1 -1
  25. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +1 -1
  26. package/dist/builtin-memory/05-kinds/review/00-base.md +1 -1
  27. package/dist/builtin-memory/05-kinds/review/companion/00-base.md +20 -0
  28. package/dist/builtin-memory/05-kinds/spec/00-base.md +3 -7
  29. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +3 -3
  30. package/dist/builtin-memory/05-kinds/spec/requirements.md +3 -3
  31. package/dist/builtin-memory/design.md +2 -2
  32. package/dist/builtin-memory/insights/capture.md +50 -0
  33. package/dist/builtin-memory/insights/init.md +45 -0
  34. package/dist/builtin-memory/insights/listen.md +9 -0
  35. package/dist/builtin-memory/internal/agent-shaping.md +2 -2
  36. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  37. package/dist/builtin-memory/internal/plugins.md +12 -11
  38. package/dist/builtin-memory/internal/storage-tiers.md +2 -2
  39. package/dist/builtin-memory/{planning.md → plan/roadmap.md} +3 -3
  40. package/dist/builtin-memory/plan.md +19 -0
  41. package/dist/builtin-memory/spec/guide.md +38 -0
  42. package/dist/builtin-memory/spec/requirements.md +31 -0
  43. package/dist/builtin-memory/spec/roadmap.md +35 -0
  44. package/dist/builtin-memory/spec.md +10 -86
  45. package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +3 -4
  46. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +49 -5
  47. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +56 -0
  48. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +920 -0
  49. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +221 -156
  50. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +4 -5
  51. package/dist/cli.js +2 -6
  52. package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +7 -35
  53. package/dist/clients/attach/__tests__/attach-keybindings.test.js +32 -16
  54. package/dist/clients/attach/__tests__/context-message.test.js +142 -6
  55. package/dist/clients/attach/__tests__/crtr-output.test.js +29 -45
  56. package/dist/clients/attach/__tests__/diagram.test.js +1 -1
  57. package/dist/clients/attach/__tests__/file-review-focus.test.js +48 -0
  58. package/dist/clients/attach/__tests__/group-activity.test.js +266 -0
  59. package/dist/clients/attach/__tests__/input-controller-extension-command.test.js +32 -0
  60. package/dist/clients/attach/__tests__/session-identity-refresh.test.js +32 -0
  61. package/dist/clients/attach/__tests__/titled-editor-preview.test.js +1 -2
  62. package/dist/clients/attach/chrome/header.d.ts +10 -0
  63. package/dist/clients/attach/chrome/header.js +30 -0
  64. package/dist/clients/attach/chrome/recap.d.ts +7 -0
  65. package/dist/clients/attach/chrome/recap.js +19 -0
  66. package/dist/clients/attach/chrome/status-line.d.ts +3 -0
  67. package/dist/clients/attach/chrome/status-line.js +5 -0
  68. package/dist/clients/attach/chrome/widgets.js +11 -21
  69. package/dist/clients/attach/command.js +11 -7
  70. package/dist/clients/attach/config.d.ts +4 -0
  71. package/dist/clients/attach/config.js +3 -0
  72. package/dist/clients/attach/input/controller.d.ts +19 -0
  73. package/dist/clients/attach/input/controller.js +62 -28
  74. package/dist/clients/attach/input/titled-editor.d.ts +0 -4
  75. package/dist/clients/attach/input/titled-editor.js +2 -10
  76. package/dist/clients/attach/overlays/file-review.d.ts +9 -5
  77. package/dist/clients/attach/overlays/file-review.js +9 -70
  78. package/dist/clients/attach/overlays/help.d.ts +33 -0
  79. package/dist/clients/attach/overlays/help.js +204 -0
  80. package/dist/clients/attach/render/activity-slot.d.ts +4 -0
  81. package/dist/clients/attach/render/activity-slot.js +13 -0
  82. package/dist/clients/attach/render/chat-view.d.ts +117 -12
  83. package/dist/clients/attach/render/chat-view.js +604 -135
  84. package/dist/clients/attach/render/context-message.d.ts +2 -1
  85. package/dist/clients/attach/render/context-message.js +17 -13
  86. package/dist/clients/attach/render/crtr-output.js +1 -3
  87. package/dist/clients/attach/render/diagram.js +2 -2
  88. package/dist/clients/attach/render/edit-diff.d.ts +4 -1
  89. package/dist/clients/attach/render/edit-diff.js +7 -11
  90. package/dist/clients/attach/render/group-activity.d.ts +32 -0
  91. package/dist/clients/attach/render/group-activity.js +206 -0
  92. package/dist/clients/attach/render/group-recap.d.ts +10 -0
  93. package/dist/clients/attach/render/group-recap.js +93 -0
  94. package/dist/clients/attach/render/html-markdown.d.ts +17 -0
  95. package/dist/clients/attach/render/html-markdown.js +228 -0
  96. package/dist/clients/attach/render/measured-container.d.ts +96 -0
  97. package/dist/clients/attach/render/measured-container.js +322 -0
  98. package/dist/clients/attach/render/scroll-animation.d.ts +8 -0
  99. package/dist/clients/attach/render/scroll-animation.js +80 -0
  100. package/dist/clients/attach/render/tool-calls.js +6 -6
  101. package/dist/clients/attach/render/transcript-copy.d.ts +9 -0
  102. package/dist/clients/attach/render/transcript-copy.js +81 -0
  103. package/dist/clients/attach/render/viewport.d.ts +29 -0
  104. package/dist/clients/attach/render/viewport.js +159 -0
  105. package/dist/clients/attach/session/bindings.d.ts +10 -7
  106. package/dist/clients/attach/session/bindings.js +23 -8
  107. package/dist/clients/attach/session/connection.js +4 -5
  108. package/dist/clients/attach/session/context.d.ts +6 -4
  109. package/dist/clients/attach/session/frame.d.ts +34 -0
  110. package/dist/clients/attach/session/frame.js +176 -0
  111. package/dist/clients/attach/session/frames.d.ts +4 -1
  112. package/dist/clients/attach/session/frames.js +14 -1
  113. package/dist/clients/attach/session/identity.js +4 -3
  114. package/dist/clients/attach/session/input-wiring.d.ts +14 -4
  115. package/dist/clients/attach/session/input-wiring.js +15 -13
  116. package/dist/clients/attach/session/invariant.d.ts +9 -0
  117. package/dist/clients/attach/session/invariant.js +15 -0
  118. package/dist/clients/attach/session/keys.d.ts +10 -2
  119. package/dist/clients/attach/session/keys.js +13 -5
  120. package/dist/clients/attach/session/layout.d.ts +10 -8
  121. package/dist/clients/attach/session/layout.js +33 -34
  122. package/dist/clients/attach/session/mouse.d.ts +18 -0
  123. package/dist/clients/attach/session/mouse.js +64 -0
  124. package/dist/clients/attach/session/pane-tag.d.ts +3 -0
  125. package/dist/clients/attach/session/pane-tag.js +51 -8
  126. package/dist/clients/attach/session/reconnect.d.ts +3 -0
  127. package/dist/clients/attach/session/reconnect.js +42 -5
  128. package/dist/clients/attach/session/surface-frame.d.ts +20 -0
  129. package/dist/clients/attach/session/surface-frame.js +73 -0
  130. package/dist/clients/attach/session/surface-host.d.ts +64 -0
  131. package/dist/clients/attach/session/surface-host.js +255 -0
  132. package/dist/clients/attach/session/whip.d.ts +17 -0
  133. package/dist/clients/attach/session/whip.js +334 -0
  134. package/dist/clients/attach/slash/dispatch.d.ts +13 -1
  135. package/dist/clients/attach/slash/dispatch.js +51 -29
  136. package/dist/clients/attach/viewer.js +907 -819
  137. package/dist/clients/conversation/conversation.d.ts +65 -0
  138. package/dist/clients/conversation/conversation.js +318 -0
  139. package/dist/clients/conversation/frames.d.ts +30 -0
  140. package/dist/clients/conversation/frames.js +52 -0
  141. package/dist/clients/conversation/region.d.ts +51 -0
  142. package/dist/clients/conversation/region.js +265 -0
  143. package/dist/clients/conversation/submit.d.ts +38 -0
  144. package/dist/clients/conversation/submit.js +51 -0
  145. package/dist/clients/inbox/__tests__/inbox-controller.test.js +247 -0
  146. package/dist/clients/inbox/__tests__/mount-panel.test.js +776 -0
  147. package/dist/clients/inbox/controller.d.ts +79 -0
  148. package/dist/clients/inbox/controller.js +448 -0
  149. package/dist/clients/inbox/deck-adapter.d.ts +29 -0
  150. package/dist/clients/inbox/deck-adapter.js +62 -0
  151. package/dist/clients/inbox/layout.d.ts +8 -0
  152. package/dist/clients/inbox/layout.js +9 -0
  153. package/dist/clients/inbox/resolve.d.ts +10 -0
  154. package/dist/clients/inbox/resolve.js +34 -0
  155. package/dist/clients/inbox/review/__tests__/editor-roundtrip.test.js +29 -0
  156. package/dist/clients/inbox/review/__tests__/remap.test.js +196 -0
  157. package/dist/clients/inbox/review/__tests__/review-core.test.js +341 -0
  158. package/dist/clients/inbox/review/anchors.d.ts +41 -0
  159. package/dist/clients/inbox/review/anchors.js +89 -0
  160. package/dist/clients/inbox/review/columns.d.ts +22 -0
  161. package/dist/clients/inbox/review/columns.js +40 -0
  162. package/dist/clients/inbox/review/comments-client.d.ts +66 -0
  163. package/dist/clients/inbox/review/comments-client.js +98 -0
  164. package/dist/clients/inbox/review/composition.d.ts +60 -0
  165. package/dist/clients/inbox/review/composition.js +278 -0
  166. package/dist/clients/inbox/review/document-region.d.ts +120 -0
  167. package/dist/clients/inbox/review/document-region.js +902 -0
  168. package/dist/clients/inbox/review/frame.d.ts +45 -0
  169. package/dist/clients/inbox/review/frame.js +215 -0
  170. package/dist/clients/inbox/review/keys.d.ts +67 -0
  171. package/dist/clients/inbox/review/keys.js +130 -0
  172. package/dist/clients/inbox/review/launch.d.ts +10 -0
  173. package/dist/clients/inbox/review/launch.js +129 -0
  174. package/dist/clients/inbox/review/live-source.d.ts +24 -0
  175. package/dist/clients/inbox/review/live-source.js +160 -0
  176. package/dist/clients/inbox/review/nvim.d.ts +13 -0
  177. package/dist/clients/inbox/review/nvim.js +42 -0
  178. package/dist/clients/inbox/review/remap.d.ts +51 -0
  179. package/dist/clients/inbox/review/remap.js +157 -0
  180. package/dist/clients/inbox/review/review-client.d.ts +30 -0
  181. package/dist/clients/inbox/review/review-client.js +107 -0
  182. package/dist/clients/inbox/review/roundtrip.d.ts +5 -0
  183. package/dist/clients/inbox/review/roundtrip.js +36 -0
  184. package/dist/clients/inbox/review/state.d.ts +143 -0
  185. package/dist/clients/inbox/review/state.js +282 -0
  186. package/dist/clients/inbox/review-adapter.d.ts +29 -0
  187. package/dist/clients/inbox/review-adapter.js +92 -0
  188. package/dist/clients/inbox/review-visibility.d.ts +5 -0
  189. package/dist/clients/inbox/review-visibility.js +19 -0
  190. package/dist/clients/inbox/surface.d.ts +19 -0
  191. package/dist/clients/inbox/surface.js +128 -0
  192. package/dist/clients/inbox/tui/ansi.d.ts +66 -0
  193. package/dist/clients/inbox/tui/ansi.js +317 -0
  194. package/dist/clients/inbox/tui/clipboard.d.ts +2 -0
  195. package/dist/clients/inbox/tui/clipboard.js +26 -0
  196. package/dist/clients/inbox/tui/input.d.ts +7 -0
  197. package/dist/clients/inbox/tui/input.js +653 -0
  198. package/dist/clients/inbox/tui/keys.d.ts +28 -0
  199. package/dist/clients/inbox/tui/keys.js +111 -0
  200. package/dist/clients/inbox/tui/panel.d.ts +2 -0
  201. package/dist/clients/inbox/tui/panel.js +217 -0
  202. package/dist/clients/inbox/tui/render.d.ts +37 -0
  203. package/dist/clients/inbox/tui/render.js +476 -0
  204. package/dist/clients/inbox/tui/types.d.ts +57 -0
  205. package/dist/clients/inbox/tui/types.js +1 -0
  206. package/dist/clients/inbox/tui.d.ts +6 -0
  207. package/dist/clients/inbox/tui.js +74 -0
  208. package/dist/clients/surfaces/host.d.ts +66 -0
  209. package/dist/clients/surfaces/host.js +1 -0
  210. package/dist/clients/surfaces/region.d.ts +16 -0
  211. package/dist/clients/surfaces/region.js +1 -0
  212. package/dist/clients/web/__tests__/push-engine.test.js +14 -158
  213. package/dist/clients/web/dev-server.d.ts +3 -3
  214. package/dist/clients/web/dev-server.js +9 -23
  215. package/dist/clients/web/events.js +4 -6
  216. package/dist/clients/web/push-engine.d.ts +2 -66
  217. package/dist/clients/web/push-engine.js +30 -161
  218. package/dist/clients/web/push-registry.d.ts +0 -4
  219. package/dist/clients/web/push-registry.js +2 -7
  220. package/dist/clients/web/server.js +3 -35
  221. package/dist/clients/web/web-client/shared/protocol.d.ts +0 -108
  222. package/dist/clients/web/web-cmd.js +2 -2
  223. package/dist/commands/__tests__/human.test.js +27 -15
  224. package/dist/commands/api-client.js +3 -3
  225. package/dist/commands/canvas-browse.js +1 -1
  226. package/dist/commands/canvas-issue.js +1 -1
  227. package/dist/commands/canvas-use.js +1 -1
  228. package/dist/commands/human/doc.d.ts +2 -0
  229. package/dist/commands/human/doc.js +98 -0
  230. package/dist/commands/human/inbox.d.ts +2 -0
  231. package/dist/commands/human/inbox.js +101 -0
  232. package/dist/commands/human/prompts.d.ts +2 -3
  233. package/dist/commands/human/prompts.js +27 -180
  234. package/dist/commands/human/queue.d.ts +1 -5
  235. package/dist/commands/human/queue.js +54 -216
  236. package/dist/commands/human/review.d.ts +2 -0
  237. package/dist/commands/human/review.js +298 -0
  238. package/dist/commands/human/shared.d.ts +2 -69
  239. package/dist/commands/human/shared.js +1 -181
  240. package/dist/commands/human.js +15 -20
  241. package/dist/commands/memory/__tests__/lint.test.d.ts +1 -0
  242. package/dist/commands/memory/__tests__/lint.test.js +15 -0
  243. package/dist/commands/memory/delete.js +2 -2
  244. package/dist/commands/memory/lint.d.ts +1 -0
  245. package/dist/commands/memory/lint.js +21 -3
  246. package/dist/commands/memory/read.js +4 -4
  247. package/dist/commands/node.js +8 -7
  248. package/dist/commands/pkg/plugin-manage.js +143 -68
  249. package/dist/commands/surface-inspect.js +2 -2
  250. package/dist/commands/surface.js +6 -12
  251. package/dist/commands/sys/__tests__/setup-core.test.js +9 -5
  252. package/dist/commands/sys/__tests__/setup-front-door.test.js +7 -42
  253. package/dist/commands/sys/config.js +29 -3
  254. package/dist/commands/sys/feedback.js +1 -1
  255. package/dist/commands/sys/setup-core.d.ts +12 -11
  256. package/dist/commands/sys/setup-core.js +55 -46
  257. package/dist/commands/sys/setup-wizard.d.ts +4 -1
  258. package/dist/commands/sys/setup-wizard.js +50 -9
  259. package/dist/commands/sys/sync-deps.js +1 -1
  260. package/dist/commands/sys.js +4 -6
  261. package/dist/core/__tests__/broker-sdk-wiring.test.js +6 -1
  262. package/dist/core/__tests__/broker-snapshot-history.test.js +9 -1
  263. package/dist/core/__tests__/dead-node-policy-table.test.js +3 -4
  264. package/dist/core/__tests__/fixtures/fake-engine.d.ts +8 -5
  265. package/dist/core/__tests__/fixtures/fake-engine.js +28 -2
  266. package/dist/core/__tests__/full/broker-provider-retry.test.js +87 -2
  267. package/dist/core/__tests__/full/broker-whip-interrupt.test.d.ts +1 -0
  268. package/dist/core/__tests__/full/broker-whip-interrupt.test.js +39 -0
  269. package/dist/core/__tests__/full/detach-focus.test.js +1 -1
  270. package/dist/core/__tests__/human-deliver-e2e.test.js +64 -64
  271. package/dist/core/__tests__/human-deliver.test.js +71 -345
  272. package/dist/core/__tests__/inline-memory-refs.test.js +2 -12
  273. package/dist/core/__tests__/memory-resolver-precedence.test.js +12 -15
  274. package/dist/core/__tests__/migration.test.js +10 -0
  275. package/dist/core/__tests__/model-pin-durability.test.js +14 -0
  276. package/dist/core/__tests__/phase4-review-migration.test.d.ts +1 -0
  277. package/dist/core/__tests__/phase4-review-migration.test.js +100 -0
  278. package/dist/core/__tests__/phase4-review-store.test.d.ts +1 -0
  279. package/dist/core/__tests__/phase4-review-store.test.js +143 -0
  280. package/dist/core/__tests__/remote-canvas-target.test.js +2 -2
  281. package/dist/core/__tests__/revive.test.js +22 -53
  282. package/dist/core/__tests__/session-cycles.test.js +48 -18
  283. package/dist/core/__tests__/spawn-no-kickoff.test.js +8 -12
  284. package/dist/core/__tests__/stop-guard.test.js +16 -1
  285. package/dist/core/__tests__/tmux-surface.test.js +21 -25
  286. package/dist/core/asset-root.d.ts +7 -0
  287. package/dist/core/asset-root.js +18 -0
  288. package/dist/core/broker-client/client.d.ts +0 -7
  289. package/dist/core/broker-client/client.js +4 -10
  290. package/dist/core/broker-client/transport-relay.d.ts +3 -4
  291. package/dist/core/broker-client/transport-relay.js +3 -4
  292. package/dist/core/canvas/__tests__/attention.test.js +1 -1
  293. package/dist/core/canvas/__tests__/remote-canvas-source.test.js +1 -1
  294. package/dist/core/canvas/__tests__/remote-transport.test.d.ts +1 -0
  295. package/dist/core/{view/__tests__/transport-remote.test.js → canvas/__tests__/remote-transport.test.js} +2 -2
  296. package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
  297. package/dist/core/canvas/attention.js +4 -4
  298. package/dist/core/canvas/canvas.d.ts +6 -11
  299. package/dist/core/canvas/canvas.js +11 -33
  300. package/dist/core/canvas/db.js +207 -38
  301. package/dist/core/canvas/extensions.d.ts +1 -1
  302. package/dist/core/canvas/extensions.js +8 -1
  303. package/dist/core/canvas/index.d.ts +0 -1
  304. package/dist/core/canvas/index.js +0 -1
  305. package/dist/core/canvas/nav-model.d.ts +1 -1
  306. package/dist/core/canvas/nav-model.js +1 -1
  307. package/dist/core/canvas/paths.d.ts +5 -0
  308. package/dist/core/canvas/paths.js +16 -0
  309. package/dist/core/canvas/remote-canvas-source.js +2 -2
  310. package/dist/core/canvas/remote-transport.d.ts +7 -0
  311. package/dist/core/{view/transport-remote.js → canvas/remote-transport.js} +3 -4
  312. package/dist/core/canvas/source.js +1 -1
  313. package/dist/core/canvas/status-glyph.js +4 -12
  314. package/dist/core/canvas/types.d.ts +10 -35
  315. package/dist/core/clipboard-text.d.ts +8 -0
  316. package/dist/core/clipboard-text.js +18 -0
  317. package/dist/core/command-manifests/registry.d.ts +1 -3
  318. package/dist/core/command-plugins/bundle.d.ts +29 -0
  319. package/dist/core/command-plugins/bundle.js +198 -0
  320. package/dist/core/command-plugins/discovery.d.ts +0 -8
  321. package/dist/core/command-plugins/discovery.js +2 -39
  322. package/dist/core/command-plugins/transport/http-fetch.d.ts +4 -30
  323. package/dist/core/command-plugins/transport/http-fetch.js +17 -95
  324. package/dist/core/command.d.ts +1 -13
  325. package/dist/core/command.js +1 -16
  326. package/dist/core/config.d.ts +4 -0
  327. package/dist/core/config.js +28 -3
  328. package/dist/core/document-lines.d.ts +4 -0
  329. package/dist/core/document-lines.js +6 -0
  330. package/dist/core/feed/feed.d.ts +2 -0
  331. package/dist/core/feed/feed.js +11 -7
  332. package/dist/core/human/__tests__/inbox-cancel-worker.d.ts +1 -0
  333. package/dist/core/human/__tests__/inbox-cancel-worker.js +7 -0
  334. package/dist/core/human/__tests__/inbox-claim-worker.d.ts +1 -0
  335. package/dist/core/human/__tests__/inbox-claim-worker.js +17 -0
  336. package/dist/core/human/__tests__/inbox-core.test.d.ts +1 -0
  337. package/dist/core/human/__tests__/inbox-core.test.js +144 -0
  338. package/dist/core/human/__tests__/inbox-stale-claim-worker.d.ts +1 -0
  339. package/dist/core/human/__tests__/inbox-stale-claim-worker.js +8 -0
  340. package/dist/core/human/__tests__/visible-paths.test.d.ts +1 -0
  341. package/dist/{clients/attach → core/human}/__tests__/visible-paths.test.js +9 -0
  342. package/dist/core/human/claim.d.ts +23 -0
  343. package/dist/core/human/claim.js +67 -0
  344. package/dist/core/human/convention.d.ts +30 -0
  345. package/dist/core/human/convention.js +202 -0
  346. package/dist/core/human/deck-factories.d.ts +8 -0
  347. package/dist/core/human/deck-factories.js +15 -0
  348. package/dist/core/human/deck-schema.d.ts +70 -0
  349. package/dist/core/human/deck-schema.js +92 -0
  350. package/dist/core/human/root.d.ts +7 -0
  351. package/dist/core/human/root.js +37 -0
  352. package/dist/core/human/scan.d.ts +3 -0
  353. package/dist/core/human/scan.js +76 -0
  354. package/dist/core/human/summary.d.ts +8 -0
  355. package/dist/core/human/summary.js +44 -0
  356. package/dist/core/human/tickets.d.ts +49 -0
  357. package/dist/core/human/tickets.js +190 -0
  358. package/dist/core/human/types.d.ts +136 -0
  359. package/dist/core/human/types.js +5 -0
  360. package/dist/{clients/attach → core/human}/visible-paths.d.ts +1 -1
  361. package/dist/{clients/attach → core/human}/visible-paths.js +3 -2
  362. package/dist/core/inspector/chrome.d.ts +6 -0
  363. package/dist/core/inspector/chrome.js +14 -0
  364. package/dist/core/inspector/contract.d.ts +105 -0
  365. package/dist/core/inspector/contract.js +1 -0
  366. package/dist/core/inspector/core.d.ts +4 -5
  367. package/dist/core/inspector/core.js +3 -5
  368. package/dist/core/inspector/host.d.ts +46 -0
  369. package/dist/core/{tui → inspector}/host.js +42 -208
  370. package/dist/core/inspector/text.d.ts +2 -2
  371. package/dist/core/inspector/transport.d.ts +6 -0
  372. package/dist/core/inspector/transport.js +36 -0
  373. package/dist/core/inspector/tui.d.ts +2 -2
  374. package/dist/core/inspector/tui.js +15 -15
  375. package/dist/core/keybindings/__tests__/inbox-affordance.test.js +11 -11
  376. package/dist/core/keybindings/__tests__/resolve.test.js +10 -19
  377. package/dist/core/keybindings/attach-control.d.ts +9 -3
  378. package/dist/core/keybindings/attach-control.js +3 -1
  379. package/dist/core/keybindings/catalog.d.ts +6 -5
  380. package/dist/core/keybindings/catalog.js +61 -133
  381. package/dist/core/keybindings/inbox.d.ts +5 -6
  382. package/dist/core/keybindings/inbox.js +9 -10
  383. package/dist/core/keybindings/index.d.ts +3 -3
  384. package/dist/core/keybindings/index.js +3 -3
  385. package/dist/core/keybindings/persistence.d.ts +5 -1
  386. package/dist/core/keybindings/persistence.js +24 -6
  387. package/dist/core/keybindings/resolve.d.ts +6 -0
  388. package/dist/core/keybindings/resolve.js +10 -3
  389. package/dist/core/memory/inline-ref-grammar.js +2 -3
  390. package/dist/core/memory-resolver.d.ts +27 -29
  391. package/dist/core/memory-resolver.js +45 -39
  392. package/dist/core/model-routes.d.ts +33 -0
  393. package/dist/core/model-routes.js +42 -3
  394. package/dist/core/preview-registry.js +17 -55
  395. package/dist/core/profiles/select.js +3 -3
  396. package/dist/core/{view/remote-canvas-target.d.ts → remote-canvas-target.d.ts} +2 -2
  397. package/dist/core/{view/remote-canvas-target.js → remote-canvas-target.js} +4 -4
  398. package/dist/core/review/__tests__/capture-origin.test.d.ts +1 -0
  399. package/dist/core/review/__tests__/capture-origin.test.js +139 -0
  400. package/dist/core/review/__tests__/comments.test.d.ts +1 -0
  401. package/dist/core/review/__tests__/comments.test.js +98 -0
  402. package/dist/core/review/__tests__/project.test.d.ts +1 -0
  403. package/dist/core/review/__tests__/project.test.js +71 -0
  404. package/dist/core/review/__tests__/remap.test.d.ts +1 -0
  405. package/dist/core/review/__tests__/remap.test.js +159 -0
  406. package/dist/core/review/__tests__/stage-identity.test.d.ts +1 -0
  407. package/dist/core/review/__tests__/stage-identity.test.js +140 -0
  408. package/dist/core/review/birth.d.ts +7 -0
  409. package/dist/core/review/birth.js +23 -0
  410. package/dist/core/review/comments.d.ts +69 -0
  411. package/dist/core/review/comments.js +295 -0
  412. package/dist/core/review/companion.d.ts +35 -0
  413. package/dist/core/review/companion.js +205 -0
  414. package/dist/core/review/document.d.ts +27 -0
  415. package/dist/core/review/document.js +138 -0
  416. package/dist/core/review/project.d.ts +13 -0
  417. package/dist/core/review/project.js +58 -0
  418. package/dist/core/review/realize.d.ts +19 -0
  419. package/dist/core/review/realize.js +217 -0
  420. package/dist/core/review/remap.d.ts +24 -0
  421. package/dist/core/review/remap.js +156 -0
  422. package/dist/core/review/signal.d.ts +5 -0
  423. package/dist/core/review/signal.js +31 -0
  424. package/dist/core/review/stage.d.ts +26 -0
  425. package/dist/core/review/stage.js +184 -0
  426. package/dist/core/review/store.d.ts +46 -0
  427. package/dist/core/review/store.js +163 -0
  428. package/dist/core/review/ticket-filter.d.ts +17 -0
  429. package/dist/core/review/ticket-filter.js +32 -0
  430. package/dist/core/review/types.d.ts +130 -0
  431. package/dist/core/review/types.js +23 -0
  432. package/dist/core/runtime/__tests__/session-visibility.test.d.ts +1 -0
  433. package/dist/core/runtime/__tests__/session-visibility.test.js +119 -0
  434. package/dist/core/runtime/bearings.d.ts +5 -17
  435. package/dist/core/runtime/bearings.js +88 -71
  436. package/dist/core/runtime/broker-protocol.d.ts +31 -8
  437. package/dist/core/runtime/broker.d.ts +15 -1
  438. package/dist/core/runtime/broker.js +573 -141
  439. package/dist/core/runtime/canvas-extensions.d.ts +4 -5
  440. package/dist/core/runtime/canvas-extensions.js +5 -5
  441. package/dist/core/runtime/front-door.js +4 -4
  442. package/dist/core/runtime/headless-pi.d.ts +24 -0
  443. package/dist/core/runtime/headless-pi.js +152 -0
  444. package/dist/core/runtime/kickoff.d.ts +1 -5
  445. package/dist/core/runtime/kickoff.js +1 -5
  446. package/dist/core/runtime/model-registry.d.ts +3 -0
  447. package/dist/core/runtime/model-registry.js +13 -0
  448. package/dist/core/runtime/naming-persist.d.ts +4 -0
  449. package/dist/core/runtime/naming-persist.js +27 -1
  450. package/dist/core/runtime/naming.d.ts +17 -12
  451. package/dist/core/runtime/naming.js +91 -140
  452. package/dist/core/runtime/node-read.js +19 -10
  453. package/dist/core/runtime/nodes.d.ts +9 -14
  454. package/dist/core/runtime/nodes.js +3 -53
  455. package/dist/core/runtime/package-health.d.ts +6 -21
  456. package/dist/core/runtime/package-health.js +12 -123
  457. package/dist/core/runtime/placement-tmux.d.ts +2 -1
  458. package/dist/core/runtime/placement-tmux.js +6 -4
  459. package/dist/core/runtime/placement.d.ts +1 -0
  460. package/dist/core/runtime/placement.js +4 -8
  461. package/dist/core/runtime/promote.js +3 -0
  462. package/dist/core/runtime/recap.d.ts +2 -7
  463. package/dist/core/runtime/recap.js +35 -69
  464. package/dist/core/runtime/revive.js +34 -77
  465. package/dist/core/runtime/session-cycles.d.ts +4 -0
  466. package/dist/core/runtime/session-cycles.js +14 -1
  467. package/dist/core/runtime/session-visibility.d.ts +37 -0
  468. package/dist/core/runtime/session-visibility.js +83 -0
  469. package/dist/core/runtime/spawn.js +10 -22
  470. package/dist/core/runtime/stop-guard.d.ts +1 -1
  471. package/dist/core/runtime/stop-guard.js +18 -13
  472. package/dist/core/runtime/surface-bg.js +1 -1
  473. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  474. package/dist/core/runtime/tmux-chrome.js +1 -1
  475. package/dist/core/runtime/tmux.d.ts +35 -7
  476. package/dist/core/runtime/tmux.js +439 -62
  477. package/dist/core/runtime/tool-group-summary.d.ts +19 -0
  478. package/dist/core/runtime/tool-group-summary.js +88 -0
  479. package/dist/core/scope.d.ts +2 -7
  480. package/dist/core/scope.js +9 -35
  481. package/dist/core/substrate/schema.d.ts +2 -2
  482. package/dist/core/termrender/code-doc.d.ts +12 -0
  483. package/dist/core/termrender/code-doc.js +92 -0
  484. package/dist/core/termrender/display.d.ts +12 -0
  485. package/dist/core/termrender/display.js +19 -0
  486. package/dist/core/termrender/termrender.d.ts +73 -0
  487. package/dist/core/termrender/termrender.js +795 -0
  488. package/dist/core/termrender/version.d.ts +1 -0
  489. package/dist/core/termrender/version.js +1 -0
  490. package/dist/core/tui/terminal.js +1 -2
  491. package/dist/core/user-settings.d.ts +71 -3
  492. package/dist/core/user-settings.js +83 -1
  493. package/dist/daemon/api/__tests__/inbox.test.js +44 -133
  494. package/dist/daemon/api/handlers/human.d.ts +0 -10
  495. package/dist/daemon/api/handlers/human.js +104 -702
  496. package/dist/daemon/api/handlers/inbox.js +113 -142
  497. package/dist/daemon/api/handlers/review-comments.d.ts +2 -0
  498. package/dist/daemon/api/handlers/review-comments.js +500 -0
  499. package/dist/daemon/api/handlers/reviews.d.ts +2 -0
  500. package/dist/daemon/api/handlers/reviews.js +287 -0
  501. package/dist/daemon/api/map.d.ts +22 -7
  502. package/dist/daemon/api/map.js +159 -11
  503. package/dist/daemon/api/server.js +4 -0
  504. package/dist/daemon/cron-run.js +6 -9
  505. package/dist/daemon/crtrd.js +17 -33
  506. package/dist/daemon/fleet.d.ts +1 -8
  507. package/dist/daemon/fleet.js +4 -30
  508. package/dist/daemon/human/finish.d.ts +14 -0
  509. package/dist/daemon/human/finish.js +115 -0
  510. package/dist/daemon/human/sweep.d.ts +6 -0
  511. package/dist/daemon/human/sweep.js +49 -0
  512. package/dist/daemon/messaging/node-message.d.ts +18 -0
  513. package/dist/daemon/messaging/node-message.js +70 -0
  514. package/dist/daemon/review/comment-notify.d.ts +26 -0
  515. package/dist/daemon/review/comment-notify.js +107 -0
  516. package/dist/daemon/review/deliver.d.ts +7 -0
  517. package/dist/daemon/review/deliver.js +56 -0
  518. package/dist/daemon/review/finish.d.ts +22 -0
  519. package/dist/daemon/review/finish.js +254 -0
  520. package/dist/daemon/review/sweep.d.ts +11 -0
  521. package/dist/daemon/review/sweep.js +120 -0
  522. package/dist/index.d.ts +2 -0
  523. package/dist/index.js +2 -2
  524. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +42 -64
  525. package/dist/pi-extensions/__tests__/canvas-recap.test.d.ts +1 -0
  526. package/dist/pi-extensions/__tests__/canvas-recap.test.js +159 -0
  527. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +16 -38
  528. package/dist/pi-extensions/__tests__/canvas-structured-output.test.d.ts +1 -0
  529. package/dist/pi-extensions/__tests__/canvas-structured-output.test.js +63 -0
  530. package/dist/pi-extensions/canvas-context-intro.d.ts +8 -11
  531. package/dist/pi-extensions/canvas-context-intro.js +50 -104
  532. package/dist/pi-extensions/canvas-recap.d.ts +15 -2
  533. package/dist/pi-extensions/canvas-recap.js +192 -69
  534. package/dist/pi-extensions/canvas-review-boundary.d.ts +59 -0
  535. package/dist/pi-extensions/canvas-review-boundary.js +81 -0
  536. package/dist/pi-extensions/canvas-stophook.d.ts +3 -3
  537. package/dist/pi-extensions/canvas-stophook.js +78 -128
  538. package/dist/pi-extensions/canvas-structured-output.js +52 -8
  539. package/dist/shared/generated-context.d.ts +29 -4
  540. package/dist/shared/generated-context.js +122 -7
  541. package/dist/shared/tool-groups.d.ts +23 -0
  542. package/dist/shared/tool-groups.js +60 -0
  543. package/dist/shared/working-activity.d.ts +8 -0
  544. package/dist/shared/working-activity.js +26 -0
  545. package/dist/types.d.ts +36 -2
  546. package/dist/types.js +51 -0
  547. package/dist/web-client/assets/index-CCPaLeW7.css +2 -0
  548. package/dist/web-client/assets/index-kyOWsnZK.js +86 -0
  549. package/dist/web-client/index.html +2 -2
  550. package/dist/web-client/sw.js +1 -1
  551. package/docs/compat/hearth-crtr-v4.md +2 -2
  552. package/docs/compat/hearth-crtr-v5.md +4 -2
  553. package/docs/public-api.md +3 -3
  554. package/package.json +21 -14
  555. package/runtime.lock.json +4661 -1453
  556. package/scripts/postinstall.mjs +15 -2
  557. package/dist/builtin-pi-packages/pi-mode-switch/README.md +0 -36
  558. package/dist/builtin-pi-packages/pi-mode-switch/bin/mode +0 -36
  559. package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +0 -449
  560. package/dist/builtin-pi-packages/pi-mode-switch/package.json +0 -14
  561. package/dist/builtin-pi-packages/pi-mode-switch/tsconfig.json +0 -9
  562. package/dist/builtin-views/_lib/states.mjs +0 -161
  563. package/dist/builtin-views/canvas/core.mjs +0 -657
  564. package/dist/builtin-views/canvas/text.mjs +0 -58
  565. package/dist/builtin-views/canvas/tui.mjs +0 -168
  566. package/dist/builtin-views/canvas/web.jsx +0 -121
  567. package/dist/builtin-views/chat/core.mjs +0 -684
  568. package/dist/builtin-views/chat/text.mjs +0 -101
  569. package/dist/builtin-views/chat/tui.mjs +0 -360
  570. package/dist/builtin-views/chat/web.jsx +0 -362
  571. package/dist/builtin-views/git-pr/core.mjs +0 -673
  572. package/dist/builtin-views/git-pr/text.mjs +0 -84
  573. package/dist/builtin-views/git-pr/tui.mjs +0 -301
  574. package/dist/builtin-views/git-pr/web.jsx +0 -216
  575. package/dist/builtin-views/inbox/_lib/render.mjs +0 -175
  576. package/dist/builtin-views/inbox/core.mjs +0 -1273
  577. package/dist/builtin-views/inbox/text.mjs +0 -73
  578. package/dist/builtin-views/inbox/tui.mjs +0 -314
  579. package/dist/builtin-views/inbox/web.jsx +0 -188
  580. package/dist/builtin-views/linkedin/core.mjs +0 -906
  581. package/dist/builtin-views/linkedin/text.mjs +0 -69
  582. package/dist/builtin-views/linkedin/tui.mjs +0 -413
  583. package/dist/builtin-views/linkedin/web.jsx +0 -206
  584. package/dist/builtin-views/prompt-review/core.mjs +0 -735
  585. package/dist/builtin-views/prompt-review/text.mjs +0 -15
  586. package/dist/builtin-views/prompt-review/tui.mjs +0 -196
  587. package/dist/builtin-views/prompt-review/web.jsx +0 -484
  588. package/dist/builtin-views/workspace-sidebar/__tests__/core.test.js +0 -50
  589. package/dist/builtin-views/workspace-sidebar/__tests__/core.test.ts +0 -53
  590. package/dist/builtin-views/workspace-sidebar/core.mjs +0 -670
  591. package/dist/builtin-views/workspace-sidebar/text.mjs +0 -53
  592. package/dist/builtin-views/workspace-sidebar/tui.mjs +0 -141
  593. package/dist/builtin-views/workspace-sidebar/web.jsx +0 -109
  594. package/dist/clients/attach/session/mode.d.ts +0 -25
  595. package/dist/clients/attach/session/mode.js +0 -97
  596. package/dist/commands/sys/promptstudio.d.ts +0 -2
  597. package/dist/commands/sys/promptstudio.js +0 -65
  598. package/dist/commands/view-cycle.d.ts +0 -2
  599. package/dist/commands/view-cycle.js +0 -130
  600. package/dist/commands/view-list.d.ts +0 -2
  601. package/dist/commands/view-list.js +0 -66
  602. package/dist/commands/view-new.d.ts +0 -2
  603. package/dist/commands/view-new.js +0 -74
  604. package/dist/commands/view-pick.d.ts +0 -6
  605. package/dist/commands/view-pick.js +0 -129
  606. package/dist/commands/view-run.d.ts +0 -2
  607. package/dist/commands/view-run.js +0 -214
  608. package/dist/commands/view.d.ts +0 -2
  609. package/dist/commands/view.js +0 -31
  610. package/dist/commands/workspace.d.ts +0 -2
  611. package/dist/commands/workspace.js +0 -165
  612. package/dist/core/__tests__/chat-view-reconnect.test.js +0 -105
  613. package/dist/core/__tests__/full/consult-startup-failure.test.js +0 -124
  614. package/dist/core/canvas/__tests__/human-work-outbox.test.js +0 -123
  615. package/dist/core/canvas/human-work-outbox.d.ts +0 -73
  616. package/dist/core/canvas/human-work-outbox.js +0 -261
  617. package/dist/core/command-plugins/store.d.ts +0 -16
  618. package/dist/core/command-plugins/store.js +0 -64
  619. package/dist/core/keybindings/__tests__/bespoke-consumers.test.js +0 -40
  620. package/dist/core/runtime/fault-recovery-nudge.d.ts +0 -3
  621. package/dist/core/runtime/fault-recovery-nudge.js +0 -3
  622. package/dist/core/tui/__tests__/host-keybindings.test.js +0 -113
  623. package/dist/core/tui/host.d.ts +0 -66
  624. package/dist/core/view/__tests__/transport-cache.test.js +0 -62
  625. package/dist/core/view/bridge.d.ts +0 -10
  626. package/dist/core/view/bridge.js +0 -31
  627. package/dist/core/view/chrome.d.ts +0 -9
  628. package/dist/core/view/chrome.js +0 -22
  629. package/dist/core/view/contract.d.ts +0 -205
  630. package/dist/core/view/contract.js +0 -23
  631. package/dist/core/view/loader.d.ts +0 -31
  632. package/dist/core/view/loader.js +0 -188
  633. package/dist/core/view/stream-local.d.ts +0 -3
  634. package/dist/core/view/stream-local.js +0 -231
  635. package/dist/core/view/transport-cache.d.ts +0 -8
  636. package/dist/core/view/transport-cache.js +0 -38
  637. package/dist/core/view/transport-local.d.ts +0 -7
  638. package/dist/core/view/transport-local.js +0 -83
  639. package/dist/core/view/transport-remote.d.ts +0 -2
  640. package/dist/core/view/transport.d.ts +0 -10
  641. package/dist/core/view/transport.js +0 -15
  642. package/dist/pi-extensions/naming-tool.d.ts +0 -24
  643. package/dist/pi-extensions/naming-tool.js +0 -67
  644. package/dist/prompts/view.d.ts +0 -7
  645. package/dist/prompts/view.js +0 -172
  646. package/dist/web/ViewChrome.d.ts +0 -7
  647. package/dist/web/ViewChrome.js +0 -28
  648. package/dist/web/ViewPane.d.ts +0 -39
  649. package/dist/web/ViewPane.js +0 -48
  650. package/dist/web/index.d.ts +0 -8
  651. package/dist/web/index.js +0 -22
  652. package/dist/web/runtime.d.ts +0 -39
  653. package/dist/web/runtime.js +0 -232
  654. package/dist/web/states.d.ts +0 -24
  655. package/dist/web/states.js +0 -24
  656. package/dist/web/transport-http.d.ts +0 -5
  657. package/dist/web/transport-http.js +0 -34
  658. package/dist/web/transport-stream.d.ts +0 -3
  659. package/dist/web/transport-stream.js +0 -204
  660. package/dist/web-client/assets/index-U_NZ66VE.js +0 -79
  661. package/dist/web-client/assets/index-aaeu4adv.css +0 -2
  662. /package/dist/{builtin-views/workspace-sidebar/__tests__/core.test.d.ts → clients/attach/__tests__/file-review-focus.test.d.ts} +0 -0
  663. /package/dist/clients/attach/__tests__/{visible-paths.test.d.ts → group-activity.test.d.ts} +0 -0
  664. /package/dist/{core/__tests__/chat-view-reconnect.test.d.ts → clients/attach/__tests__/input-controller-extension-command.test.d.ts} +0 -0
  665. /package/dist/{core/__tests__/full/consult-startup-failure.test.d.ts → clients/attach/__tests__/session-identity-refresh.test.d.ts} +0 -0
  666. /package/dist/{core/canvas/__tests__/human-work-outbox.test.d.ts → clients/inbox/__tests__/inbox-controller.test.d.ts} +0 -0
  667. /package/dist/{core/keybindings/__tests__/bespoke-consumers.test.d.ts → clients/inbox/__tests__/mount-panel.test.d.ts} +0 -0
  668. /package/dist/{core/tui/__tests__/host-keybindings.test.d.ts → clients/inbox/review/__tests__/editor-roundtrip.test.d.ts} +0 -0
  669. /package/dist/{core/view/__tests__/transport-cache.test.d.ts → clients/inbox/review/__tests__/remap.test.d.ts} +0 -0
  670. /package/dist/{core/view/__tests__/transport-remote.test.d.ts → clients/inbox/review/__tests__/review-core.test.d.ts} +0 -0
@@ -0,0 +1,902 @@
1
+ import { relative, resolve } from 'node:path';
2
+ import { CURSOR_MARKER } from '@earendil-works/pi-tui';
3
+ import { documentLines } from '../../../core/document-lines.js';
4
+ import { codeRenderedDoc } from '../../../core/termrender/code-doc.js';
5
+ import { deriveAnchorUnits, remapUnitIndex, unitIndexForLine, unitRangeForSpan } from './anchors.js';
6
+ import { centerHorizontal, clipLine, CYAN, DIM, RESET, REVERSE, sanitize, truncate, YELLOW } from '../tui/ansi.js';
7
+ import { ReviewDocumentError } from './comments-client.js';
8
+ import { PAN_STEP, docSurface, maxHScroll, panBy, renderReviewDoc, renderReviewFrame, renderWidthForCols, reservedRows, } from './frame.js';
9
+ import { matchReviewKey, reviewKeyText } from './keys.js';
10
+ import { renderInputBuffer } from '../tui/render.js';
11
+ import { openReviewedFileInEditor } from './nvim.js';
12
+ import { buildLineMap, remapRange } from './remap.js';
13
+ import { cleanPastedText, closeCompose, closeHelp, closeList, commitCompose, ensureAnchorVisible, initReviewState, moveActiveUnit, moveListIndex, openComposeEdit, openComposeNew, openHelp, openList, scrollBy, textBackspace, textDelete, textEnd, textHome, textInsert, textVerticalWrapped, textWordBackspace, textWordLeft, textWordRight, deleteAtListIndex, undoLast, } from './state.js';
14
+ /** The reviewed document as a composed region: existing review rendering and
15
+ * navigation over a live file, with comments projected from the daemon and
16
+ * ranges remapped by this terminal as the file changes. */
17
+ export class DocumentRegion {
18
+ absFile;
19
+ fileLabel;
20
+ kind;
21
+ source;
22
+ comments;
23
+ services;
24
+ unsubscribeComments;
25
+ ready;
26
+ cols = 0;
27
+ rows = 0;
28
+ focused = false;
29
+ closed = false;
30
+ spaceArmed = false;
31
+ state;
32
+ cache;
33
+ /** Every non-deleted comment the daemon currently projects, newest last. */
34
+ projection = [];
35
+ status = 'preparing';
36
+ /** Region-local error/summary state — never a host notice (D1). */
37
+ problem;
38
+ composeAnchorLost = false;
39
+ dirty;
40
+ /** Serializes base-hash transitions so B→C cannot overtake A→B. */
41
+ remapChain = Promise.resolve();
42
+ constructor(opts) {
43
+ this.absFile = opts.absFile;
44
+ this.fileLabel = labelFor(opts.cwd, opts.absFile);
45
+ this.kind = docSurface(opts.absFile);
46
+ this.source = opts.source;
47
+ this.comments = opts.comments;
48
+ this.services = opts.services;
49
+ this.source.onChange((next, previous) => {
50
+ this.onSourceChanged(next, previous);
51
+ });
52
+ this.unsubscribeComments = this.comments.onChanged(() => {
53
+ void this.refreshProjection();
54
+ });
55
+ this.ready = this.align().catch((error) => {
56
+ if (this.closed)
57
+ return;
58
+ this.status = 'refused';
59
+ this.problem = describe(error);
60
+ this.repaint();
61
+ });
62
+ }
63
+ /** Resolves once the daemon coordinate base has been aligned to the file on
64
+ * disk. The owner may await it so the first painted frame already carries
65
+ * current ranges; painting earlier shows only a preparing line, never a
66
+ * comment at a coordinate the daemon has not accepted. */
67
+ open() {
68
+ return this.ready;
69
+ }
70
+ hasUnsentCompose() {
71
+ const state = this.state;
72
+ return state?.mode === 'compose' && state.compose !== null && state.compose.buffer.length > 0;
73
+ }
74
+ // ── SurfaceRegion ─────────────────────────────────────────────────────────
75
+ setViewport(cols, rows) {
76
+ const nextCols = Math.max(0, Math.floor(cols));
77
+ const nextRows = Math.max(0, Math.floor(rows));
78
+ if (nextCols === this.cols && nextRows === this.rows)
79
+ return;
80
+ this.cols = nextCols;
81
+ this.rows = nextRows;
82
+ this.reflow();
83
+ }
84
+ render() {
85
+ if (this.rows <= 0 || this.cols <= 0)
86
+ return [];
87
+ if (this.status !== 'ready' || this.state === undefined)
88
+ return this.renderPlaceholder();
89
+ const doc = this.document(this.cols, this.state.sourceRevision);
90
+ const lines = renderReviewFrame(this.state, this.fileLabel, doc, this.cols, this.rows, {
91
+ lineNumbers: this.kind.doc === 'code',
92
+ ...(this.footerLine() === undefined ? {} : { notice: this.footerLine() }),
93
+ });
94
+ const grouped = this.state.mode === 'list' && this.state.comments.some((comment) => comment.anchorStatus === 'detached')
95
+ ? this.renderGroupedList(lines)
96
+ : lines;
97
+ const rendered = this.state.mode === 'compose' && this.composeAnchorLost
98
+ ? this.statedComposeAnchor(grouped)
99
+ : grouped;
100
+ return this.focused ? this.markCursorLocation(rendered, this.state) : rendered;
101
+ }
102
+ handleInput(data) {
103
+ if (this.closed || this.status !== 'ready')
104
+ return false;
105
+ const state = this.state;
106
+ if (state === undefined)
107
+ return false;
108
+ const key = matchReviewKey(data);
109
+ if (key === null)
110
+ return false;
111
+ if (state.mode === 'compose') {
112
+ this.handleComposeKey(key);
113
+ this.repaint();
114
+ return true;
115
+ }
116
+ if (this.spaceArmed) {
117
+ this.handleLeaderKey(key);
118
+ return true;
119
+ }
120
+ if (state.mode === 'list') {
121
+ const consumed = this.handleListKey(key);
122
+ this.repaint();
123
+ return consumed;
124
+ }
125
+ if (state.mode === 'help') {
126
+ if (key.type === 'help' || (key.type === 'cancel' && key.text === undefined)) {
127
+ this.state = closeHelp(state);
128
+ this.repaint();
129
+ return true;
130
+ }
131
+ if (key.type === 'cancel') {
132
+ this.state = closeHelp(state);
133
+ this.repaint();
134
+ return true;
135
+ }
136
+ return false;
137
+ }
138
+ return this.handleViewKey(key);
139
+ }
140
+ handleWheel(direction, lines) {
141
+ const state = this.state;
142
+ if (this.closed || this.status !== 'ready' || state === undefined)
143
+ return false;
144
+ const doc = this.document(this.cols, state.sourceRevision);
145
+ const delta = (direction === 'up' ? -1 : 1) * Math.max(1, Math.floor(lines));
146
+ this.state = scrollBy(state, delta, this.bodyHeight(state), doc.lines.length);
147
+ this.repaint();
148
+ return true;
149
+ }
150
+ setFocused(value) {
151
+ if (this.focused === value)
152
+ return;
153
+ this.focused = value;
154
+ this.repaint();
155
+ }
156
+ summary() {
157
+ if (this.status === 'refused')
158
+ return `${this.fileLabel} — unavailable`;
159
+ if (this.status === 'preparing')
160
+ return `${this.fileLabel} — preparing`;
161
+ const open = this.projection.filter((comment) => comment.status === 'open').length;
162
+ const detached = this.projection.filter((comment) => comment.anchorStatus === 'detached').length;
163
+ const parts = [this.fileLabel, `${open} open`];
164
+ if (detached > 0)
165
+ parts.push(`${detached} detached`);
166
+ if (this.source.current().status === 'stale')
167
+ parts.push(`stale (${this.source.current().staleReason ?? 'unreadable'})`);
168
+ return parts.join(' · ');
169
+ }
170
+ onDirty(fn) {
171
+ this.dirty = fn;
172
+ }
173
+ /** Releases this region's render cache and stops every projected repaint.
174
+ * This region closes the source it was handed; that close stops its watcher. */
175
+ close() {
176
+ if (this.closed)
177
+ return;
178
+ // Stop the only source of new synchronous callbacks before closing the
179
+ // region to every already-in-flight asynchronous continuation.
180
+ this.source.close();
181
+ this.closed = true;
182
+ this.unsubscribeComments();
183
+ this.cache = undefined;
184
+ this.dirty = undefined;
185
+ }
186
+ // ── Open-time base alignment ──────────────────────────────────────────────
187
+ /** Bring the daemon's comment coordinates onto the bytes now on disk, then
188
+ * build the first state from what the daemon accepted. The zero-comment case
189
+ * still submits its batch: the base digest itself is what advances, and a
190
+ * later create against current bytes is rejected until it does. */
191
+ async align() {
192
+ // Each pass re-reads the daemon base AND the file, because a write landing
193
+ // during an await would otherwise leave the base aligned to bytes that are
194
+ // already gone. A pass whose source moved under it is discarded and retried
195
+ // against the settled file rather than committed at a hash nobody holds.
196
+ for (;;) {
197
+ const base = await this.comments.anchorBase();
198
+ if (this.closed)
199
+ return;
200
+ if (resolve(base.file) !== resolve(this.absFile)) {
201
+ throw new Error(`review document mismatch — the daemon's canonical file is ${base.file}, not ${this.absFile}`);
202
+ }
203
+ const snapshot = this.source.current();
204
+ if (snapshot.status === 'stale') {
205
+ throw new Error(`the reviewed file cannot be read (${snapshot.staleReason ?? 'unreadable'})`);
206
+ }
207
+ if (base.docHash !== snapshot.sha256) {
208
+ const listed = await this.comments.list();
209
+ if (this.closed)
210
+ return;
211
+ if (this.movedSince(snapshot))
212
+ continue;
213
+ const map = buildLineMap(documentLines(base.text), snapshot.lines);
214
+ await this.comments.remapRanges({
215
+ fromDocHash: base.docHash,
216
+ toDocHash: snapshot.sha256,
217
+ entries: batchEntries(nonDeleted(listed), map),
218
+ });
219
+ if (this.closed)
220
+ return;
221
+ }
222
+ const comments = await this.comments.list();
223
+ if (this.closed)
224
+ return;
225
+ if (this.movedSince(snapshot))
226
+ continue;
227
+ this.projection = nonDeleted(comments);
228
+ this.status = 'ready';
229
+ this.buildState(snapshot);
230
+ this.repaint();
231
+ return;
232
+ }
233
+ }
234
+ movedSince(snapshot) {
235
+ const current = this.source.current();
236
+ return current.status === 'live' && current.sha256 !== snapshot.sha256;
237
+ }
238
+ /** Re-derive this region from the daemon's own coordinate base. The only
239
+ * honest recovery once a batch is rejected: the local map that produced it
240
+ * is no longer a true description of what the daemon holds. */
241
+ async realign() {
242
+ if (this.closed)
243
+ return;
244
+ try {
245
+ await this.align();
246
+ }
247
+ catch (error) {
248
+ if (this.closed)
249
+ return;
250
+ this.status = 'refused';
251
+ this.problem = describe(error);
252
+ this.repaint();
253
+ }
254
+ }
255
+ buildState(snapshot) {
256
+ const doc = this.document(this.cols, snapshot.revision);
257
+ this.state = initReviewState(snapshot.lines, deriveAnchorUnits(doc), orderedForDisplay(this.projection), snapshot.revision);
258
+ this.state = ensureAnchorVisible(this.state, doc, this.bodyHeight(this.state));
259
+ }
260
+ // ── Live source ───────────────────────────────────────────────────────────
261
+ onSourceChanged(next, previous) {
262
+ if (this.closed || this.status !== 'ready')
263
+ return;
264
+ const state = this.state;
265
+ if (state === undefined)
266
+ return;
267
+ if (next.status === 'stale') {
268
+ this.problem = undefined;
269
+ this.repaint();
270
+ return;
271
+ }
272
+ if (next.text === previous.text) {
273
+ this.problem = undefined;
274
+ this.repaint();
275
+ return;
276
+ }
277
+ // Order is load-bearing: every coordinate is remapped through ONE map built
278
+ // from the outgoing text, before any unit is re-derived from the new render.
279
+ const map = buildLineMap(previous.lines, next.lines);
280
+ const commentRanges = new Map();
281
+ for (const comment of this.projection) {
282
+ commentRanges.set(comment.id, comment.anchorStatus === 'detached'
283
+ ? { range: comment.originalRange ?? { line: comment.line, endLine: comment.endLine }, detached: true }
284
+ : mapRange(map, { line: comment.line, endLine: comment.endLine }));
285
+ }
286
+ // A lost compose anchor stays lost: remapping its old numbers against
287
+ // whatever now occupies them would silently re-point the buffer at
288
+ // unrelated text. Only cancelling or opening a new compose clears it.
289
+ const composeAnchor = state.compose === null || this.composeAnchorLost
290
+ ? undefined
291
+ : mapRange(map, state.compose.anchor);
292
+ const activeRange = mapRange(map, unitRange(state, state.activeUnit));
293
+ const selectionRange = state.selectionAnchor === null
294
+ ? undefined
295
+ : mapRange(map, unitRange(state, state.selectionAnchor));
296
+ const topLine = mapLine(map, this.topVisibleSourceLine(state), previous.lines.length);
297
+ const doc = this.document(this.cols, next.revision);
298
+ const { units, rowUnits } = deriveAnchorUnits(doc);
299
+ const active = unitRangeForSpan(units, activeRange.range.line, activeRange.range.endLine).hi;
300
+ const selection = selectionRange === undefined
301
+ ? null
302
+ : unitRangeForSpan(units, selectionRange.range.line, selectionRange.range.endLine).lo;
303
+ const comments = this.projection.map((comment) => {
304
+ const mapped = commentRanges.get(comment.id);
305
+ if (mapped === undefined)
306
+ return comment;
307
+ return applyMappedRange(comment, mapped);
308
+ });
309
+ this.projection = comments;
310
+ const detached = {};
311
+ for (const comment of comments) {
312
+ if (comment.anchorStatus === 'detached') {
313
+ detached[comment.id] = comment.originalRange ?? { line: comment.line, endLine: comment.endLine };
314
+ }
315
+ }
316
+ this.composeAnchorLost = this.composeAnchorLost || composeAnchor?.detached === true;
317
+ const compose = state.compose === null || composeAnchor === undefined
318
+ ? state.compose
319
+ : { ...state.compose, anchor: composeAnchor.range };
320
+ let nextState = {
321
+ ...state,
322
+ sourceLines: next.lines,
323
+ units,
324
+ rowUnits,
325
+ comments: orderedForDisplay(comments),
326
+ sourceRevision: next.revision,
327
+ detached,
328
+ activeUnit: active,
329
+ selectionAnchor: selection,
330
+ compose,
331
+ listIndex: Math.max(0, Math.min(state.listIndex, comments.length - 1)),
332
+ scroll: units[unitIndexForLine(units, topLine)]?.firstRow ?? 0,
333
+ };
334
+ nextState = ensureAnchorVisible(nextState, doc, this.bodyHeight(nextState));
335
+ this.state = nextState;
336
+ this.problem = this.composeAnchorLost ? 'the commented lines were removed — this comment has no anchor' : undefined;
337
+ this.repaint();
338
+ this.remapChain = this.remapChain.then(() => this.submitBatch(previous.sha256, next.sha256, commentRanges), () => this.submitBatch(previous.sha256, next.sha256, commentRanges));
339
+ }
340
+ /** One transaction covering every non-deleted comment. A rejection is stated
341
+ * in this region's own footer and the projection is re-read from the
342
+ * authority, which is the only thing that can still be true afterwards. */
343
+ async submitBatch(fromDocHash, toDocHash, ranges) {
344
+ if (this.closed)
345
+ return;
346
+ const entries = [...ranges].map(([id, mapped]) => mapped.detached
347
+ ? { id, detached: true }
348
+ : { id, anchor: mapped.range });
349
+ try {
350
+ await this.comments.remapRanges({ fromDocHash, toDocHash, entries });
351
+ if (this.closed)
352
+ return;
353
+ await this.refreshProjection();
354
+ }
355
+ catch (error) {
356
+ if (this.closed)
357
+ return;
358
+ this.problem = `comment ranges were rejected — ${describe(error)}`;
359
+ this.repaint();
360
+ await this.realign();
361
+ }
362
+ }
363
+ // ── Daemon-projected comments ─────────────────────────────────────────────
364
+ async refreshProjection() {
365
+ if (this.closed || this.status === 'refused')
366
+ return;
367
+ try {
368
+ const comments = nonDeleted(await this.comments.list());
369
+ if (this.closed)
370
+ return;
371
+ this.projection = comments;
372
+ const state = this.state;
373
+ if (state !== undefined) {
374
+ const ordered = orderedForDisplay(comments);
375
+ this.state = {
376
+ ...state,
377
+ comments: ordered,
378
+ detached: Object.fromEntries(ordered.flatMap((comment) => comment.anchorStatus === 'detached'
379
+ ? [[comment.id, comment.originalRange ?? { line: comment.line, endLine: comment.endLine }]]
380
+ : [])),
381
+ listIndex: Math.max(0, Math.min(state.listIndex, ordered.length - 1)),
382
+ };
383
+ }
384
+ this.repaint();
385
+ }
386
+ catch (error) {
387
+ if (this.closed)
388
+ return;
389
+ this.problem = `comments could not be read — ${describe(error)}`;
390
+ this.repaint();
391
+ }
392
+ }
393
+ async mutate(operation) {
394
+ if (this.closed)
395
+ return;
396
+ try {
397
+ await operation();
398
+ if (this.closed)
399
+ return;
400
+ this.problem = undefined;
401
+ }
402
+ catch (error) {
403
+ if (this.closed)
404
+ return;
405
+ this.problem = error instanceof ReviewDocumentError
406
+ ? `${error.code.replace(/_/g, ' ')} — the document moved under this comment`
407
+ : describe(error);
408
+ this.repaint();
409
+ }
410
+ await this.refreshProjection();
411
+ }
412
+ // ── Keys ──────────────────────────────────────────────────────────────────
413
+ handleComposeKey(key) {
414
+ const state = this.state;
415
+ const compose = state?.compose;
416
+ if (state === undefined || compose === null || compose === undefined)
417
+ return;
418
+ if (key.type === 'cancel' && key.text === undefined) {
419
+ this.composeAnchorLost = false;
420
+ this.state = closeCompose(state);
421
+ return;
422
+ }
423
+ if (key.type === 'confirm') {
424
+ if (this.composeAnchorLost) {
425
+ this.problem = 'comment not saved — its anchored lines were removed';
426
+ return;
427
+ }
428
+ const committed = commitCompose(state);
429
+ this.composeAnchorLost = false;
430
+ this.state = committed.state;
431
+ const commit = committed.commit;
432
+ if (commit === null)
433
+ return;
434
+ const docHash = this.source.current().sha256;
435
+ if (commit.editingId === null) {
436
+ void this.mutate(() => this.comments.create({
437
+ line: commit.anchor.line,
438
+ endLine: commit.anchor.endLine,
439
+ text: commit.text,
440
+ docHash,
441
+ }));
442
+ return;
443
+ }
444
+ const editing = this.projection.find((comment) => comment.id === commit.editingId);
445
+ if (editing === undefined) {
446
+ this.problem = 'that comment no longer exists';
447
+ return;
448
+ }
449
+ void this.mutate(() => this.comments.edit({
450
+ id: editing.id,
451
+ text: commit.text,
452
+ ifRevision: editing.revision,
453
+ }));
454
+ return;
455
+ }
456
+ if (key.type === 'composeNewline') {
457
+ this.state = { ...state, compose: { ...compose, ...textInsert(compose.buffer, compose.cursor, '\n') } };
458
+ return;
459
+ }
460
+ if (key.type === 'composeWordBackspace') {
461
+ this.state = { ...state, compose: { ...compose, ...textWordBackspace(compose.buffer, compose.cursor) } };
462
+ return;
463
+ }
464
+ if (key.type === 'composeBackspace') {
465
+ this.state = { ...state, compose: { ...compose, ...textBackspace(compose.buffer, compose.cursor) } };
466
+ return;
467
+ }
468
+ if (key.type === 'composeDelete') {
469
+ this.state = { ...state, compose: { ...compose, ...textDelete(compose.buffer, compose.cursor) } };
470
+ return;
471
+ }
472
+ if (key.type === 'composeMove') {
473
+ const cursor = key.direction === 'wordLeft'
474
+ ? textWordLeft(compose.buffer, compose.cursor)
475
+ : textWordRight(compose.buffer, compose.cursor);
476
+ this.state = { ...state, compose: { ...compose, cursor } };
477
+ return;
478
+ }
479
+ if (key.type === 'arrow' && key.direction === 'left') {
480
+ this.state = { ...state, compose: { ...compose, cursor: Math.max(0, compose.cursor - 1) } };
481
+ return;
482
+ }
483
+ if (key.type === 'arrow' && key.direction === 'right') {
484
+ this.state = {
485
+ ...state,
486
+ compose: { ...compose, cursor: Math.min([...compose.buffer].length, compose.cursor + 1) },
487
+ };
488
+ return;
489
+ }
490
+ if (key.type === 'composeHome') {
491
+ this.state = { ...state, compose: { ...compose, cursor: textHome(compose.buffer, compose.cursor) } };
492
+ return;
493
+ }
494
+ if (key.type === 'composeEnd') {
495
+ this.state = { ...state, compose: { ...compose, cursor: textEnd(compose.buffer, compose.cursor) } };
496
+ return;
497
+ }
498
+ if (key.type === 'arrow' && (key.direction === 'up' || key.direction === 'down')) {
499
+ const delta = key.direction === 'up' ? -1 : 1;
500
+ this.state = {
501
+ ...state,
502
+ compose: {
503
+ ...compose,
504
+ cursor: textVerticalWrapped(compose.buffer, compose.cursor, delta, this.composeWidth()),
505
+ },
506
+ };
507
+ return;
508
+ }
509
+ const text = reviewKeyText(key);
510
+ if (text === undefined)
511
+ return;
512
+ if (key.type === 'paste') {
513
+ const clean = cleanPastedText(text);
514
+ if (clean.length === 0)
515
+ return;
516
+ this.state = { ...state, compose: { ...compose, ...textInsert(compose.buffer, compose.cursor, clean) } };
517
+ return;
518
+ }
519
+ this.state = { ...state, compose: { ...compose, ...textInsert(compose.buffer, compose.cursor, text) } };
520
+ }
521
+ handleLeaderKey(key) {
522
+ const state = this.state;
523
+ this.spaceArmed = false;
524
+ if (state === undefined)
525
+ return;
526
+ const input = reviewKeyText(key);
527
+ if (input === 'c') {
528
+ const opened = openComposeNew(state);
529
+ this.composeAnchorLost = false;
530
+ this.state = ensureAnchorVisible(opened, this.document(this.cols, opened.sourceRevision), this.bodyHeight(opened));
531
+ }
532
+ else if (input === 'l') {
533
+ this.state = openList(state);
534
+ }
535
+ else if (input === 'u') {
536
+ const undone = undoLast(state);
537
+ if (undone.id !== null) {
538
+ const id = undone.id;
539
+ void this.mutate(() => this.comments.remove(id));
540
+ }
541
+ }
542
+ else if (input === 'e') {
543
+ this.editInEditor();
544
+ }
545
+ else if (input === 's') {
546
+ const snapshot = this.source.current();
547
+ if (snapshot.status === 'stale') {
548
+ this.problem = `approval refused while the source is ${snapshot.staleReason ?? 'unreadable'}`;
549
+ }
550
+ else {
551
+ this.services.submit();
552
+ }
553
+ }
554
+ this.repaint();
555
+ }
556
+ handleListKey(key) {
557
+ const state = this.state;
558
+ if (state === undefined)
559
+ return false;
560
+ if (key.type === 'cancel') {
561
+ this.state = closeList(state);
562
+ return true;
563
+ }
564
+ if ((key.type === 'moveUnit' && key.text === 'j') || (key.type === 'arrow' && key.direction === 'down')) {
565
+ this.state = moveListIndex(state, 1);
566
+ return true;
567
+ }
568
+ if ((key.type === 'moveUnit' && key.text === 'k') || (key.type === 'arrow' && key.direction === 'up')) {
569
+ this.state = moveListIndex(state, -1);
570
+ return true;
571
+ }
572
+ if (key.type === 'listEdit' || key.type === 'confirm') {
573
+ if (state.comments.length > 0) {
574
+ const opened = openComposeEdit(state, state.listIndex);
575
+ this.composeAnchorLost = false;
576
+ this.state = ensureAnchorVisible(opened, this.document(this.cols, opened.sourceRevision), this.bodyHeight(opened));
577
+ }
578
+ return true;
579
+ }
580
+ if (key.type === 'scrollPage' && key.text === 'd' && state.comments.length > 0) {
581
+ const deleted = deleteAtListIndex(state);
582
+ if (deleted.id !== null) {
583
+ const id = deleted.id;
584
+ void this.mutate(() => this.comments.remove(id));
585
+ }
586
+ return true;
587
+ }
588
+ if (key.type === 'leader') {
589
+ this.spaceArmed = true;
590
+ return true;
591
+ }
592
+ return false;
593
+ }
594
+ handleViewKey(key) {
595
+ const state = this.state;
596
+ if (state === undefined)
597
+ return false;
598
+ const doc = this.document(this.cols, state.sourceRevision);
599
+ const bodyHeight = this.bodyHeight(state);
600
+ if (key.type === 'leader') {
601
+ this.spaceArmed = true;
602
+ return true;
603
+ }
604
+ if (key.type === 'moveUnit' || (key.type === 'arrow' && (key.direction === 'up' || key.direction === 'down'))) {
605
+ const delta = key.type === 'moveUnit' ? key.delta : key.direction === 'down' ? 1 : -1;
606
+ this.state = ensureAnchorVisible(moveActiveUnit(state, delta, false), doc, bodyHeight);
607
+ }
608
+ else if (key.type === 'extendUnit') {
609
+ this.state = ensureAnchorVisible(moveActiveUnit(state, key.delta, true), doc, bodyHeight);
610
+ }
611
+ else if (key.type === 'scrollPage') {
612
+ this.state = scrollBy(state, key.delta * bodyHeight, bodyHeight, doc.lines.length);
613
+ }
614
+ else if ((key.type === 'pan' || (key.type === 'arrow' && (key.direction === 'left' || key.direction === 'right')))
615
+ && maxHScroll(state, doc, this.cols, this.rows, this.gutterWidth()) > 0) {
616
+ const delta = key.type === 'pan' ? key.delta : key.direction === 'right' ? 1 : -1;
617
+ this.state = panBy(state, delta * PAN_STEP, doc, this.cols, this.rows, this.gutterWidth());
618
+ }
619
+ else if (key.type === 'help') {
620
+ this.state = openHelp(state);
621
+ }
622
+ else if (key.type === 'cancel' && key.text === undefined) {
623
+ // Esc from the document belongs to the composed owner, which decides
624
+ // whether an unsent comment should hold the surface open.
625
+ return false;
626
+ }
627
+ else if (key.type === 'cancel') {
628
+ return false;
629
+ }
630
+ else {
631
+ return false;
632
+ }
633
+ this.repaint();
634
+ return true;
635
+ }
636
+ /** The reviewed file is the deliverable: the person's own editor writes it,
637
+ * this region never does. The source is re-read the moment the editor exits
638
+ * rather than waiting for the watcher's coalescing window. */
639
+ editInEditor() {
640
+ const state = this.state;
641
+ if (state === undefined)
642
+ return;
643
+ const line = state.units[state.activeUnit]?.start ?? 1;
644
+ const resume = this.services.suspendForChild();
645
+ try {
646
+ const outcome = openReviewedFileInEditor(this.absFile, line);
647
+ if (outcome.error !== undefined)
648
+ this.problem = `editor failed — ${outcome.error}`;
649
+ }
650
+ finally {
651
+ resume();
652
+ }
653
+ this.source.refreshNow();
654
+ }
655
+ // ── Geometry and rendering ────────────────────────────────────────────────
656
+ /** The composition owns cursor focus; its focused document region contributes
657
+ * exactly one whole marker at the same visual location the document already
658
+ * calls active. There is deliberately no synthetic fallback row: a marker is
659
+ * only valid when it belongs to a rendered anchor or the compose/list cursor. */
660
+ markCursorLocation(lines, state) {
661
+ if (state.mode === 'compose')
662
+ return markAtToken(lines, `${REVERSE} ${RESET}`);
663
+ if (state.mode === 'list') {
664
+ const markedListCursor = markAtToken(lines, `${CYAN}▸${RESET}`);
665
+ if (markedListCursor !== lines)
666
+ return markedListCursor;
667
+ }
668
+ const row = this.activeAnchorFrameRow(state);
669
+ return row === undefined ? lines : markAtToken(lines, `${CYAN}▌${RESET}`, row);
670
+ }
671
+ /** The frame paints source rows after its three-line header. Scroll indicators
672
+ * replace their source rows, so they are never cursor targets. */
673
+ activeAnchorFrameRow(state) {
674
+ const bodyHeight = Math.max(1, this.rows - reservedRows(state, this.cols));
675
+ const total = state.rowUnits.length;
676
+ const scroll = Math.max(0, Math.min(state.scroll, Math.max(0, total - bodyHeight)));
677
+ for (let bodyRow = 0; bodyRow < bodyHeight; bodyRow++) {
678
+ const sourceRow = scroll + bodyRow;
679
+ const replacedByIndicator = (bodyRow === 0 && scroll > 0)
680
+ || (bodyRow === bodyHeight - 1 && sourceRow < total - 1);
681
+ if (!replacedByIndicator && state.rowUnits[sourceRow] === state.activeUnit)
682
+ return 3 + bodyRow;
683
+ }
684
+ return undefined;
685
+ }
686
+ reflow() {
687
+ const state = this.state;
688
+ if (state === undefined || this.cols <= 0)
689
+ return;
690
+ const previousActive = state.units[state.activeUnit];
691
+ const previousSelection = state.selectionAnchor === null ? undefined : state.units[state.selectionAnchor];
692
+ const doc = this.document(this.cols, state.sourceRevision);
693
+ const { units, rowUnits } = deriveAnchorUnits(doc);
694
+ const reflowed = {
695
+ ...state,
696
+ units,
697
+ rowUnits,
698
+ activeUnit: previousActive === undefined ? 0 : remapUnitIndex(units, previousActive),
699
+ selectionAnchor: previousSelection === undefined ? null : remapUnitIndex(units, previousSelection),
700
+ };
701
+ this.state = ensureAnchorVisible(reflowed, doc, this.bodyHeight(reflowed));
702
+ }
703
+ document(width, sourceRevision) {
704
+ const cached = this.cache;
705
+ if (cached !== undefined && cached.width === width && cached.sourceRevision === sourceRevision)
706
+ return cached.doc;
707
+ const text = this.source.current().text;
708
+ const doc = this.kind.doc === 'code'
709
+ ? codeRenderedDoc(text, this.kind.language ?? 'text', renderWidthForCols(width))
710
+ : renderReviewDoc(text, width);
711
+ this.cache = { width, sourceRevision, doc };
712
+ return doc;
713
+ }
714
+ bodyHeight(state) {
715
+ const groupedListRows = state.mode === 'list' && state.comments.some((comment) => comment.anchorStatus === 'detached')
716
+ ? 1 + (state.comments.some((comment) => comment.anchorStatus !== 'detached') ? 1 : 0)
717
+ : 0;
718
+ return Math.max(1, this.rows - reservedRows(state, this.cols) - groupedListRows);
719
+ }
720
+ composeWidth() {
721
+ return Math.max(10, Math.min(Math.max(20, this.cols - 4), 120) - 2);
722
+ }
723
+ gutterWidth() {
724
+ return this.kind.doc === 'code' ? String(this.source.current().lines.length).length : 0;
725
+ }
726
+ /** The region's own footer line: its error, then its stale verdict. */
727
+ footerLine() {
728
+ if (this.problem !== undefined)
729
+ return this.problem;
730
+ const snapshot = this.source.current();
731
+ if (snapshot.status === 'stale') {
732
+ return `source ${snapshot.staleReason ?? 'unreadable'} — showing the last good read; approval is refused`;
733
+ }
734
+ return undefined;
735
+ }
736
+ renderPlaceholder() {
737
+ const message = this.status === 'refused'
738
+ ? `Review unavailable — ${this.problem ?? 'the document could not be prepared'}`
739
+ : `Preparing ${this.fileLabel}…`;
740
+ const lines = ['', ` ${message}`];
741
+ while (lines.length < this.rows)
742
+ lines.push('');
743
+ return lines.slice(0, this.rows).map((line) => clipLine(line, this.cols));
744
+ }
745
+ /** `frame.ts` renders the ordinary list. Detached anchors need stronger
746
+ * treatment: their own leading group plus the original range and snippet,
747
+ * because no document marker can lead a person back to them. */
748
+ renderGroupedList(base) {
749
+ const state = this.state;
750
+ const maxW = Math.min(Math.max(20, this.cols - 4), 120);
751
+ const detached = state.comments.filter((comment) => comment.anchorStatus === 'detached');
752
+ const anchored = state.comments.filter((comment) => comment.anchorStatus !== 'detached');
753
+ const panel = [` ${YELLOW}Detached${RESET}`];
754
+ for (const comment of detached)
755
+ panel.push(this.commentListRow(comment, state.comments.indexOf(comment), maxW, true));
756
+ if (anchored.length > 0) {
757
+ panel.push(` ${DIM}Comments${RESET}`);
758
+ for (const comment of anchored)
759
+ panel.push(this.commentListRow(comment, state.comments.indexOf(comment), maxW, false));
760
+ }
761
+ panel.push('');
762
+ panel.push(` ${DIM}j/k${RESET} move ${DIM}enter/e${RESET} edit ${DIM}d${RESET} delete ${DIM}q/esc${RESET} close`);
763
+ const available = Math.max(0, this.rows - 3);
764
+ const visible = panel.slice(0, available);
765
+ const start = Math.max(3, this.rows - visible.length);
766
+ const out = [...base];
767
+ const centered = centerHorizontal(visible, this.cols, Math.min(this.cols, maxW + 2));
768
+ for (let i = 0; i < centered.length; i++)
769
+ out[start + i] = clipLine(centered[i] ?? '', this.cols);
770
+ return out.slice(0, this.rows);
771
+ }
772
+ /** The compose header states a lost anchor where the person is already
773
+ * looking, leaving the footer's compose keys intact. */
774
+ statedComposeAnchor(lines) {
775
+ const state = this.state;
776
+ if (state?.compose == null)
777
+ return lines;
778
+ const maxW = Math.min(Math.max(20, this.cols - 4), 120);
779
+ const bufferRows = renderInputBuffer(state.compose.buffer, state.compose.cursor, Math.max(10, maxW - 2)).length;
780
+ const header = this.rows - (bufferRows + 3);
781
+ if (header < 0 || header >= lines.length)
782
+ return lines;
783
+ const out = [...lines];
784
+ out[header] = clipLine(centerHorizontal([` ${YELLOW}Comment anchor removed by an edit — esc, then re-anchor:${RESET}`], this.cols, Math.min(this.cols, maxW + 2))[0] ?? '', this.cols);
785
+ return out;
786
+ }
787
+ commentListRow(comment, index, maxW, detached) {
788
+ const range = detached
789
+ ? comment.originalRange ?? { line: comment.line, endLine: comment.endLine }
790
+ : { line: comment.line, endLine: comment.endLine };
791
+ const loc = range.line === range.endLine ? `L${range.line}` : `L${range.line}-${range.endLine}`;
792
+ const cursor = index === this.state.listIndex ? `${CYAN}▸${RESET}` : ' ';
793
+ const snippet = detached ? `“${sanitize(comment.lineText).replace(/\s+/g, ' ').trim()}” · ` : '';
794
+ const text = `${snippet}${sanitize(comment.comment).replace(/\s+/g, ' ').trim()}`;
795
+ return ` ${cursor} ${DIM}[${loc}]${RESET} ${truncate(text, Math.max(10, maxW - 14))}`;
796
+ }
797
+ topVisibleSourceLine(state) {
798
+ const unit = state.rowUnits[state.scroll];
799
+ if (unit !== null && unit !== undefined)
800
+ return state.units[unit]?.start ?? 1;
801
+ for (let row = state.scroll; row < state.rowUnits.length; row++) {
802
+ const candidate = state.rowUnits[row];
803
+ if (candidate !== null && candidate !== undefined)
804
+ return state.units[candidate]?.start ?? 1;
805
+ }
806
+ return 1;
807
+ }
808
+ repaint() {
809
+ if (this.closed)
810
+ return;
811
+ this.dirty?.();
812
+ }
813
+ }
814
+ // ── Pure helpers ─────────────────────────────────────────────────────────────
815
+ function labelFor(cwd, absFile) {
816
+ const rel = relative(cwd, absFile);
817
+ return rel === '' || rel.startsWith('..') ? absFile : rel;
818
+ }
819
+ function nonDeleted(comments) {
820
+ return comments.filter((comment) => comment.status !== 'deleted');
821
+ }
822
+ /** Detached comments lead the list: they no longer point anywhere in the
823
+ * document, so they are only reachable there. */
824
+ function orderedForDisplay(comments) {
825
+ const detached = comments.filter((comment) => comment.anchorStatus === 'detached');
826
+ const anchored = comments.filter((comment) => comment.anchorStatus !== 'detached');
827
+ return [...detached, ...anchored];
828
+ }
829
+ function unitRange(state, index) {
830
+ const unit = state.units[index];
831
+ if (unit === undefined)
832
+ return { line: 1, endLine: 1 };
833
+ return { line: unit.start, endLine: unit.end };
834
+ }
835
+ /** `remap.ts` speaks 0-based lines with an exclusive end; comment anchors are
836
+ * 1-based and inclusive. This is the only place the two meet. */
837
+ function mapRange(map, range) {
838
+ const result = remapRange(map, { line: range.line - 1, endLine: range.endLine });
839
+ if (result.kind === 'detached')
840
+ return { range, detached: true };
841
+ return { range: { line: result.line + 1, endLine: Math.max(result.line + 1, result.endLine) }, detached: false };
842
+ }
843
+ function mapLine(map, line, oldLineCount) {
844
+ const old = line - 1;
845
+ const exact = map.get(old);
846
+ if (exact !== undefined)
847
+ return exact + 1;
848
+ // A deleted top row has no range to remap. Preserve locality by choosing the
849
+ // nearest surviving source row, preferring the following row on a tie.
850
+ for (let distance = 1; distance < oldLineCount; distance++) {
851
+ const after = map.get(old + distance);
852
+ if (after !== undefined)
853
+ return after + 1;
854
+ const before = map.get(old - distance);
855
+ if (before !== undefined)
856
+ return before + 1;
857
+ }
858
+ return 1;
859
+ }
860
+ function applyMappedRange(comment, mapped) {
861
+ if (mapped.detached) {
862
+ return {
863
+ ...comment,
864
+ anchorStatus: 'detached',
865
+ originalRange: comment.originalRange ?? { line: comment.line, endLine: comment.endLine },
866
+ };
867
+ }
868
+ if (comment.anchorStatus === 'detached')
869
+ return comment;
870
+ const shifted = mapped.range.line !== comment.line || mapped.range.endLine !== comment.endLine;
871
+ return {
872
+ ...comment,
873
+ line: mapped.range.line,
874
+ endLine: mapped.range.endLine,
875
+ anchorStatus: shifted ? 'shifted' : comment.anchorStatus,
876
+ };
877
+ }
878
+ function batchEntries(comments, map) {
879
+ return comments.map((comment) => {
880
+ if (comment.anchorStatus === 'detached')
881
+ return { id: comment.id, detached: true };
882
+ const mapped = mapRange(map, { line: comment.line, endLine: comment.endLine });
883
+ return mapped.detached
884
+ ? { id: comment.id, detached: true }
885
+ : { id: comment.id, anchor: mapped.range };
886
+ });
887
+ }
888
+ function markAtToken(lines, token, row) {
889
+ const index = row ?? lines.findIndex((line) => line.includes(token));
890
+ const line = lines[index];
891
+ if (line === undefined)
892
+ return lines;
893
+ const at = line.indexOf(token);
894
+ if (at === -1)
895
+ return lines;
896
+ const marked = [...lines];
897
+ marked[index] = `${line.slice(0, at)}${CURSOR_MARKER}${line.slice(at)}`;
898
+ return marked;
899
+ }
900
+ function describe(error) {
901
+ return error instanceof Error ? error.message : String(error);
902
+ }