@oh-my-pi/pi-tui 18.4.3 → 18.4.4

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 (447) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/types/apps/cleanse-board.d.ts +17 -0
  3. package/dist/types/apps/git/help.d.ts +16 -0
  4. package/dist/types/apps/git/sidebar.d.ts +18 -0
  5. package/dist/types/apps/live-visualizer.d.ts +10 -0
  6. package/dist/types/apps/ps-top.d.ts +15 -0
  7. package/dist/types/apps/standalone-picker.d.ts +3 -0
  8. package/dist/types/autocomplete.d.ts +8 -0
  9. package/dist/types/chat/advisor-message.d.ts +12 -2
  10. package/dist/types/chat/assistant-message.d.ts +20 -0
  11. package/dist/types/chat/bash-execution.d.ts +9 -0
  12. package/dist/types/chat/chat-transcript-builder.d.ts +6 -1
  13. package/dist/types/chat/collab-prompt-message.d.ts +4 -0
  14. package/dist/types/chat/compaction-summary-message.d.ts +15 -0
  15. package/dist/types/chat/eval-execution.d.ts +8 -0
  16. package/dist/types/chat/execution-shared.d.ts +58 -0
  17. package/dist/types/chat/late-diagnostics-message.d.ts +15 -1
  18. package/dist/types/chat/read-tool-group.d.ts +15 -0
  19. package/dist/types/chat/skill-message.d.ts +9 -0
  20. package/dist/types/chat/stripped-tool-calls-placeholder.d.ts +3 -0
  21. package/dist/types/chat/todo-reminder.d.ts +4 -1
  22. package/dist/types/chat/tool-execution.d.ts +25 -1
  23. package/dist/types/chat/transcript-actions.d.ts +37 -0
  24. package/dist/types/chat/transcript-entry.d.ts +3 -1
  25. package/dist/types/chat/user-message.d.ts +25 -0
  26. package/dist/types/chrome/collab-qrcode.d.ts +7 -0
  27. package/dist/types/chrome/context-thresholds.d.ts +11 -0
  28. package/dist/types/chrome/countdown-timer.d.ts +9 -3
  29. package/dist/types/chrome/diff.d.ts +12 -0
  30. package/dist/types/chrome/dynamic-border.d.ts +3 -0
  31. package/dist/types/chrome/format.d.ts +6 -0
  32. package/dist/types/chrome/message-divider.d.ts +13 -0
  33. package/dist/types/chrome/message-frame.d.ts +10 -0
  34. package/dist/types/chrome/message-notice.d.ts +23 -0
  35. package/dist/types/chrome/overlay-box.d.ts +11 -2
  36. package/dist/types/chrome/qrcode.d.ts +7 -0
  37. package/dist/types/chrome/segment-track.d.ts +7 -0
  38. package/dist/types/chrome/selector-helpers.d.ts +0 -6
  39. package/dist/types/chrome/status-notice.d.ts +14 -0
  40. package/dist/types/chrome/streaming-panel.d.ts +3 -0
  41. package/dist/types/chrome/tool-activity.d.ts +3 -0
  42. package/dist/types/chrome/transcript-container.d.ts +13 -0
  43. package/dist/types/chrome/transcript-status.d.ts +15 -3
  44. package/dist/types/components/box.d.ts +7 -0
  45. package/dist/types/components/disclosure.d.ts +9 -0
  46. package/dist/types/components/editor.d.ts +37 -0
  47. package/dist/types/components/form.d.ts +14 -0
  48. package/dist/types/components/image.d.ts +7 -0
  49. package/dist/types/components/input.d.ts +20 -1
  50. package/dist/types/components/key-value-list.d.ts +3 -0
  51. package/dist/types/components/layout/geometry.d.ts +16 -0
  52. package/dist/types/components/layout/row.d.ts +7 -0
  53. package/dist/types/components/layout/split-pane.d.ts +8 -0
  54. package/dist/types/components/layout/stack.d.ts +6 -0
  55. package/dist/types/components/loader.d.ts +48 -0
  56. package/dist/types/components/markdown.d.ts +8 -0
  57. package/dist/types/components/metric.d.ts +7 -0
  58. package/dist/types/components/progress-bar.d.ts +3 -0
  59. package/dist/types/components/scroll-view.d.ts +6 -0
  60. package/dist/types/components/section.d.ts +3 -0
  61. package/dist/types/components/select-list.d.ts +33 -0
  62. package/dist/types/components/settings-list.d.ts +68 -0
  63. package/dist/types/components/spacer.d.ts +4 -0
  64. package/dist/types/components/tab-bar.d.ts +9 -0
  65. package/dist/types/components/table.d.ts +7 -0
  66. package/dist/types/components/text.d.ts +6 -0
  67. package/dist/types/components/tree-view.d.ts +9 -0
  68. package/dist/types/components/truncated-text.d.ts +3 -0
  69. package/dist/types/components/wizard-step.d.ts +6 -0
  70. package/dist/types/hotkeys-markdown.d.ts +24 -0
  71. package/dist/types/index.d.ts +5 -0
  72. package/dist/types/latex-block.d.ts +7 -0
  73. package/dist/types/native/apply.d.ts +39 -0
  74. package/dist/types/native/backend.d.ts +94 -0
  75. package/dist/types/native/blobs.d.ts +29 -0
  76. package/dist/types/native/describe.d.ts +56 -0
  77. package/dist/types/native/encode.d.ts +50 -0
  78. package/dist/types/native/icons.d.ts +18 -0
  79. package/dist/types/native/memo.d.ts +27 -0
  80. package/dist/types/native/node.d.ts +107 -0
  81. package/dist/types/native/overlay.d.ts +64 -0
  82. package/dist/types/native/picker.d.ts +124 -0
  83. package/dist/types/native/reconcile.d.ts +41 -0
  84. package/dist/types/native/settle.d.ts +11 -0
  85. package/dist/types/native/spans.d.ts +21 -0
  86. package/dist/types/native/state.d.ts +6 -0
  87. package/dist/types/native/tone.d.ts +24 -0
  88. package/dist/types/overlays/advisor-config.d.ts +20 -0
  89. package/dist/types/overlays/agent-hub-renderer.d.ts +27 -11
  90. package/dist/types/overlays/agent-hub.d.ts +10 -0
  91. package/dist/types/overlays/agent-transcript-viewer.d.ts +7 -0
  92. package/dist/types/overlays/agents-hub.d.ts +8 -0
  93. package/dist/types/overlays/annotation-overlay.d.ts +3 -0
  94. package/dist/types/overlays/ask-dialog.d.ts +23 -0
  95. package/dist/types/overlays/bordered-loader.d.ts +3 -0
  96. package/dist/types/overlays/btw-history-panel.d.ts +9 -0
  97. package/dist/types/overlays/btw-panel.d.ts +3 -0
  98. package/dist/types/overlays/cleanse-panel.d.ts +5 -2
  99. package/dist/types/overlays/composer-shape-preview.d.ts +7 -1
  100. package/dist/types/overlays/copy-selector.d.ts +38 -0
  101. package/dist/types/overlays/error-banner.d.ts +9 -0
  102. package/dist/types/overlays/extensions/extension-list.d.ts +1 -0
  103. package/dist/types/overlays/history-search.d.ts +18 -0
  104. package/dist/types/overlays/hook-editor.d.ts +28 -1
  105. package/dist/types/overlays/hook-input.d.ts +3 -0
  106. package/dist/types/overlays/hook-selector.d.ts +10 -0
  107. package/dist/types/overlays/hub-frame.d.ts +18 -0
  108. package/dist/types/overlays/jobs-panel.d.ts +24 -0
  109. package/dist/types/overlays/login-dialog.d.ts +10 -0
  110. package/dist/types/overlays/logout-account-selector.d.ts +4 -0
  111. package/dist/types/overlays/mcp-add-wizard.d.ts +4 -0
  112. package/dist/types/overlays/model-browser.d.ts +69 -0
  113. package/dist/types/overlays/model-hub.d.ts +7 -0
  114. package/dist/types/overlays/model-picker.d.ts +7 -0
  115. package/dist/types/overlays/move-overlay.d.ts +3 -0
  116. package/dist/types/overlays/oauth-selector.d.ts +4 -0
  117. package/dist/types/overlays/omfg-panel.d.ts +3 -0
  118. package/dist/types/overlays/pause-screen.d.ts +12 -0
  119. package/dist/types/overlays/plan-review-overlay.d.ts +5 -0
  120. package/dist/types/overlays/plan-save-overlay.d.ts +10 -0
  121. package/dist/types/overlays/plugin-selector.d.ts +4 -0
  122. package/dist/types/overlays/plugin-settings.d.ts +30 -0
  123. package/dist/types/overlays/queue-mode-selector.d.ts +4 -0
  124. package/dist/types/overlays/reset-usage-selector.d.ts +8 -0
  125. package/dist/types/overlays/rewind-selector.d.ts +5 -0
  126. package/dist/types/overlays/session-info-overlay.d.ts +19 -1
  127. package/dist/types/overlays/session-selector.d.ts +64 -1
  128. package/dist/types/overlays/settings-defs.d.ts +14 -0
  129. package/dist/types/overlays/settings-selector.d.ts +21 -0
  130. package/dist/types/overlays/show-images-selector.d.ts +4 -0
  131. package/dist/types/overlays/snapcompact-shape-preview.d.ts +2 -0
  132. package/dist/types/overlays/stats-notice.d.ts +8 -0
  133. package/dist/types/overlays/theme-selector.d.ts +11 -0
  134. package/dist/types/overlays/thinking-selector.d.ts +4 -0
  135. package/dist/types/overlays/tree-selector.d.ts +60 -2
  136. package/dist/types/overlays/usage-dashboard.d.ts +26 -3
  137. package/dist/types/overlays/usage-row.d.ts +36 -2
  138. package/dist/types/prompt/attachment-chips.d.ts +8 -0
  139. package/dist/types/prompt/composer-attachments.d.ts +15 -3
  140. package/dist/types/prompt/composer.d.ts +18 -1
  141. package/dist/types/prompt/custom-editor.d.ts +48 -2
  142. package/dist/types/prompt/editor-top-gap.d.ts +2 -0
  143. package/dist/types/prompt/macos-spelling.d.ts +6 -0
  144. package/dist/types/prompt/magic-keywords.d.ts +9 -0
  145. package/dist/types/prompt/queued-messages.d.ts +23 -0
  146. package/dist/types/prompt/skill-tokens.d.ts +4 -2
  147. package/dist/types/prompt/welcome.d.ts +16 -0
  148. package/dist/types/render/code-cell.d.ts +10 -0
  149. package/dist/types/render/file-list.d.ts +8 -0
  150. package/dist/types/render/hyperlink.d.ts +14 -0
  151. package/dist/types/render/output-block.d.ts +43 -0
  152. package/dist/types/render/output-pane.d.ts +17 -0
  153. package/dist/types/render/tool-card.d.ts +8 -0
  154. package/dist/types/render/tree-list.d.ts +8 -0
  155. package/dist/types/render/width-aware-text.d.ts +7 -0
  156. package/dist/types/setup/scenes/outro.d.ts +7 -0
  157. package/dist/types/setup/scenes/sign-in.d.ts +7 -0
  158. package/dist/types/setup/scenes/splash.d.ts +8 -0
  159. package/dist/types/setup/scenes/types.d.ts +6 -1
  160. package/dist/types/setup/wizard-overlay.d.ts +9 -0
  161. package/dist/types/status-line/component.d.ts +49 -5
  162. package/dist/types/status-line/context-usage.d.ts +20 -0
  163. package/dist/types/status-line/footer.d.ts +8 -0
  164. package/dist/types/status-line/metrics.d.ts +1 -2
  165. package/dist/types/status-line/segments.d.ts +10 -1
  166. package/dist/types/status-line/types.d.ts +40 -1
  167. package/dist/types/terminal-capabilities.d.ts +1 -1
  168. package/dist/types/terminal.d.ts +27 -0
  169. package/dist/types/theme/defaults/index.d.ts +100 -0
  170. package/dist/types/theme/schema.d.ts +2 -0
  171. package/dist/types/theme/shimmer.d.ts +8 -0
  172. package/dist/types/theme/theme-class.d.ts +2 -0
  173. package/dist/types/theme/theme.d.ts +33 -1
  174. package/dist/types/tools/ask.d.ts +3 -0
  175. package/dist/types/tools/ast-edit.d.ts +10 -1
  176. package/dist/types/tools/ast-grep.d.ts +10 -1
  177. package/dist/types/tools/autoresearch.d.ts +21 -4
  178. package/dist/types/tools/bash.d.ts +30 -1
  179. package/dist/types/tools/cfg-render.d.ts +5 -1
  180. package/dist/types/tools/debug.d.ts +3 -1
  181. package/dist/types/tools/default-renderer.d.ts +6 -1
  182. package/dist/types/tools/edit.d.ts +14 -1
  183. package/dist/types/tools/eval.d.ts +12 -1
  184. package/dist/types/tools/fetch.d.ts +16 -1
  185. package/dist/types/tools/find.d.ts +15 -1
  186. package/dist/types/tools/github.d.ts +3 -0
  187. package/dist/types/tools/glob.d.ts +14 -1
  188. package/dist/types/tools/goal.d.ts +3 -1
  189. package/dist/types/tools/grep.d.ts +14 -1
  190. package/dist/types/tools/grouped-file-output.d.ts +58 -0
  191. package/dist/types/tools/json-tree.d.ts +20 -0
  192. package/dist/types/tools/lsp.d.ts +3 -10
  193. package/dist/types/tools/mcp.d.ts +16 -1
  194. package/dist/types/tools/memory.d.ts +26 -0
  195. package/dist/types/tools/native-view.d.ts +90 -0
  196. package/dist/types/tools/proc-render.d.ts +7 -1
  197. package/dist/types/tools/read.d.ts +23 -1
  198. package/dist/types/tools/renderer.d.ts +57 -0
  199. package/dist/types/tools/report-tool-issue.d.ts +3 -0
  200. package/dist/types/tools/resolve.d.ts +14 -1
  201. package/dist/types/tools/task.d.ts +3 -0
  202. package/dist/types/tools/think.d.ts +3 -0
  203. package/dist/types/tools/todo.d.ts +12 -0
  204. package/dist/types/tools/vibe.d.ts +3 -1
  205. package/dist/types/tools/wait.d.ts +3 -0
  206. package/dist/types/tools/web-search.d.ts +8 -0
  207. package/dist/types/tools/write.d.ts +14 -1
  208. package/dist/types/tools/xdev.d.ts +13 -0
  209. package/dist/types/tui.d.ts +49 -0
  210. package/package.json +9 -9
  211. package/src/apps/autoresearch-dashboard.ts +362 -4
  212. package/src/apps/cleanse-board.ts +219 -1
  213. package/src/apps/cleanse-picker.ts +9 -1
  214. package/src/apps/debug/log-viewer.ts +15 -15
  215. package/src/apps/debug/protocol-probe.ts +2 -20
  216. package/src/apps/git/avatar.ts +2 -22
  217. package/src/apps/git/git-tui.ts +406 -59
  218. package/src/apps/git/help.ts +130 -0
  219. package/src/apps/git/sidebar.ts +491 -0
  220. package/src/apps/live-visualizer.ts +65 -0
  221. package/src/apps/ps-top.ts +448 -17
  222. package/src/apps/session-picker.ts +1 -0
  223. package/src/apps/setup-model-picker.ts +5 -1
  224. package/src/apps/standalone-picker.ts +55 -1
  225. package/src/autocomplete.ts +44 -1
  226. package/src/chat/advisor-message.ts +72 -3
  227. package/src/chat/assistant-message.ts +346 -11
  228. package/src/chat/background-tan-message.ts +4 -4
  229. package/src/chat/bash-execution.ts +81 -0
  230. package/src/chat/cache-invalidation-marker.ts +8 -0
  231. package/src/chat/chat-transcript-builder.ts +37 -6
  232. package/src/chat/collab-prompt-message.ts +15 -3
  233. package/src/chat/compaction-summary-message.ts +74 -1
  234. package/src/chat/custom-message.ts +3 -1
  235. package/src/chat/eval-execution.ts +57 -0
  236. package/src/chat/execution-shared.ts +153 -1
  237. package/src/chat/hook-message.ts +1 -0
  238. package/src/chat/late-diagnostics-message.ts +85 -2
  239. package/src/chat/read-tool-group.ts +341 -1
  240. package/src/chat/served-model-marker.ts +8 -0
  241. package/src/chat/skill-message.ts +63 -0
  242. package/src/chat/stripped-tool-calls-placeholder.ts +18 -10
  243. package/src/chat/todo-reminder.ts +34 -4
  244. package/src/chat/tool-execution.ts +385 -10
  245. package/src/chat/transcript-actions.ts +38 -0
  246. package/src/chat/transcript-browser.ts +2 -9
  247. package/src/chat/transcript-entry.ts +10 -1
  248. package/src/chat/transcript-render-helpers.ts +16 -15
  249. package/src/chat/ttsr-notification.ts +38 -1
  250. package/src/chat/user-message.ts +167 -4
  251. package/src/chrome/collab-qrcode.ts +27 -2
  252. package/src/chrome/context-thresholds.ts +34 -0
  253. package/src/chrome/countdown-timer.ts +24 -1
  254. package/src/chrome/diff.ts +60 -1
  255. package/src/chrome/dynamic-border.ts +7 -0
  256. package/src/chrome/format.ts +12 -0
  257. package/src/chrome/message-divider.ts +33 -1
  258. package/src/chrome/message-frame.ts +53 -10
  259. package/src/chrome/message-notice.ts +71 -0
  260. package/src/chrome/overlay-box.ts +45 -2
  261. package/src/chrome/qrcode.ts +28 -0
  262. package/src/chrome/segment-track.ts +26 -0
  263. package/src/chrome/selector-helpers.ts +1 -17
  264. package/src/chrome/status-notice.ts +46 -0
  265. package/src/chrome/streaming-panel.ts +25 -1
  266. package/src/chrome/tool-activity.ts +20 -0
  267. package/src/chrome/transcript-container.ts +37 -0
  268. package/src/chrome/transcript-status.ts +40 -11
  269. package/src/components/box.ts +51 -0
  270. package/src/components/disclosure.ts +33 -0
  271. package/src/components/editor.ts +216 -27
  272. package/src/components/form.ts +93 -9
  273. package/src/components/image.ts +30 -0
  274. package/src/components/input.ts +77 -26
  275. package/src/components/key-value-list.ts +18 -0
  276. package/src/components/layout/geometry.ts +44 -0
  277. package/src/components/layout/row.ts +30 -0
  278. package/src/components/layout/split-pane.ts +65 -0
  279. package/src/components/layout/stack.ts +27 -0
  280. package/src/components/loader.ts +183 -1
  281. package/src/components/markdown.ts +21 -0
  282. package/src/components/metric.ts +39 -0
  283. package/src/components/progress-bar.ts +28 -0
  284. package/src/components/scroll-view.ts +23 -0
  285. package/src/components/section.ts +16 -0
  286. package/src/components/select-list.ts +174 -25
  287. package/src/components/settings-list.ts +264 -7
  288. package/src/components/spacer.ts +17 -0
  289. package/src/components/tab-bar.ts +42 -0
  290. package/src/components/table.ts +34 -0
  291. package/src/components/text.ts +23 -0
  292. package/src/components/tree-view.ts +65 -0
  293. package/src/components/truncated-text.ts +14 -0
  294. package/src/components/wizard-step.ts +18 -9
  295. package/src/debug-server.ts +14 -7
  296. package/src/hotkeys-markdown.ts +251 -69
  297. package/src/index.ts +6 -0
  298. package/src/latex-block.ts +11 -0
  299. package/src/native/apply.ts +275 -0
  300. package/src/native/backend.ts +682 -0
  301. package/src/native/blobs.ts +70 -0
  302. package/src/native/describe.ts +124 -0
  303. package/src/native/encode.ts +233 -0
  304. package/src/native/icons.ts +184 -0
  305. package/src/native/memo.ts +64 -0
  306. package/src/native/node.ts +112 -0
  307. package/src/native/overlay.ts +131 -0
  308. package/src/native/picker.ts +333 -0
  309. package/src/native/reconcile.ts +1063 -0
  310. package/src/native/settle.ts +23 -0
  311. package/src/native/spans.ts +308 -0
  312. package/src/native/state.ts +28 -0
  313. package/src/native/tone.ts +79 -0
  314. package/src/overlays/advisor-config.ts +420 -0
  315. package/src/overlays/agent-hub-renderer.ts +155 -18
  316. package/src/overlays/agent-hub.ts +1037 -44
  317. package/src/overlays/agent-transcript-viewer.ts +129 -0
  318. package/src/overlays/agents-hub.ts +688 -53
  319. package/src/overlays/annotation-overlay.ts +391 -52
  320. package/src/overlays/ask-dialog.ts +435 -24
  321. package/src/overlays/bordered-loader.ts +17 -0
  322. package/src/overlays/btw-history-panel.ts +182 -0
  323. package/src/overlays/btw-panel.ts +68 -2
  324. package/src/overlays/cleanse-panel.ts +105 -15
  325. package/src/overlays/codex-reset-fireworks.ts +65 -5
  326. package/src/overlays/composer-shape-preview.ts +26 -1
  327. package/src/overlays/copy-selector.ts +545 -23
  328. package/src/overlays/error-banner.ts +44 -0
  329. package/src/overlays/extensions/extension-list.ts +23 -17
  330. package/src/overlays/history-search.ts +175 -33
  331. package/src/overlays/hook-editor.ts +113 -6
  332. package/src/overlays/hook-input.ts +22 -1
  333. package/src/overlays/hook-selector.ts +311 -22
  334. package/src/overlays/hub-frame.ts +80 -0
  335. package/src/overlays/jobs-panel.ts +112 -0
  336. package/src/overlays/login-dialog.ts +183 -1
  337. package/src/overlays/logout-account-selector.ts +102 -4
  338. package/src/overlays/mcp-add-wizard.ts +232 -53
  339. package/src/overlays/model-browser.ts +681 -22
  340. package/src/overlays/model-hub.ts +1402 -78
  341. package/src/overlays/model-picker.ts +175 -1
  342. package/src/overlays/move-overlay.ts +45 -0
  343. package/src/overlays/oauth-selector.ts +227 -23
  344. package/src/overlays/omfg-panel.ts +86 -2
  345. package/src/overlays/pause-screen.ts +55 -4
  346. package/src/overlays/plan-review-overlay.ts +352 -0
  347. package/src/overlays/plan-save-overlay.ts +45 -0
  348. package/src/overlays/plugin-selector.ts +21 -1
  349. package/src/overlays/plugin-settings.ts +195 -5
  350. package/src/overlays/queue-mode-selector.ts +19 -1
  351. package/src/overlays/reset-usage-selector.ts +110 -40
  352. package/src/overlays/rewind-selector.ts +458 -44
  353. package/src/overlays/session-account-selector.ts +1 -1
  354. package/src/overlays/session-info-overlay.ts +137 -1
  355. package/src/overlays/session-selector.ts +807 -46
  356. package/src/overlays/settings-defs.ts +41 -0
  357. package/src/overlays/settings-selector.ts +583 -23
  358. package/src/overlays/show-images-selector.ts +18 -1
  359. package/src/overlays/snapcompact-shape-preview.ts +48 -3
  360. package/src/overlays/stats-notice.ts +31 -0
  361. package/src/overlays/theme-selector.ts +31 -1
  362. package/src/overlays/thinking-selector.ts +22 -1
  363. package/src/overlays/tree-selector.ts +570 -110
  364. package/src/overlays/usage-dashboard.ts +587 -12
  365. package/src/overlays/usage-row.ts +161 -25
  366. package/src/prompt/attachment-chips.ts +60 -0
  367. package/src/prompt/composer-attachments.ts +31 -9
  368. package/src/prompt/composer.ts +55 -2
  369. package/src/prompt/custom-editor.ts +302 -4
  370. package/src/prompt/editor-top-gap.ts +8 -0
  371. package/src/prompt/macos-spelling.ts +24 -9
  372. package/src/prompt/magic-keywords.ts +22 -1
  373. package/src/prompt/model-mention-autocomplete.ts +1 -0
  374. package/src/prompt/queued-messages.ts +101 -0
  375. package/src/prompt/skill-tokens.ts +5 -3
  376. package/src/prompt/welcome.ts +213 -6
  377. package/src/render/code-cell.ts +103 -2
  378. package/src/render/file-list.ts +29 -0
  379. package/src/render/hyperlink.ts +32 -0
  380. package/src/render/output-block.ts +110 -0
  381. package/src/render/output-pane.ts +37 -0
  382. package/src/render/tool-card.ts +137 -0
  383. package/src/render/tree-list.ts +39 -0
  384. package/src/render/width-aware-text.ts +20 -0
  385. package/src/setup/scenes/composer.ts +29 -0
  386. package/src/setup/scenes/glyph.ts +15 -0
  387. package/src/setup/scenes/model.ts +36 -8
  388. package/src/setup/scenes/outro.ts +28 -1
  389. package/src/setup/scenes/sign-in.ts +122 -13
  390. package/src/setup/scenes/splash.ts +28 -1
  391. package/src/setup/scenes/theme.ts +88 -3
  392. package/src/setup/scenes/types.ts +7 -1
  393. package/src/setup/startup-splash.ts +46 -9
  394. package/src/setup/wizard-overlay.ts +140 -23
  395. package/src/status-line/component.ts +398 -16
  396. package/src/status-line/context-usage.ts +279 -50
  397. package/src/status-line/footer.ts +94 -1
  398. package/src/status-line/metrics.ts +3 -6
  399. package/src/status-line/segments.ts +463 -97
  400. package/src/status-line/separators.ts +3 -16
  401. package/src/status-line/types.ts +43 -1
  402. package/src/terminal-capabilities.ts +7 -0
  403. package/src/terminal.ts +130 -2
  404. package/src/theme/defaults/dark-neon-noir.json +100 -0
  405. package/src/theme/defaults/index.ts +2 -0
  406. package/src/theme/defaults/light-prism.json +5 -5
  407. package/src/theme/defaults/onyx.json +62 -62
  408. package/src/theme/schema.ts +15 -0
  409. package/src/theme/shimmer.ts +33 -0
  410. package/src/theme/theme-class.ts +24 -19
  411. package/src/theme/theme.ts +111 -9
  412. package/src/tools/ask.ts +157 -1
  413. package/src/tools/ast-edit.ts +174 -1
  414. package/src/tools/ast-grep.ts +82 -2
  415. package/src/tools/autoresearch.ts +149 -1
  416. package/src/tools/bash.ts +180 -49
  417. package/src/tools/cfg-render.ts +66 -1
  418. package/src/tools/debug.ts +78 -1
  419. package/src/tools/default-renderer.ts +35 -1
  420. package/src/tools/edit.ts +247 -2
  421. package/src/tools/eval.ts +257 -1
  422. package/src/tools/fetch.ts +102 -3
  423. package/src/tools/find.ts +127 -1
  424. package/src/tools/github.ts +119 -10
  425. package/src/tools/glob.ts +116 -1
  426. package/src/tools/goal.ts +56 -1
  427. package/src/tools/grep.ts +83 -2
  428. package/src/tools/grouped-file-output.ts +205 -0
  429. package/src/tools/json-tree.ts +116 -1
  430. package/src/tools/lsp.ts +318 -1
  431. package/src/tools/mcp.ts +80 -1
  432. package/src/tools/memory.ts +93 -11
  433. package/src/tools/native-view.ts +292 -0
  434. package/src/tools/proc-render.ts +221 -1
  435. package/src/tools/read.ts +199 -4
  436. package/src/tools/renderer.ts +61 -0
  437. package/src/tools/report-tool-issue.ts +7 -1
  438. package/src/tools/resolve.ts +63 -1
  439. package/src/tools/task.ts +406 -4
  440. package/src/tools/think.ts +19 -1
  441. package/src/tools/todo.ts +89 -0
  442. package/src/tools/vibe.ts +230 -2
  443. package/src/tools/wait.ts +181 -1
  444. package/src/tools/web-search.ts +113 -1
  445. package/src/tools/write.ts +144 -5
  446. package/src/tools/xdev.ts +64 -1
  447. package/src/tui.ts +294 -3
