@selesai/code 0.9.15 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +6 -9
  3. package/dist/defaults/models.json +2 -2
  4. package/dist/defaults/settings.json +3 -7
  5. package/dist/extensions/package.json +2 -4
  6. package/dist/extensions/pi-zentui/.editorconfig +12 -0
  7. package/dist/extensions/pi-zentui/.github/CODEOWNERS +1 -0
  8. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/bug_report.yml +110 -0
  9. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config.yml +8 -0
  10. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config_question.yml +56 -0
  11. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/feature_request.yml +63 -0
  12. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/rendering_issue.yml +106 -0
  13. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/runtime_detection.yml +103 -0
  14. package/dist/extensions/pi-zentui/.github/dependabot.yml +47 -0
  15. package/dist/extensions/pi-zentui/.github/pull_request_template.md +34 -0
  16. package/dist/extensions/pi-zentui/.github/release.yml +18 -0
  17. package/dist/extensions/pi-zentui/.github/workflows/ci.yml +79 -0
  18. package/dist/extensions/pi-zentui/.github/workflows/publish.yml +63 -0
  19. package/dist/extensions/pi-zentui/AGENTS.md +107 -0
  20. package/dist/extensions/pi-zentui/CONTRIBUTING.md +68 -0
  21. package/dist/extensions/pi-zentui/LICENSE +21 -0
  22. package/dist/extensions/pi-zentui/README.md +188 -0
  23. package/dist/extensions/pi-zentui/SECURITY.md +18 -0
  24. package/dist/extensions/pi-zentui/assets/main-cover.png +0 -0
  25. package/dist/extensions/pi-zentui/assets/screenshots/editor-accent-rail.png +0 -0
  26. package/dist/extensions/pi-zentui/assets/screenshots/editor-minimalist.png +0 -0
  27. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode-copy-friendly.png +0 -0
  28. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode.png +0 -0
  29. package/dist/extensions/pi-zentui/assets/screenshots/user-message-compact.png +0 -0
  30. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed-copy-friendly.png +0 -0
  31. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed.png +0 -0
  32. package/dist/extensions/pi-zentui/assets/screenshots/user-message-labeled.png +0 -0
  33. package/dist/extensions/pi-zentui/assets/zentui.png +0 -0
  34. package/dist/extensions/pi-zentui/biome.json +31 -0
  35. package/dist/extensions/pi-zentui/docs/configuration.md +509 -0
  36. package/dist/extensions/pi-zentui/docs/footer-format.md +162 -0
  37. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-editor.ts +99 -0
  38. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-layout-patch.ts +457 -0
  39. package/dist/extensions/pi-zentui/extensions/zentui/completion-menu.ts +157 -0
  40. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +2100 -0
  41. package/dist/extensions/pi-zentui/extensions/zentui/editor-metadata-format.ts +321 -0
  42. package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +73 -0
  43. package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +89 -0
  44. package/dist/extensions/pi-zentui/extensions/zentui/footer-format.ts +364 -0
  45. package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +122 -0
  46. package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +812 -0
  47. package/dist/extensions/pi-zentui/extensions/zentui/format.ts +568 -0
  48. package/dist/extensions/pi-zentui/extensions/zentui/git.ts +384 -0
  49. package/dist/extensions/pi-zentui/extensions/zentui/icons.ts +252 -0
  50. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +1406 -0
  51. package/dist/extensions/pi-zentui/extensions/zentui/interaction-summary.ts +1049 -0
  52. package/dist/extensions/pi-zentui/extensions/zentui/live-context.ts +75 -0
  53. package/dist/extensions/pi-zentui/extensions/zentui/minimalist-editor.ts +463 -0
  54. package/dist/extensions/pi-zentui/extensions/zentui/package-version.ts +651 -0
  55. package/dist/extensions/pi-zentui/extensions/zentui/project-refresh.ts +120 -0
  56. package/dist/extensions/pi-zentui/extensions/zentui/project-state.ts +59 -0
  57. package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +166 -0
  58. package/dist/extensions/pi-zentui/extensions/zentui/repository-root.ts +98 -0
  59. package/dist/extensions/pi-zentui/extensions/zentui/runtime.ts +841 -0
  60. package/dist/extensions/pi-zentui/extensions/zentui/selector-border.ts +93 -0
  61. package/dist/extensions/pi-zentui/extensions/zentui/session-lifecycle.ts +60 -0
  62. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +1796 -0
  63. package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +128 -0
  64. package/dist/extensions/pi-zentui/extensions/zentui/state.ts +94 -0
  65. package/dist/extensions/pi-zentui/extensions/zentui/style.ts +424 -0
  66. package/dist/extensions/pi-zentui/extensions/zentui/telemetry.ts +82 -0
  67. package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +1290 -0
  68. package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +248 -0
  69. package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +215 -0
  70. package/dist/extensions/pi-zentui/extensions/zentui/user-message.ts +206 -0
  71. package/dist/extensions/pi-zentui/extensions/zentui/working-line-messages.ts +24 -0
  72. package/dist/extensions/pi-zentui/extensions/zentui/working-line-spinners.ts +54 -0
  73. package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +1433 -0
  74. package/dist/extensions/pi-zentui/package-lock.json +5311 -0
  75. package/dist/extensions/pi-zentui/package.json +60 -0
  76. package/dist/extensions/pi-zentui/test/accent-rail-editor.test.ts +232 -0
  77. package/dist/extensions/pi-zentui/test/accent-rail-layout-patch.test.ts +669 -0
  78. package/dist/extensions/pi-zentui/test/completion-menu.test.ts +265 -0
  79. package/dist/extensions/pi-zentui/test/config.test.ts +3119 -0
  80. package/dist/extensions/pi-zentui/test/editor-metadata-format.test.ts +405 -0
  81. package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +260 -0
  82. package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +1286 -0
  83. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +7196 -0
  84. package/dist/extensions/pi-zentui/test/extension-status.test.ts +182 -0
  85. package/dist/extensions/pi-zentui/test/fixtures/starship-nerd-font-symbols.toml +172 -0
  86. package/dist/extensions/pi-zentui/test/footer-format.test.ts +431 -0
  87. package/dist/extensions/pi-zentui/test/footer-layout.test.ts +113 -0
  88. package/dist/extensions/pi-zentui/test/footer-repository-path.test.ts +123 -0
  89. package/dist/extensions/pi-zentui/test/format.test.ts +865 -0
  90. package/dist/extensions/pi-zentui/test/git.test.ts +212 -0
  91. package/dist/extensions/pi-zentui/test/icons.test.ts +69 -0
  92. package/dist/extensions/pi-zentui/test/interaction-summary.test.ts +1402 -0
  93. package/dist/extensions/pi-zentui/test/live-context-integration.test.ts +378 -0
  94. package/dist/extensions/pi-zentui/test/live-context.test.ts +139 -0
  95. package/dist/extensions/pi-zentui/test/minimalist-editor.test.ts +811 -0
  96. package/dist/extensions/pi-zentui/test/package-version.test.ts +321 -0
  97. package/dist/extensions/pi-zentui/test/project-refresh.test.ts +250 -0
  98. package/dist/extensions/pi-zentui/test/project-state.test.ts +159 -0
  99. package/dist/extensions/pi-zentui/test/repository-root.test.ts +108 -0
  100. package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +825 -0
  101. package/dist/extensions/pi-zentui/test/runtime.test.ts +183 -0
  102. package/dist/extensions/pi-zentui/test/session-lifecycle.test.ts +40 -0
  103. package/dist/extensions/pi-zentui/test/settings-command.test.ts +1381 -0
  104. package/dist/extensions/pi-zentui/test/settings-previews.test.ts +347 -0
  105. package/dist/extensions/pi-zentui/test/state.test.ts +84 -0
  106. package/dist/extensions/pi-zentui/test/telemetry-lifecycle-integration.test.ts +276 -0
  107. package/dist/extensions/pi-zentui/test/telemetry.test.ts +105 -0
  108. package/dist/extensions/pi-zentui/test/turn-summary-lifecycle.integration.test.ts +327 -0
  109. package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +164 -0
  110. package/dist/extensions/pi-zentui/test/user-message-styles.test.ts +292 -0
  111. package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +730 -0
  112. package/dist/extensions/pi-zentui/test/working-line-messages.test.ts +34 -0
  113. package/dist/extensions/pi-zentui/test/working-line.test.ts +1990 -0
  114. package/dist/extensions/pi-zentui/tsconfig.json +16 -0
  115. package/dist/extensions/tps.test.ts +38 -0
  116. package/dist/extensions/{pi-powerline-footer/tps.ts → tps.ts} +4 -0
  117. package/dist/modes/interactive/components/settings-selector.d.ts +1 -1
  118. package/dist/modes/interactive/interactive-mode.js +1 -1
  119. package/dist/skills/design-references/SKILL.md +31 -0
  120. package/dist/skills/design-references/references/clickhouse/DESIGN.md +544 -0
  121. package/dist/themes/adventure.json +84 -0
  122. package/dist/themes/adwaita-dark.json +84 -0
  123. package/dist/themes/arcoiris.json +84 -0
  124. package/dist/themes/arthur.json +84 -0
  125. package/dist/themes/atom.json +84 -0
  126. package/dist/themes/aura.json +84 -0
  127. package/dist/themes/black-metal-bathory.json +84 -0
  128. package/dist/themes/black-metal-burzum.json +84 -0
  129. package/dist/themes/black-metal-khold.json +84 -0
  130. package/dist/themes/box.json +84 -0
  131. package/dist/themes/brogrammer.json +84 -0
  132. package/dist/themes/carbonfox.json +84 -0
  133. package/dist/themes/catppuccin-mocha.json +84 -0
  134. package/dist/themes/citruszest.json +84 -0
  135. package/dist/themes/cursor-dark.json +84 -0
  136. package/dist/themes/cutie-pro.json +84 -0
  137. package/dist/themes/dark-modern.json +84 -0
  138. package/dist/themes/dark-pastel.json +84 -0
  139. package/dist/themes/dimmed-monokai.json +84 -0
  140. package/dist/themes/doom-peacock.json +84 -0
  141. package/dist/themes/dracula-plus.json +84 -0
  142. package/dist/themes/dracula.json +80 -0
  143. package/dist/themes/earthsong.json +84 -0
  144. package/dist/themes/everforest-dark-hard.json +84 -0
  145. package/dist/themes/everforest-dark.json +80 -0
  146. package/dist/themes/fahrenheit.json +84 -0
  147. package/dist/themes/flatland.json +84 -0
  148. package/dist/themes/flexoki-dark.json +84 -0
  149. package/dist/themes/front-end-delight.json +84 -0
  150. package/dist/themes/fun-forrest.json +84 -0
  151. package/dist/themes/galizur.json +84 -0
  152. package/dist/themes/github-dark-colorblind.json +84 -0
  153. package/dist/themes/github-dark-high-contrast.json +84 -0
  154. package/dist/themes/glacier.json +84 -0
  155. package/dist/themes/gruber-darker.json +84 -0
  156. package/dist/themes/gruvbox-dark-hard.json +84 -0
  157. package/dist/themes/gruvbox-dark.json +84 -0
  158. package/dist/themes/gruvbox-material.json +84 -0
  159. package/dist/themes/guezwhoz.json +84 -0
  160. package/dist/themes/hacktober.json +84 -0
  161. package/dist/themes/hardcore.json +84 -0
  162. package/dist/themes/havn-skumring.json +84 -0
  163. package/dist/themes/ic-orange-ppl.json +84 -0
  164. package/dist/themes/iterm2-smoooooth.json +84 -0
  165. package/dist/themes/iterm2-tango-dark.json +84 -0
  166. package/dist/themes/japanesque.json +84 -0
  167. package/dist/themes/jellybeans.json +84 -0
  168. package/dist/themes/kanagawa-wave.json +84 -0
  169. package/dist/themes/kurokula.json +84 -0
  170. package/dist/themes/later-this-evening.json +84 -0
  171. package/dist/themes/lovelace.json +84 -0
  172. package/dist/themes/material-darker.json +84 -0
  173. package/dist/themes/matte-black.json +84 -0
  174. package/dist/themes/mellow.json +84 -0
  175. package/dist/themes/miasma.json +84 -0
  176. package/dist/themes/nord.json +80 -0
  177. package/dist/themes/nvim-dark.json +84 -0
  178. package/dist/themes/one-dark.json +80 -0
  179. package/dist/themes/popping-and-locking.json +84 -0
  180. package/dist/themes/rose-pine.json +80 -0
  181. package/dist/themes/sea-shells.json +84 -0
  182. package/dist/themes/sleepy-hollow.json +84 -0
  183. package/dist/themes/smyck.json +84 -0
  184. package/dist/themes/solarized-dark.json +80 -0
  185. package/dist/themes/tokyo-night.json +80 -0
  186. package/dist/themes/tomorrow-night-bright.json +84 -0
  187. package/dist/themes/tomorrow-night-burns.json +84 -0
  188. package/dist/themes/tomorrow-night.json +84 -0
  189. package/dist/themes/twilight.json +84 -0
  190. package/dist/themes/vague.json +84 -0
  191. package/dist/themes/vesper.json +84 -0
  192. package/dist/themes/xcode-dark-hc.json +84 -0
  193. package/docs/custom-provider.md +0 -1
  194. package/package.json +3 -3
  195. package/dist/extensions/auto-model/auto-model.test.ts +0 -18
  196. package/dist/extensions/auto-model/classifier.ts +0 -23
  197. package/dist/extensions/auto-model/config.ts +0 -51
  198. package/dist/extensions/auto-model/index.ts +0 -22
  199. package/dist/extensions/auto-model/lifecycle.test.ts +0 -24
  200. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
  201. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
  202. package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
  203. package/dist/extensions/pi-powerline-footer/README.md +0 -513
  204. package/dist/extensions/pi-powerline-footer/banner.png +0 -0
  205. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
  206. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
  207. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
  208. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
  209. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
  210. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
  211. package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
  212. package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
  213. package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
  214. package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
  215. package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
  216. package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
  217. package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
  218. package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
  219. package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
  220. package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
  221. package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
  222. package/dist/extensions/pi-powerline-footer/package.json +0 -49
  223. package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
  224. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
  225. package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
  226. package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
  227. package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
  228. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
  229. package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
  230. package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
  231. package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
  232. package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
  233. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
  234. package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
  235. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
  236. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
  237. package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
  238. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
  239. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
  240. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
  241. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
  242. package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
  243. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
  244. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
  245. package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
  246. package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
  247. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
  248. package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
  249. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
  250. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
  251. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
  252. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
  253. package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
  254. package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
  255. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
  256. package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
  257. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
  258. package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
  259. package/dist/extensions/pi-powerline-footer/theme.json +0 -12
  260. package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
  261. package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
  262. package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
  263. package/dist/extensions/pi-powerline-footer/types.ts +0 -235
  264. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
  265. package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
  266. package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
  267. package/dist/extensions/preview-tools-disabled.test.ts +0 -37
  268. package/dist/extensions/preview-tools-disabled.ts +0 -11
  269. package/dist/themes/powerline-footer/theme.json +0 -33
@@ -0,0 +1,120 @@
1
+ export type StopProjectRefreshInterval = () => void;
2
+
3
+ export type ScheduleProjectRefreshOptions = {
4
+ force?: boolean;
5
+ };
6
+
7
+ export type ProjectRefreshRun = {
8
+ isCurrent: () => boolean;
9
+ };
10
+
11
+ export type ProjectRefreshScheduler<T> = {
12
+ schedule: (target: T, options?: ScheduleProjectRefreshOptions) => void;
13
+ invalidate: () => void;
14
+ stop: () => void;
15
+ };
16
+
17
+ export const PROJECT_REFRESH_THROTTLE_MS = 5_000;
18
+
19
+ export function startProjectRefreshInterval(
20
+ intervalMs: number,
21
+ refresh: () => void,
22
+ ): StopProjectRefreshInterval {
23
+ if (intervalMs <= 0) return () => {};
24
+
25
+ const timer = setInterval(refresh, intervalMs);
26
+ timer.unref?.();
27
+
28
+ return () => clearInterval(timer);
29
+ }
30
+
31
+ export function createProjectRefreshScheduler<T>(
32
+ refresh: (target: T, run: ProjectRefreshRun) => Promise<void>,
33
+ afterRefresh: () => void,
34
+ throttleMs = PROJECT_REFRESH_THROTTLE_MS,
35
+ ): ProjectRefreshScheduler<T> {
36
+ let refreshInFlight = false;
37
+ let refreshPending = false;
38
+ let pendingForce = false;
39
+ let pendingTarget: T | undefined;
40
+ let delayedRefresh: ReturnType<typeof setTimeout> | undefined;
41
+ let lastRefreshStartedAt: number | undefined;
42
+ let generation = 0;
43
+
44
+ const clearDelayedRefresh = () => {
45
+ if (!delayedRefresh) return;
46
+ clearTimeout(delayedRefresh);
47
+ delayedRefresh = undefined;
48
+ };
49
+
50
+ const runRefresh = (target: T, options: ScheduleProjectRefreshOptions = {}) => {
51
+ clearDelayedRefresh();
52
+ if (refreshInFlight) {
53
+ refreshPending = true;
54
+ pendingForce ||= options.force === true;
55
+ pendingTarget = target;
56
+ return;
57
+ }
58
+
59
+ const currentGeneration = generation;
60
+ const run: ProjectRefreshRun = {
61
+ isCurrent: () => currentGeneration === generation,
62
+ };
63
+ refreshInFlight = true;
64
+ lastRefreshStartedAt = Date.now();
65
+ void refresh(target, run)
66
+ .catch(() => undefined)
67
+ .finally(() => {
68
+ if (currentGeneration !== generation) return;
69
+ refreshInFlight = false;
70
+ afterRefresh();
71
+ if (refreshPending) {
72
+ refreshPending = false;
73
+ const nextForce = pendingForce;
74
+ pendingForce = false;
75
+ const nextTarget = pendingTarget ?? target;
76
+ pendingTarget = undefined;
77
+ schedule(nextTarget, { force: nextForce });
78
+ }
79
+ });
80
+ };
81
+
82
+ const schedule = (target: T, options: ScheduleProjectRefreshOptions = {}) => {
83
+ if (options.force || throttleMs <= 0 || lastRefreshStartedAt === undefined) {
84
+ runRefresh(target, options);
85
+ return;
86
+ }
87
+
88
+ const delayMs = Math.max(0, throttleMs - (Date.now() - lastRefreshStartedAt));
89
+ if (delayMs === 0) {
90
+ runRefresh(target);
91
+ return;
92
+ }
93
+
94
+ pendingTarget = target;
95
+ if (delayedRefresh) return;
96
+ delayedRefresh = setTimeout(() => {
97
+ delayedRefresh = undefined;
98
+ const nextTarget = pendingTarget ?? target;
99
+ pendingTarget = undefined;
100
+ runRefresh(nextTarget);
101
+ }, delayMs);
102
+ delayedRefresh.unref?.();
103
+ };
104
+
105
+ const invalidate = () => {
106
+ generation += 1;
107
+ clearDelayedRefresh();
108
+ refreshInFlight = false;
109
+ refreshPending = false;
110
+ pendingForce = false;
111
+ pendingTarget = undefined;
112
+ lastRefreshStartedAt = undefined;
113
+ };
114
+
115
+ return {
116
+ schedule,
117
+ invalidate,
118
+ stop: invalidate,
119
+ };
120
+ }
@@ -0,0 +1,59 @@
1
+ import type { GitReadResult } from "./git";
2
+ import { emptyGitStatus } from "./git";
3
+ import type { PackageVersionReadResult } from "./package-version";
4
+ import type { RuntimeReadResult } from "./runtime";
5
+ import type { FooterState } from "./state";
6
+
7
+ /**
8
+ * Apply a project refresh (git + runtime) onto footer state, preserving
9
+ * last-good values on transient errors and clearing on cwd change / not-a-repo.
10
+ *
11
+ * Returns the cwd to store as `previousCwd` for the next refresh.
12
+ */
13
+ export function applyProjectRefreshToState(
14
+ state: FooterState,
15
+ args: {
16
+ cwd: string;
17
+ previousCwd: string | undefined;
18
+ git: GitReadResult;
19
+ runtime: RuntimeReadResult;
20
+ packageVersion?: PackageVersionReadResult;
21
+ },
22
+ ): string {
23
+ const cwdChanged = args.previousCwd !== undefined && args.previousCwd !== args.cwd;
24
+
25
+ if (cwdChanged) {
26
+ Object.assign(state, emptyGitStatus());
27
+ state.runtime = undefined;
28
+ state.packageVersion = undefined;
29
+ }
30
+
31
+ if (args.git.kind === "ok") {
32
+ Object.assign(state, args.git.status);
33
+ } else if (args.git.kind === "not_a_repo") {
34
+ Object.assign(state, emptyGitStatus());
35
+ }
36
+ // kind === "error": keep previous git fields (unless cwdChanged already cleared)
37
+
38
+ if (args.runtime.kind === "ok") {
39
+ state.runtime = args.runtime.runtime;
40
+ } else if (cwdChanged && args.runtime.kind === "error") {
41
+ // Already cleared above; keep undefined.
42
+ state.runtime = undefined;
43
+ }
44
+ // error + same cwd: keep previous runtime
45
+
46
+ if (args.packageVersion !== undefined) {
47
+ if (args.packageVersion.kind === "ok") {
48
+ // `null` means "no manifest in this cwd"; clear so the segment disappears
49
+ // even on the same cwd when the user removes their manifest.
50
+ state.packageVersion = args.packageVersion.result ?? undefined;
51
+ } else if (!cwdChanged) {
52
+ // error + same cwd: keep previous packageVersion (last-good semantics)
53
+ } else {
54
+ state.packageVersion = undefined;
55
+ }
56
+ }
57
+
58
+ return args.cwd;
59
+ }
@@ -0,0 +1,166 @@
1
+ export const ZENTUI_PROTOTYPE_PATCH_REGISTRY = Symbol.for("pi-zentui.prototype-patch-registry");
2
+
3
+ type PrototypePatchAdapter =
4
+ | "user-message-render"
5
+ | "user-message-invalidate"
6
+ | "selector-border-render";
7
+
8
+ type PrototypeMethod = (this: unknown, ...args: unknown[]) => unknown;
9
+
10
+ type PatchInvocation = {
11
+ predecessor: PrototypeMethod;
12
+ receiver: unknown;
13
+ args: unknown[];
14
+ };
15
+
16
+ type PatchBehavior = (invocation: PatchInvocation) => unknown;
17
+
18
+ type Registration = {
19
+ token: symbol;
20
+ behavior?: PatchBehavior;
21
+ };
22
+
23
+ type PatchRecord = {
24
+ method: "render" | "invalidate";
25
+ predecessor: PrototypeMethod;
26
+ predecessorDescriptor?: PropertyDescriptor;
27
+ wrapper: PrototypeMethod;
28
+ registration?: Registration;
29
+ };
30
+
31
+ type PatchRegistry = Map<PrototypePatchAdapter, PatchRecord>;
32
+
33
+ type PatchTarget = Record<PropertyKey, unknown>;
34
+
35
+ function existingRegistry(target: PatchTarget): PatchRegistry | undefined {
36
+ const existing = target[ZENTUI_PROTOTYPE_PATCH_REGISTRY];
37
+ return existing instanceof Map ? (existing as PatchRegistry) : undefined;
38
+ }
39
+
40
+ function registryFor(target: PatchTarget): PatchRegistry {
41
+ const existing = existingRegistry(target);
42
+ if (existing) return existing;
43
+ const registry: PatchRegistry = new Map();
44
+ Object.defineProperty(target, ZENTUI_PROTOTYPE_PATCH_REGISTRY, {
45
+ value: registry,
46
+ configurable: true,
47
+ });
48
+ return registry;
49
+ }
50
+
51
+ function deactivateRecord(record: PatchRecord): void {
52
+ if (!record.registration) return;
53
+ record.registration.behavior = undefined;
54
+ record.registration = undefined;
55
+ }
56
+
57
+ function restorePredecessor(target: PatchTarget, record: PatchRecord): void {
58
+ if (target[record.method] !== record.wrapper) return;
59
+ if (record.predecessorDescriptor) {
60
+ Object.defineProperty(target, record.method, record.predecessorDescriptor);
61
+ } else {
62
+ delete target[record.method];
63
+ }
64
+ }
65
+
66
+ function installWrapper(target: PatchTarget, record: PatchRecord): void {
67
+ const descriptor = record.predecessorDescriptor;
68
+ if (descriptor && "value" in descriptor) {
69
+ Object.defineProperty(target, record.method, { ...descriptor, value: record.wrapper });
70
+ return;
71
+ }
72
+ Object.defineProperty(target, record.method, {
73
+ value: record.wrapper,
74
+ writable: true,
75
+ enumerable: descriptor?.enumerable ?? true,
76
+ configurable: true,
77
+ });
78
+ }
79
+
80
+ function createCleanup(
81
+ target: PatchTarget,
82
+ adapter: PrototypePatchAdapter,
83
+ registry: PatchRegistry,
84
+ record: PatchRecord,
85
+ token: symbol,
86
+ ): () => void {
87
+ let cleaned = false;
88
+ return () => {
89
+ if (cleaned) return;
90
+ cleaned = true;
91
+ if (record.registration?.token !== token) return;
92
+ deactivateRecord(record);
93
+
94
+ const current = registry.get(adapter);
95
+ if (current !== record) return;
96
+ restorePredecessor(target, record);
97
+ registry.delete(adapter);
98
+ if (registry.size === 0) delete target[ZENTUI_PROTOTYPE_PATCH_REGISTRY];
99
+ };
100
+ }
101
+
102
+ export function installPrototypePatch(
103
+ targetValue: object,
104
+ method: "render" | "invalidate",
105
+ adapter: PrototypePatchAdapter,
106
+ behavior: PatchBehavior,
107
+ ): () => void {
108
+ const target = targetValue as PatchTarget;
109
+ const registry = registryFor(target);
110
+ let record = registry.get(adapter);
111
+
112
+ if (!(record && record.method === method && target[method] === record.wrapper)) {
113
+ const displacedRecord = record;
114
+ const predecessorDescriptor = Object.getOwnPropertyDescriptor(target, method);
115
+ const predecessor = target[method];
116
+ if (typeof predecessor !== "function") {
117
+ if (registry.size === 0) delete target[ZENTUI_PROTOTYPE_PATCH_REGISTRY];
118
+ throw new TypeError(`Cannot patch ${method}: predecessor is not a function`);
119
+ }
120
+ const nextRecord: PatchRecord = {
121
+ method,
122
+ predecessor: predecessor as PrototypeMethod,
123
+ predecessorDescriptor,
124
+ wrapper: () => undefined,
125
+ };
126
+ const wrapper: PrototypeMethod = function zentuiPrototypeWrapper(
127
+ this: unknown,
128
+ ...args: unknown[]
129
+ ): unknown {
130
+ const activeBehavior = nextRecord.registration?.behavior;
131
+ return activeBehavior
132
+ ? activeBehavior({ predecessor: nextRecord.predecessor, receiver: this, args })
133
+ : Reflect.apply(nextRecord.predecessor, this, args);
134
+ };
135
+ nextRecord.wrapper = wrapper;
136
+ try {
137
+ installWrapper(target, nextRecord);
138
+ } catch (error) {
139
+ if (registry.size === 0) delete target[ZENTUI_PROTOTYPE_PATCH_REGISTRY];
140
+ throw error;
141
+ }
142
+ record = nextRecord;
143
+ registry.set(adapter, record);
144
+ if (displacedRecord && displacedRecord !== record) deactivateRecord(displacedRecord);
145
+ }
146
+
147
+ const token = Symbol(adapter);
148
+ record.registration = { token, behavior };
149
+ return createCleanup(target, adapter, registry, record, token);
150
+ }
151
+
152
+ export function removePrototypePatch(
153
+ targetValue: object,
154
+ method: "render" | "invalidate",
155
+ adapter: PrototypePatchAdapter,
156
+ ): void {
157
+ const target = targetValue as PatchTarget;
158
+ const registry = existingRegistry(target);
159
+ const record = registry?.get(adapter);
160
+ if (!registry || !record || record.method !== method) return;
161
+
162
+ deactivateRecord(record);
163
+ restorePredecessor(target, record);
164
+ registry.delete(adapter);
165
+ if (registry.size === 0) delete target[ZENTUI_PROTOTYPE_PATCH_REGISTRY];
166
+ }
@@ -0,0 +1,98 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+
4
+ export type RepositoryRootState = {
5
+ /** The cwd whose repository lookup produced this state. */
6
+ cwd: string;
7
+ root?: string;
8
+ };
9
+
10
+ export type RepositoryRootRequest = {
11
+ cwd: string;
12
+ generation: number;
13
+ };
14
+
15
+ /** Find the nearest ancestor containing either a .git directory or worktree .git file. */
16
+ export function findRepositoryRoot(cwd: string): string | undefined {
17
+ let current = resolve(cwd);
18
+ while (true) {
19
+ if (existsSync(join(current, ".git"))) return current;
20
+ const parent = dirname(current);
21
+ if (parent === current) return undefined;
22
+ current = parent;
23
+ }
24
+ }
25
+
26
+ export function updateRepositoryRootState(
27
+ cwd: string,
28
+ isRepository: boolean,
29
+ findRoot: (cwd: string) => string | undefined = findRepositoryRoot,
30
+ ): RepositoryRootState {
31
+ if (!isRepository) return { cwd };
32
+ try {
33
+ return { cwd, root: findRoot(cwd) };
34
+ } catch {
35
+ return { cwd };
36
+ }
37
+ }
38
+
39
+ /** Return only a current, still-present root produced for this exact cwd. */
40
+ export function repositoryRootForCwd(
41
+ state: RepositoryRootState | undefined,
42
+ cwd: string,
43
+ markerExists: (path: string) => boolean = existsSync,
44
+ ): string | undefined {
45
+ if (!state?.root || state.cwd !== cwd) return undefined;
46
+ try {
47
+ return markerExists(join(state.root, ".git")) ? state.root : undefined;
48
+ } catch {
49
+ return undefined;
50
+ }
51
+ }
52
+
53
+ /** Own repository-root request generations so returning to an older cwd cannot revive old state. */
54
+ export class RepositoryRootController {
55
+ private generation = 0;
56
+ private requestedCwd: string | undefined;
57
+ private state: RepositoryRootState | undefined;
58
+
59
+ request(cwd: string): RepositoryRootRequest {
60
+ if (cwd !== this.requestedCwd) {
61
+ this.generation += 1;
62
+ this.requestedCwd = cwd;
63
+ this.state = undefined;
64
+ }
65
+ return { cwd, generation: this.generation };
66
+ }
67
+
68
+ isCurrent(request: RepositoryRootRequest): boolean {
69
+ return request.cwd === this.requestedCwd && request.generation === this.generation;
70
+ }
71
+
72
+ update(
73
+ request: RepositoryRootRequest,
74
+ isRepository: boolean,
75
+ findRoot: (cwd: string) => string | undefined = findRepositoryRoot,
76
+ ): string | undefined {
77
+ if (!this.isCurrent(request)) return undefined;
78
+ this.state = updateRepositoryRootState(request.cwd, isRepository, findRoot);
79
+ return this.state.root;
80
+ }
81
+
82
+ cachedRootForCwd(cwd: string): string | undefined {
83
+ return this.state?.cwd === cwd ? this.state.root : undefined;
84
+ }
85
+
86
+ rootForCwd(
87
+ cwd: string,
88
+ markerExists: (path: string) => boolean = existsSync,
89
+ ): string | undefined {
90
+ return repositoryRootForCwd(this.state, cwd, markerExists);
91
+ }
92
+
93
+ reset(): void {
94
+ this.generation += 1;
95
+ this.requestedCwd = undefined;
96
+ this.state = undefined;
97
+ }
98
+ }