@@ -0,0 +1,1063 @@
1
+ /**
2
+ * Reconciler: turns successive component descriptions into minimal TSP ops.
3
+ *
4
+ * Ids. Every component instance gets a stable base-36 id (its root node's
5
+ * wire id); a node nested inside it gets `<base>.<keypath>`, where the
6
+ * keypath joins each level's `key` (or child index) with `/`. Ids therefore
7
+ * derive from the description itself, so the reconciler keeps no per-node
8
+ * table: its whole memory is, per mounted component, the node object the
9
+ * component last returned (which the component itself retains as its memo),
10
+ * the component boundaries and hoisted overlays found inside it, and
11
+ * rows-fallback bookkeeping.
12
+ *
13
+ * Diffing. Reference-equal nodes are skipped without walking; component
14
+ * boundaries inside them are still visited through the recorded boundary
15
+ * list. Children are matched by id; keyed reorders become `move`s placed
16
+ * around the longest run of children that kept their order, removals are
17
+ * deferred to the end of the frame so a component moved elsewhere in the same
18
+ * frame keeps its terminal-side state. Primary text that extends the previous
19
+ * text becomes `text append`.
20
+ *
21
+ * Settling. A settled component (see `settle.ts`) drops its retained node
22
+ * after the `settle` hint; only a weak identity, its root kind, root prop
23
+ * names and child ids survive. A later change is sent as targeted ops by id
24
+ * (`set` of the root props, `del` + `add` of its children), never by diffing
25
+ * against state omp no longer keeps.
26
+ *
27
+ * `overlay` nodes described anywhere but directly under `layer` are hoisted
28
+ * into `layer`; their anchor keypaths are rewritten to wire ids.
29
+ */
30
+ import * as logger from "@oh-my-pi/pi-utils/logger";
31
+ import { TSP_TEXT_KINDS, type TspKind, type TspNode, type TspOp } from "@oh-my-pi/pi-wire";
32
+ import { type Component, Container, CURSOR_MARKER } from "../tui";
33
+ import { normalizeIconProps } from "./icons";
34
+ import type { DescribeContext, NativeChild, NativeNode } from "./node";
35
+ import { isNativeSettled } from "./settle";
36
+
37
+ /** The regions a frame fills. */
38
+ export interface NativeRegions {
39
+ readonly main: readonly NativeChild[];
40
+ readonly dock: readonly NativeChild[];
41
+ readonly layer: readonly NativeChild[];
42
+ }
43
+
44
+ /** Where a wire id came from, for event routing. */
45
+ export interface NativeTarget {
46
+ readonly component: Component;
47
+ /** Keypath inside the component ("" for its root node). */
48
+ readonly keypath: string;
49
+ }
50
+
51
+ const kNativeId = Symbol("native.id");
52
+
53
+ interface IdTagged {
54
+ [kNativeId]?: string;
55
+ }
56
+
57
+ const REGION_IDS = ["main", "dock", "layer"] as const;
58
+ type RegionId = (typeof REGION_IDS)[number];
59
+ const RESERVED_IDS: ReadonlySet<string> = new Set(REGION_IDS);
60
+ const TEXT_KINDS: ReadonlySet<string> = new Set(TSP_TEXT_KINDS);
61
+ let nextComponentId = 0;
62
+
63
+ /** Stable base-36 wire id of a component instance (its root node's id). */
64
+ export function nativeComponentId(component: Component): string {
65
+ const tagged = component as IdTagged;
66
+ let id = tagged[kNativeId];
67
+ if (id === undefined) {
68
+ do {
69
+ id = (nextComponentId++).toString(36);
70
+ } while (RESERVED_IDS.has(id));
71
+ tagged[kNativeId] = id;
72
+ }
73
+ return id;
74
+ }
75
+
76
+ function isComponent(child: NativeChild): child is Component {
77
+ return typeof (child as Component).render === "function";
78
+ }
79
+
80
+ const KEY_ESCAPES: Readonly<Record<string, string>> = { "%": "%25", "/": "%2F", "^": "%5E" };
81
+ const KEY_UNESCAPES: Readonly<Record<string, string>> = { "%25": "%", "%2F": "/", "%5E": "^" };
82
+
83
+ function escapeKey(key: string): string {
84
+ return key.replace(/[%/^]/g, c => KEY_ESCAPES[c]!);
85
+ }
86
+
87
+ /** A keypath segment back to the key it was built from (hoisted overlays carry a `^` marker). */
88
+ function unescapeKey(segment: string): string {
89
+ return (segment.startsWith("^") ? segment.slice(1) : segment).replace(/%2F|%25|%5E/g, c => KEY_UNESCAPES[c]!);
90
+ }
91
+
92
+ function nodeId(base: string, keypath: string): string {
93
+ return keypath === "" ? base : `${base}.${keypath}`;
94
+ }
95
+
96
+ function sameValue(a: unknown, b: unknown): boolean {
97
+ if (a === b) return true;
98
+ return typeof a === "object" && typeof b === "object" && a !== null && b !== null && Bun.deepEquals(a, b);
99
+ }
100
+
101
+ function asProps(node: NativeNode): Readonly<Record<string, unknown>> | undefined {
102
+ return node.p as Readonly<Record<string, unknown>> | undefined;
103
+ }
104
+
105
+ /** Longest increasing subsequence over non-negative `sources`; marks the members. */
106
+ function stableMask(sources: readonly number[]): boolean[] {
107
+ const n = sources.length;
108
+ const mask = Array.from({ length: n }, () => false);
109
+ const tails: number[] = [];
110
+ const prev = Array.from({ length: n }, () => -1);
111
+ for (let i = 0; i < n; i++) {
112
+ const value = sources[i]!;
113
+ if (value < 0) continue;
114
+ let lo = 0;
115
+ let hi = tails.length;
116
+ while (lo < hi) {
117
+ const mid = (lo + hi) >> 1;
118
+ if (sources[tails[mid]!]! < value) lo = mid + 1;
119
+ else hi = mid;
120
+ }
121
+ if (lo > 0) prev[i] = tails[lo - 1]!;
122
+ tails[lo] = i;
123
+ }
124
+ for (let at = tails.length > 0 ? tails[tails.length - 1]! : -1; at >= 0; at = prev[at]!) mask[at] = true;
125
+ return mask;
126
+ }
127
+
128
+ interface Owner {
129
+ readonly base: string;
130
+ readonly state: CompState | null;
131
+ }
132
+
133
+ interface NodeEntry {
134
+ readonly id: string;
135
+ readonly node: NativeNode;
136
+ readonly owner: Owner;
137
+ readonly keypath: string;
138
+ /** Whether overlay descendants of this node are hoisted into `layer`. */
139
+ readonly hoist: boolean;
140
+ readonly comp?: undefined;
141
+ }
142
+
143
+ interface CompEntry {
144
+ readonly id: string;
145
+ readonly comp: Component;
146
+ }
147
+
148
+ type Entry = NodeEntry | CompEntry;
149
+
150
+ /**
151
+ * What a component's walk met, in walk order: nested component boundaries
152
+ * (with their document position) and hoisted overlays. Replaying it for an
153
+ * unchanged component reproduces the walk's visits and overlay order.
154
+ */
155
+ type Inner =
156
+ | {
157
+ readonly comp: Component;
158
+ readonly parent: string;
159
+ readonly before: string | null;
160
+ readonly portal?: undefined;
161
+ }
162
+ | { readonly portal: NodeEntry; readonly comp?: undefined };
163
+
164
+ interface CompState {
165
+ readonly comp: Component;
166
+ readonly id: string;
167
+ readonly owner: Owner;
168
+ /** Last description while unsettled; null once settled. */
169
+ node: NativeNode | null;
170
+ /** Settled: identity of the last description (node, or rows array). */
171
+ weak: WeakRef<object> | null;
172
+ kind: TspKind;
173
+ propKeys: readonly string[];
174
+ childIds: readonly string[];
175
+ inner: Inner[];
176
+ rowsLines: readonly string[] | null;
177
+ rowsCols: number;
178
+ rowsNode: NativeNode | null;
179
+ settled: boolean;
180
+ settleSent: boolean;
181
+ frame: number;
182
+ /** The component whose node currently contains this one in the document (null: a region or the root). */
183
+ container: CompState | null;
184
+ /** Moved under the surface root this frame to survive a delete or a parent/child swap. */
185
+ parked: boolean;
186
+ }
187
+
188
+ interface Walk {
189
+ readonly owner: Owner;
190
+ /** Records the owner's boundaries and hoisted overlays (null: don't record). */
191
+ readonly inner: Inner[] | null;
192
+ readonly settled: boolean;
193
+ readonly hoist: boolean;
194
+ }
195
+
196
+ interface PendingMove {
197
+ readonly id: string;
198
+ readonly parent: string;
199
+ readonly before: string | null;
200
+ }
201
+
202
+ const EMPTY_ENTRIES: readonly Entry[] = [];
203
+
204
+ /** One surface's reconciler. */
205
+ export class Reconciler {
206
+ readonly surface: string;
207
+ #cx: DescribeContext | undefined;
208
+ #states = new Map<Component, CompState>();
209
+ #byId = new Map<string, CompState>();
210
+ #frame = 0;
211
+ #ops: TspOp[] = [];
212
+ #dels: Entry[] = [];
213
+ #settles: string[] = [];
214
+ /** Selected list items sent this frame, revealed once the frame's adds have landed. */
215
+ #reveals: string[] = [];
216
+ /** Wire ids `list.selected` keys resolved to (an unresolved key names no node to reveal). */
217
+ #selectedIds = new Set<string>();
218
+ #moves: PendingMove[] = [];
219
+ /** Ids of components updated this frame that wait for an enclosing `add` to move in. */
220
+ #pendingMoves = new Set<string>();
221
+ /** Components parked under the root this frame. */
222
+ #parked: CompState[] = [];
223
+ #framePortals: NodeEntry[] = [];
224
+ #prevPortals: NodeEntry[] = [];
225
+ #regions: Record<RegionId, readonly Entry[]> | null = null;
226
+ #rows = 0;
227
+ #gone = new Set<string>();
228
+ /** Live-only regions to re-add after the surface was closed and adopted. */
229
+ #readd: readonly RegionId[] = [];
230
+ #regionOwners: Record<RegionId, Owner> = {
231
+ main: { base: "main", state: null },
232
+ dock: { base: "dock", state: null },
233
+ layer: { base: "layer", state: null },
234
+ };
235
+
236
+ constructor(surface: string) {
237
+ this.surface = surface;
238
+ }
239
+
240
+ /** `rows` fallback nodes in the last reconciled frame. */
241
+ get fallbackCount(): number {
242
+ return this.#rows;
243
+ }
244
+
245
+ /** Diff the regions against the previous frame; the first call adds everything. */
246
+ reconcile(regions: NativeRegions, cx: DescribeContext): TspOp[] {
247
+ this.#cx = cx;
248
+ this.#frame++;
249
+ this.#ops = [];
250
+ this.#dels = [];
251
+ this.#settles = [];
252
+ this.#reveals = [];
253
+ this.#selectedIds.clear();
254
+ this.#framePortals = [];
255
+ this.#rows = 0;
256
+ let prev = this.#regions;
257
+ if (prev === null) {
258
+ for (const region of REGION_IDS) this.#ops.push(["add", region, this.surface, null, { id: region, k: "col" }]);
259
+ prev = { main: EMPTY_ENTRIES, dock: EMPTY_ENTRIES, layer: EMPTY_ENTRIES };
260
+ }
261
+ for (const region of this.#readd) this.#ops.push(["add", region, this.surface, null, { id: region, k: "col" }]);
262
+ this.#readd = [];
263
+ const next = {} as Record<RegionId, readonly Entry[]>;
264
+ for (const region of REGION_IDS) {
265
+ const hoist = region !== "layer";
266
+ const walk: Walk = { owner: this.#regionOwners[region], inner: null, settled: false, hoist };
267
+ const entries = this.#entries(regions[region], "", walk.owner, hoist, walk);
268
+ this.#diffChildren(region, prev[region], entries, null, walk);
269
+ next[region] = entries;
270
+ }
271
+ // Hoisted overlays sit before the overlay stack in `layer`.
272
+ const layerFirst = next.layer.length > 0 ? next.layer[0]!.id : null;
273
+ const portalWalk: Walk = { owner: this.#regionOwners.layer, inner: null, settled: false, hoist: false };
274
+ const portals = this.#framePortals.slice();
275
+ this.#diffChildren("layer", this.#prevPortals, portals, layerFirst, portalWalk);
276
+ if (this.#framePortals.length > portals.length) {
277
+ // Components inside a hoisted overlay described overlays of their own;
278
+ // only one level of hoisting exists, so those stay unsent.
279
+ logger.debug("TSP: overlay nested in a hoisted overlay dropped", {
280
+ ids: this.#framePortals.slice(portals.length).map(portal => portal.id),
281
+ });
282
+ }
283
+ this.#prevPortals = portals;
284
+ this.#regions = next;
285
+ for (const entry of this.#dels) this.#deleteEntry(entry);
286
+ // Parked to survive a parent/child swap but absent from the new description.
287
+ for (const state of this.#parked) {
288
+ if (!state.parked) continue;
289
+ state.parked = false;
290
+ this.#dropState(state);
291
+ this.#ops.push(["del", state.id]);
292
+ }
293
+ this.#parked = [];
294
+ for (const id of this.#settles) this.#ops.push(["settle", id]);
295
+ for (const id of this.#reveals) this.#ops.push(["reveal", id, "nearest"]);
296
+ const ops = this.#ops;
297
+ this.#ops = [];
298
+ return this.#gone.size === 0 ? ops : ops.filter(op => !this.#touchesGone(op));
299
+ }
300
+
301
+ /**
302
+ * The surface was closed (`keep:true`) and is being adopted: the terminal
303
+ * dropped `dock`, `layer` and focus but kept `main`. The next frame re-adds
304
+ * the live regions from scratch and keeps diffing `main`.
305
+ */
306
+ detachLive(): void {
307
+ const regions = this.#regions;
308
+ if (!regions) return;
309
+ // A fresh frame number so every state below counts as unvisited.
310
+ this.#frame++;
311
+ for (const region of ["dock", "layer"] as const) {
312
+ for (const entry of regions[region]) {
313
+ if (entry.comp === undefined) this.#dropSubtree(entry);
314
+ else {
315
+ const state = this.#states.get(entry.comp);
316
+ if (state) this.#dropState(state);
317
+ }
318
+ }
319
+ }
320
+ for (const portal of this.#prevPortals) this.#dropSubtree(portal);
321
+ this.#prevPortals = [];
322
+ this.#regions = { main: regions.main, dock: EMPTY_ENTRIES, layer: EMPTY_ENTRIES };
323
+ this.#readd = ["dock", "layer"];
324
+ }
325
+
326
+ /** The component and keypath that described wire node `id`. */
327
+ target(id: string): NativeTarget | null {
328
+ const dot = id.indexOf(".");
329
+ const state = this.#byId.get(dot === -1 ? id : id.slice(0, dot));
330
+ if (!state) return null;
331
+ const keypath =
332
+ dot === -1
333
+ ? ""
334
+ : id
335
+ .slice(dot + 1)
336
+ .split("/")
337
+ .map(unescapeKey)
338
+ .join("/");
339
+ return { component: state.comp, keypath };
340
+ }
341
+
342
+ /** The key a list item node was described with (its key, else its child index). */
343
+ itemKey(id: string): string {
344
+ const dot = id.indexOf(".");
345
+ if (dot === -1) return this.#byId.get(id)?.node?.key ?? id;
346
+ const slash = id.lastIndexOf("/");
347
+ return unescapeKey(id.slice(Math.max(dot, slash) + 1));
348
+ }
349
+
350
+ /** Wire id of the first `editor`/`input` `component` describes (for `focus`), or null. */
351
+ focusTarget(component: Component): string | null {
352
+ const state = this.#states.get(component);
353
+ return state ? this.#findEditor(state, 0) : null;
354
+ }
355
+
356
+ /** Stop referencing ids the terminal dropped (`gone`). */
357
+ forget(ids: readonly string[]): void {
358
+ for (const id of ids) this.#gone.add(id);
359
+ }
360
+
361
+ #touchesGone(op: TspOp): boolean {
362
+ const gone = this.#gone;
363
+ switch (op[0]) {
364
+ case "add":
365
+ case "move":
366
+ return gone.has(op[1]) || gone.has(op[2]) || (op[3] !== null && gone.has(op[3]));
367
+ case "focus":
368
+ return op[1] !== null && gone.has(op[1]);
369
+ case "suspend":
370
+ case "resume":
371
+ return false;
372
+ default:
373
+ return gone.has(op[1]);
374
+ }
375
+ }
376
+
377
+ #findEditor(state: CompState, depth: number): string | null {
378
+ if (depth > 64) return null;
379
+ const node = state.node ?? (state.weak?.deref() as NativeNode | undefined);
380
+ if (!node || typeof node.k !== "string") return null;
381
+ return this.#findEditorIn(node, state.id, "", state.owner, depth);
382
+ }
383
+
384
+ #findEditorIn(node: NativeNode, id: string, keypath: string, owner: Owner, depth: number): string | null {
385
+ if (node.k === "editor" || node.k === "input") return id;
386
+ for (const entry of this.#entries(node.c, keypath, owner, true, null)) {
387
+ const found =
388
+ entry.comp === undefined
389
+ ? this.#findEditorIn(entry.node, entry.id, entry.keypath, owner, depth)
390
+ : this.#findEditorOf(entry.comp, depth + 1);
391
+ if (found !== null) return found;
392
+ }
393
+ return null;
394
+ }
395
+
396
+ #findEditorOf(component: Component, depth: number): string | null {
397
+ const state = this.#states.get(component);
398
+ return state ? this.#findEditor(state, depth) : null;
399
+ }
400
+
401
+ // ── Entries ──────────────────────────────────────────────────────────
402
+
403
+ /**
404
+ * Child slots of a node with their wire ids. Overlays are split off into the
405
+ * hoisted list when `hoist` is set (and a walk collects them).
406
+ */
407
+ #entries(
408
+ children: readonly NativeChild[] | undefined,
409
+ keypath: string,
410
+ owner: Owner,
411
+ hoist: boolean,
412
+ walk: Walk | null,
413
+ ): readonly Entry[] {
414
+ if (!children || children.length === 0) return EMPTY_ENTRIES;
415
+ const out: Entry[] = [];
416
+ let used: Set<string> | undefined;
417
+ for (let i = 0; i < children.length; i++) {
418
+ const child = children[i]!;
419
+ if (isComponent(child)) {
420
+ out.push({ id: nativeComponentId(child), comp: child });
421
+ continue;
422
+ }
423
+ const segment = child.key === undefined ? String(i) : escapeKey(child.key);
424
+ let path = keypath === "" ? segment : `${keypath}/${segment}`;
425
+ if (children.length > 1) {
426
+ // A key may repeat, or equal an unkeyed sibling's index.
427
+ used ??= new Set();
428
+ if (used.has(path)) {
429
+ let n = 2;
430
+ while (used.has(`${path}~${n}`)) n++;
431
+ path = `${path}~${n}`;
432
+ }
433
+ used.add(path);
434
+ }
435
+ const entry: NodeEntry = {
436
+ id: nodeId(owner.base, path),
437
+ node: child,
438
+ owner,
439
+ keypath: path,
440
+ hoist: hoist && child.k !== "overlay" && child.k !== "picker",
441
+ };
442
+ // A picker is its own sheet: hoisted like an overlay.
443
+ if (hoist && (child.k === "overlay" || child.k === "picker")) {
444
+ if (walk) {
445
+ // Its own id namespace (`^` marks the hoisted segment): the node now
446
+ // lives under `layer`, so a same-keypath node in place must not collide.
447
+ const portalPath = keypath === "" ? `^${path}` : `${keypath}/^${path.slice(keypath.length + 1)}`;
448
+ const portal: NodeEntry = {
449
+ ...entry,
450
+ id: nodeId(owner.base, portalPath),
451
+ keypath: portalPath,
452
+ hoist: false,
453
+ };
454
+ walk.inner?.push({ portal });
455
+ this.#framePortals.push(portal);
456
+ }
457
+ continue;
458
+ }
459
+ out.push(entry);
460
+ }
461
+ return out;
462
+ }
463
+
464
+ // ── Children ─────────────────────────────────────────────────────────
465
+
466
+ #diffChildren(
467
+ parent: string,
468
+ oldEntries: readonly Entry[],
469
+ newEntries: readonly Entry[],
470
+ tailBefore: string | null,
471
+ walk: Walk,
472
+ ): void {
473
+ const oldIndex = new Map<string, number>();
474
+ if (oldEntries.length > 0) {
475
+ const keep = new Set<string>();
476
+ for (const entry of newEntries) keep.add(entry.id);
477
+ for (let i = 0; i < oldEntries.length; i++) {
478
+ const entry = oldEntries[i]!;
479
+ oldIndex.set(entry.id, i);
480
+ if (!keep.has(entry.id)) this.#dels.push(entry);
481
+ }
482
+ }
483
+ const count = newEntries.length;
484
+ const sources = newEntries.map(entry => oldIndex.get(entry.id) ?? -1);
485
+ const stable = stableMask(sources);
486
+ // Children that kept their relative order stay; every other child is
487
+ // placed before the nearest following one that stays. Processing left to
488
+ // right keeps walk order (and so hoisted-overlay order) canonical.
489
+ const anchors: (string | null)[] = [];
490
+ let anchor = tailBefore;
491
+ for (let i = count - 1; i >= 0; i--) {
492
+ anchors[i] = anchor;
493
+ if (stable[i]) anchor = newEntries[i]!.id;
494
+ }
495
+ for (let i = 0; i < count; i++) {
496
+ const entry = newEntries[i]!;
497
+ const source = sources[i]!;
498
+ const before = anchors[i]!;
499
+ const next = i + 1 < count ? newEntries[i + 1]!.id : tailBefore;
500
+ if (source < 0) {
501
+ this.#place(entry, parent, before, next, walk);
502
+ continue;
503
+ }
504
+ if (!stable[i]) {
505
+ const state = entry.comp === undefined ? undefined : this.#states.get(entry.comp);
506
+ if (state) this.#move(state, parent, before);
507
+ else this.#ops.push(["move", entry.id, parent, before]);
508
+ }
509
+ if (entry.comp !== undefined) this.#visitChild(entry.comp, parent, before, next, walk);
510
+ else this.#diffNode(oldEntries[source] as NodeEntry, entry, parent, before, walk);
511
+ }
512
+ }
513
+
514
+ /**
515
+ * Put a child that wasn't under `parent` last frame before `before`.
516
+ * `next` is its following sibling, recorded for later re-adds.
517
+ */
518
+ #place(entry: Entry, parent: string, before: string | null, next: string | null, walk: Walk): void {
519
+ if (entry.comp === undefined) {
520
+ this.#ops.push(["add", entry.id, parent, before, this.#materialize(entry, this.#walkFor(entry, walk))]);
521
+ this.#flushMoves();
522
+ return;
523
+ }
524
+ const state = this.#states.get(entry.comp);
525
+ if (state?.frame === this.#frame) {
526
+ this.#warnDuplicate(state);
527
+ return;
528
+ }
529
+ walk.inner?.push({ comp: entry.comp, parent, before: next });
530
+ if (state) {
531
+ this.#move(state, parent, before);
532
+ this.#visit(state, parent, before, walk.settled);
533
+ return;
534
+ }
535
+ this.#addComponent(entry.comp, parent, before, walk.settled);
536
+ }
537
+
538
+ /**
539
+ * Visit a component whose document position is already right. `before` is
540
+ * an anchor present in the document now (for a root re-add); `next` is its
541
+ * following sibling, recorded for later frames.
542
+ */
543
+ #visitChild(comp: Component, parent: string, before: string | null, next: string | null, walk: Walk): void {
544
+ const state = this.#states.get(comp);
545
+ if (state?.frame === this.#frame) {
546
+ this.#warnDuplicate(state);
547
+ return;
548
+ }
549
+ walk.inner?.push({ comp, parent, before: next });
550
+ if (state) this.#visit(state, parent, before, walk.settled);
551
+ else this.#addComponent(comp, parent, before, walk.settled);
552
+ }
553
+
554
+ #addComponent(comp: Component, parent: string, before: string | null, settled: boolean): void {
555
+ const state = this.#createState(comp, parent);
556
+ this.#ops.push(["add", state.id, parent, before, this.#materializeComponent(state, settled)]);
557
+ this.#flushMoves();
558
+ }
559
+
560
+ #warnDuplicate(state: CompState): void {
561
+ logger.warn("TSP: component described twice in one frame; later occurrence skipped", {
562
+ id: state.id,
563
+ kind: state.comp.constructor.name,
564
+ });
565
+ }
566
+
567
+ #walkFor(entry: NodeEntry, walk: Walk): Walk {
568
+ return entry.hoist === walk.hoist ? walk : { ...walk, hoist: entry.hoist };
569
+ }
570
+
571
+ // ── Nodes ────────────────────────────────────────────────────────────
572
+
573
+ #diffNode(old: NodeEntry, next: NodeEntry, parent: string, before: string | null, walk: Walk): void {
574
+ const inner = this.#walkFor(next, walk);
575
+ if (old.node === next.node) {
576
+ this.#revisitNode(next, inner);
577
+ return;
578
+ }
579
+ if (old.node.k !== next.node.k) {
580
+ this.#dropSubtree(old);
581
+ this.#ops.push(["del", next.id]);
582
+ this.#ops.push(["add", next.id, parent, before, this.#materialize(next, inner)]);
583
+ this.#flushMoves();
584
+ return;
585
+ }
586
+ const oldEntries = this.#entries(old.node.c, old.keypath, old.owner, old.hoist, null);
587
+ const newEntries = this.#entries(next.node.c, next.keypath, next.owner, next.hoist, inner);
588
+ this.#diffProps(
589
+ next.id,
590
+ next.node.k,
591
+ this.#wireProps(old.node, old.owner, oldEntries),
592
+ this.#wireProps(next.node, next.owner, newEntries),
593
+ );
594
+ this.#diffChildren(next.id, oldEntries, newEntries, null, inner);
595
+ }
596
+
597
+ /** Walk an unchanged node for the component boundaries and overlays inside it. */
598
+ #revisitNode(entry: NodeEntry, walk: Walk): void {
599
+ const entries = this.#entries(entry.node.c, entry.keypath, entry.owner, entry.hoist, walk);
600
+ for (let i = 0; i < entries.length; i++) {
601
+ const child = entries[i]!;
602
+ const before = i + 1 < entries.length ? entries[i + 1]!.id : null;
603
+ if (child.comp === undefined) this.#revisitNode(child, this.#walkFor(child, walk));
604
+ else this.#visitChild(child.comp, entry.id, before, before, walk);
605
+ }
606
+ }
607
+
608
+ #diffProps(
609
+ id: string,
610
+ kind: TspKind,
611
+ oldProps: Readonly<Record<string, unknown>> | undefined,
612
+ newProps: Readonly<Record<string, unknown>> | undefined,
613
+ ): void {
614
+ const textKind = TEXT_KINDS.has(kind);
615
+ let set: Record<string, unknown> | undefined;
616
+ if (newProps) {
617
+ for (const key in newProps) {
618
+ const value = newProps[key];
619
+ if (value === undefined || (textKind && key === "text")) continue;
620
+ if (!sameValue(oldProps?.[key], value)) (set ??= {})[key] = value;
621
+ }
622
+ if (kind === "list") this.#noteSelected(set?.selected);
623
+ }
624
+ if (oldProps) {
625
+ for (const key in oldProps) {
626
+ if (oldProps[key] === undefined || newProps?.[key] !== undefined) continue;
627
+ if (textKind && key === "text") continue;
628
+ (set ??= {})[key] = null;
629
+ }
630
+ }
631
+ if (textKind) {
632
+ const oldText = oldProps?.text as string | undefined;
633
+ const newText = newProps?.text as string | undefined;
634
+ if (newText === undefined) {
635
+ if (oldText !== undefined) (set ??= {}).text = null;
636
+ } else if (newText !== oldText) {
637
+ this.#textOp(id, kind, oldText ?? "", newText);
638
+ }
639
+ }
640
+ if (set) this.#ops.push(["set", id, set]);
641
+ }
642
+
643
+ #textOp(id: string, kind: TspKind, oldText: string, newText: string): void {
644
+ if (newText.length >= oldText.length && newText.startsWith(oldText)) {
645
+ this.#ops.push(["text", id, "append", newText.slice(oldText.length)]);
646
+ return;
647
+ }
648
+ if ((kind === "editor" || kind === "input") && oldText.length > 0) {
649
+ const limit = Math.min(oldText.length, newText.length);
650
+ let prefix = 0;
651
+ while (prefix < limit && oldText.charCodeAt(prefix) === newText.charCodeAt(prefix)) prefix++;
652
+ let suffix = 0;
653
+ while (
654
+ suffix < limit - prefix &&
655
+ oldText.charCodeAt(oldText.length - 1 - suffix) === newText.charCodeAt(newText.length - 1 - suffix)
656
+ )
657
+ suffix++;
658
+ this.#ops.push([
659
+ "splice",
660
+ id,
661
+ prefix,
662
+ oldText.length - prefix - suffix,
663
+ newText.slice(prefix, newText.length - suffix),
664
+ ]);
665
+ return;
666
+ }
667
+ this.#ops.push(["text", id, "replace", newText]);
668
+ }
669
+
670
+ /**
671
+ * Props as sent: item keys, header children and anchor keypaths resolved to
672
+ * wire ids, and icon glyphs split into `icon` spans.
673
+ */
674
+ #wireProps(
675
+ node: NativeNode,
676
+ owner: Owner,
677
+ entries: readonly Entry[],
678
+ ): Readonly<Record<string, unknown>> | undefined {
679
+ return normalizeIconProps(node.k, this.#resolveRefs(node, owner, entries));
680
+ }
681
+
682
+ #resolveRefs(
683
+ node: NativeNode,
684
+ owner: Owner,
685
+ entries: readonly Entry[],
686
+ ): Readonly<Record<string, unknown>> | undefined {
687
+ const props = asProps(node);
688
+ switch (node.k) {
689
+ case "list": {
690
+ const selected = props?.selected;
691
+ if (typeof selected !== "string") return props;
692
+ const item = entries.find(entry => entry.comp === undefined && entry.node.key === selected);
693
+ if (!item) return props;
694
+ this.#selectedIds.add(item.id);
695
+ return { ...props, selected: item.id };
696
+ }
697
+ case "card":
698
+ case "section": {
699
+ if (props?.head !== undefined) return props;
700
+ const head = entries.find(entry => entry.comp === undefined && entry.node.key === "head");
701
+ return head ? { ...props, head: head.id } : props;
702
+ }
703
+ case "overlay": {
704
+ const anchor = props?.anchor;
705
+ if (owner.state === null || typeof anchor !== "object" || anchor === null) return props;
706
+ if ("caret" in anchor && typeof anchor.caret === "string")
707
+ return { ...props, anchor: { caret: nodeId(owner.base, anchor.caret) } };
708
+ if ("node" in anchor && typeof anchor.node === "string")
709
+ return { ...props, anchor: { ...anchor, node: nodeId(owner.base, anchor.node) } };
710
+ return props;
711
+ }
712
+ default:
713
+ return props;
714
+ }
715
+ }
716
+
717
+ /** A full wire subtree for an entry; nested components get states (or pending moves when mounted elsewhere). */
718
+ #materialize(entry: NodeEntry, walk: Walk): TspNode {
719
+ const entries = this.#entries(entry.node.c, entry.keypath, entry.owner, entry.hoist, walk);
720
+ const children: TspNode[] = [];
721
+ for (let i = 0; i < entries.length; i++) {
722
+ const child = entries[i]!;
723
+ if (child.comp === undefined) {
724
+ children.push(this.#materialize(child, this.#walkFor(child, walk)));
725
+ continue;
726
+ }
727
+ let state = this.#states.get(child.comp);
728
+ if (state?.frame === this.#frame) {
729
+ this.#warnDuplicate(state);
730
+ continue;
731
+ }
732
+ const before = i + 1 < entries.length ? entries[i + 1]!.id : null;
733
+ walk.inner?.push({ comp: child.comp, parent: entry.id, before });
734
+ if (state) {
735
+ const next = this.#resolve(state);
736
+ if (next.k === (state.node?.k ?? state.kind)) {
737
+ // Mounted elsewhere: update it in place now, then move it in once
738
+ // this subtree lands, keeping its terminal-side state. Its own adds
739
+ // flush their moves; ours wait for the enclosing add.
740
+ // Pending before the visit: a delete during it that would take this
741
+ // component along parks it instead.
742
+ this.#pendingMoves.add(state.id);
743
+ const pending = this.#moves;
744
+ this.#moves = [];
745
+ this.#visit(state, entry.id, before, walk.settled, next);
746
+ this.#moves = pending;
747
+ pending.push({ id: state.id, parent: entry.id, before });
748
+ continue;
749
+ }
750
+ // Its root kind changed too: nothing worth keeping, rebuild it here.
751
+ this.#dropState(state);
752
+ this.#ops.push(["del", state.id]);
753
+ }
754
+ state = this.#createState(child.comp, entry.id);
755
+ children.push(this.#materializeComponent(state, walk.settled));
756
+ }
757
+ return this.#wire(entry.id, entry.node.k, this.#wireProps(entry.node, entry.owner, entries), children);
758
+ }
759
+
760
+ #wire(
761
+ id: string,
762
+ k: TspKind,
763
+ props: Readonly<Record<string, unknown>> | undefined,
764
+ children: readonly TspNode[],
765
+ ): TspNode {
766
+ if (k === "list") this.#noteSelected(props?.selected);
767
+ const out: { id: string; k: TspKind; p?: Record<string, unknown>; c?: readonly TspNode[] } = { id, k };
768
+ if (props) {
769
+ let p: Record<string, unknown> | undefined;
770
+ for (const key in props) {
771
+ const value = props[key];
772
+ if (value !== undefined && value !== null) (p ??= {})[key] = value;
773
+ }
774
+ if (p) out.p = p;
775
+ }
776
+ if (children.length > 0) out.c = children;
777
+ return out as TspNode;
778
+ }
779
+
780
+ /** A list's selection went out (on add or change): scroll it into view at frame end. */
781
+ #noteSelected(selected: unknown): void {
782
+ if (typeof selected === "string" && this.#selectedIds.has(selected)) this.#reveals.push(selected);
783
+ }
784
+
785
+ /** Move components an `add` left out into their new parent, last first so every `before` exists. */
786
+ #flushMoves(): void {
787
+ const moves = this.#moves;
788
+ if (moves.length === 0) return;
789
+ this.#moves = [];
790
+ for (let i = moves.length - 1; i >= 0; i--) {
791
+ const move = moves[i]!;
792
+ const state = this.#byId.get(move.id);
793
+ if (state) this.#move(state, move.parent, move.before);
794
+ else this.#ops.push(["move", move.id, move.parent, move.before]);
795
+ this.#pendingMoves.delete(move.id);
796
+ }
797
+ }
798
+
799
+ /** The component whose description holds node `parent` in the document (null: region, root, or `layer` via hoisting). */
800
+ #containerOf(parent: string): CompState | null {
801
+ if (parent.includes("^")) return null;
802
+ const dot = parent.indexOf(".");
803
+ return this.#byId.get(dot === -1 ? parent : parent.slice(0, dot)) ?? null;
804
+ }
805
+
806
+ /**
807
+ * Move a component. When the target currently sits inside the component
808
+ * itself (a parent and child swapped places this frame), first park the
809
+ * component on that path under the root; it moves to its own new place
810
+ * later in the frame.
811
+ */
812
+ #move(state: CompState, parent: string, before: string | null): void {
813
+ const target = this.#containerOf(parent);
814
+ // The first component on the path that still has to move this frame
815
+ // (not yet visited, or waiting for an enclosing add): parking it breaks
816
+ // the cycle without disturbing anything already in its final place.
817
+ let movable: CompState | null = null;
818
+ for (let at = target, guard = 0; at && guard < 4096; at = at.container, guard++) {
819
+ if (at === state) {
820
+ if (movable) {
821
+ this.#ops.push(["move", movable.id, this.surface, null]);
822
+ movable.container = null;
823
+ movable.parked = true;
824
+ this.#parked.push(movable);
825
+ }
826
+ break;
827
+ }
828
+ if (!movable && (at.frame !== this.#frame || this.#pendingMoves.has(at.id))) movable = at;
829
+ }
830
+ this.#ops.push(["move", state.id, parent, before]);
831
+ state.container = target;
832
+ state.parked = false;
833
+ }
834
+
835
+ // ── Components ───────────────────────────────────────────────────────
836
+
837
+ #createState(comp: Component, parent: string): CompState {
838
+ const id = nativeComponentId(comp);
839
+ const owner: { base: string; state: CompState | null } = { base: id, state: null };
840
+ const state: CompState = {
841
+ comp,
842
+ id,
843
+ owner,
844
+ node: null,
845
+ weak: null,
846
+ kind: "col",
847
+ propKeys: [],
848
+ childIds: [],
849
+ inner: [],
850
+ rowsLines: null,
851
+ rowsCols: 0,
852
+ rowsNode: null,
853
+ settled: false,
854
+ settleSent: false,
855
+ frame: 0,
856
+ container: this.#containerOf(parent),
857
+ parked: false,
858
+ };
859
+ owner.state = state;
860
+ this.#states.set(comp, state);
861
+ this.#byId.set(id, state);
862
+ return state;
863
+ }
864
+
865
+ /** The component's description, or a `rows` node rendered at `cx.cols`. */
866
+ #resolve(state: CompState): NativeNode {
867
+ const comp = state.comp;
868
+ const describe = comp.describe;
869
+ // A Container subclass that paints its own rows but inherits the plain
870
+ // column description would lose its rendering: fall back to rows.
871
+ // (Read lazily: this module and tui.ts import each other.)
872
+ const inherited = describe === Container.prototype.describe && comp.render !== Container.prototype.render;
873
+ if (describe && !inherited) {
874
+ const node = describe.call(comp, this.#cx!);
875
+ if (node) return node;
876
+ }
877
+ this.#rows++;
878
+ const cols = Math.max(1, this.#cx!.cols);
879
+ const lines = comp.render(cols);
880
+ if (state.rowsNode && lines === state.rowsLines && cols === state.rowsCols) return state.rowsNode;
881
+ state.rowsLines = lines;
882
+ state.rowsCols = cols;
883
+ const clean = lines.some(line => line.includes(CURSOR_MARKER))
884
+ ? lines.map(line => line.replaceAll(CURSOR_MARKER, ""))
885
+ : lines;
886
+ state.rowsNode = { k: "rows", p: { cols, lines: clean } };
887
+ return state.rowsNode;
888
+ }
889
+
890
+ #identity(state: CompState, node: NativeNode): object {
891
+ return node === state.rowsNode && state.rowsLines ? state.rowsLines : node;
892
+ }
893
+
894
+ #rootEntry(state: CompState, node: NativeNode): NodeEntry {
895
+ return { id: state.id, node, owner: state.owner, keypath: "", hoist: true };
896
+ }
897
+
898
+ #materializeComponent(state: CompState, settledAbove: boolean): TspNode {
899
+ state.frame = this.#frame;
900
+ const node = this.#resolve(state);
901
+ const settled = settledAbove || state.settled || isNativeSettled(state.comp);
902
+ const inner: Inner[] = [];
903
+ const wire = this.#materialize(this.#rootEntry(state, node), { owner: state.owner, inner, settled, hoist: true });
904
+ state.inner = inner;
905
+ state.node = node;
906
+ state.weak = null;
907
+ state.settleSent = false;
908
+ this.#finish(state, node, settled);
909
+ return wire;
910
+ }
911
+
912
+ #visit(
913
+ state: CompState,
914
+ parent: string,
915
+ before: string | null,
916
+ settledAbove: boolean,
917
+ next: NativeNode = this.#resolve(state),
918
+ ): void {
919
+ state.frame = this.#frame;
920
+ const settled = settledAbove || state.settled || isNativeSettled(state.comp);
921
+ if (state.node !== null) {
922
+ if (next === state.node) this.#revisitComponent(state, settled);
923
+ else {
924
+ const inner: Inner[] = [];
925
+ const walk: Walk = { owner: state.owner, inner, settled, hoist: true };
926
+ this.#diffNode(this.#rootEntry(state, state.node), this.#rootEntry(state, next), parent, before, walk);
927
+ state.inner = inner;
928
+ state.node = next;
929
+ }
930
+ } else if (state.weak?.deref() === this.#identity(state, next)) {
931
+ this.#revisitComponent(state, settled);
932
+ } else {
933
+ this.#replaceSettled(state, next, parent, before, settled);
934
+ }
935
+ this.#finish(state, next, settled);
936
+ }
937
+
938
+ /** Replay an unchanged component's walk: visit its boundaries, re-offer its overlays. */
939
+ #revisitComponent(state: CompState, settled: boolean): void {
940
+ const walk: Walk = { owner: state.owner, inner: null, settled, hoist: true };
941
+ for (const item of state.inner) {
942
+ if (item.comp === undefined) this.#framePortals.push(item.portal);
943
+ else this.#visitChild(item.comp, item.parent, item.before, item.before, walk);
944
+ }
945
+ }
946
+
947
+ /** A settled component changed: targeted ops by id, no diff against dropped state. */
948
+ #replaceSettled(state: CompState, next: NativeNode, parent: string, before: string | null, settled: boolean): void {
949
+ state.settleSent = false;
950
+ const inner: Inner[] = [];
951
+ const walk: Walk = { owner: state.owner, inner, settled, hoist: true };
952
+ if (next.k !== state.kind) {
953
+ this.#dropNested(state);
954
+ this.#ops.push(["del", state.id]);
955
+ this.#ops.push(["add", state.id, parent, before, this.#materialize(this.#rootEntry(state, next), walk)]);
956
+ this.#flushMoves();
957
+ state.inner = inner;
958
+ return;
959
+ }
960
+ const entries = this.#entries(next.c, "", state.owner, true, walk);
961
+ const props = this.#wireProps(next, state.owner, entries);
962
+ const textKind = TEXT_KINDS.has(next.k);
963
+ let set: Record<string, unknown> | undefined;
964
+ if (props) {
965
+ for (const key in props) {
966
+ const value = props[key];
967
+ if (value === undefined || (textKind && key === "text")) continue;
968
+ (set ??= {})[key] = value;
969
+ }
970
+ }
971
+ for (const key of state.propKeys) {
972
+ if (props?.[key] === undefined) (set ??= {})[key] = null;
973
+ }
974
+ if (set) this.#ops.push(["set", state.id, set]);
975
+ if (next.k === "list") this.#noteSelected(set?.selected);
976
+ if (textKind && typeof props?.text === "string") this.#ops.push(["text", state.id, "replace", props.text]);
977
+ this.#dropNested(state);
978
+ for (const childId of state.childIds) {
979
+ // A child component updated elsewhere this frame, or parked, no longer sits here.
980
+ const child = this.#byId.get(childId);
981
+ if (child?.frame !== this.#frame && !child?.parked) this.#ops.push(["del", childId]);
982
+ }
983
+ this.#diffChildren(state.id, EMPTY_ENTRIES, entries, null, walk);
984
+ state.inner = inner;
985
+ }
986
+
987
+ /** Send the settle hint once, then keep only what targeted updates need. */
988
+ #finish(state: CompState, node: NativeNode, settled: boolean): void {
989
+ if (!settled) return;
990
+ state.settled = true;
991
+ if (!state.settleSent) {
992
+ state.settleSent = true;
993
+ this.#settles.push(state.id);
994
+ }
995
+ const entries = this.#entries(node.c, "", state.owner, true, null);
996
+ const props = this.#wireProps(node, state.owner, entries);
997
+ const keys: string[] = [];
998
+ if (props) for (const key in props) if (props[key] !== undefined) keys.push(key);
999
+ state.kind = node.k;
1000
+ state.propKeys = keys;
1001
+ state.childIds = entries.map(entry => entry.id);
1002
+ state.weak = new WeakRef(this.#identity(state, node));
1003
+ state.node = null;
1004
+ }
1005
+
1006
+ // ── Removal ──────────────────────────────────────────────────────────
1007
+
1008
+ #deleteEntry(entry: Entry): void {
1009
+ if (entry.comp !== undefined) {
1010
+ const state = this.#states.get(entry.comp);
1011
+ if (state?.frame === this.#frame) return;
1012
+ if (state) this.#dropState(state);
1013
+ this.#ops.push(["del", entry.id]);
1014
+ return;
1015
+ }
1016
+ this.#dropSubtree(entry);
1017
+ this.#ops.push(["del", entry.id]);
1018
+ }
1019
+
1020
+ /**
1021
+ * Forget the components inside a subtree about to be deleted. Call before
1022
+ * emitting its `del`: a component already updated this frame and waiting to
1023
+ * move into a new subtree is parked under the surface root first, so the
1024
+ * delete doesn't take it along.
1025
+ */
1026
+ #dropSubtree(entry: NodeEntry): void {
1027
+ for (const child of this.#entries(entry.node.c, entry.keypath, entry.owner, entry.hoist, null)) {
1028
+ if (child.comp === undefined) this.#dropSubtree(child);
1029
+ else {
1030
+ const state = this.#states.get(child.comp);
1031
+ if (state) this.#dropOrPark(state);
1032
+ }
1033
+ }
1034
+ }
1035
+
1036
+ #dropNested(state: CompState): void {
1037
+ for (const item of state.inner) {
1038
+ if (item.comp === undefined) continue;
1039
+ const child = this.#states.get(item.comp);
1040
+ if (child) this.#dropOrPark(child);
1041
+ }
1042
+ state.inner = [];
1043
+ }
1044
+
1045
+ #dropOrPark(state: CompState): void {
1046
+ // Already parked: no longer inside the subtree being deleted.
1047
+ if (state.parked) return;
1048
+ if (state.frame !== this.#frame) this.#dropState(state);
1049
+ else if (this.#pendingMoves.has(state.id)) {
1050
+ this.#ops.push(["move", state.id, this.surface, null]);
1051
+ state.container = null;
1052
+ state.parked = true;
1053
+ this.#parked.push(state);
1054
+ }
1055
+ }
1056
+
1057
+ #dropState(state: CompState): void {
1058
+ state.parked = false;
1059
+ this.#states.delete(state.comp);
1060
+ this.#byId.delete(state.id);
1061
+ this.#dropNested(state);
1062
+ }
1063
+ }