@selesai/code 0.9.14 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +6 -9
  3. package/dist/defaults/models.json +79 -29
  4. package/dist/defaults/settings.json +3 -7
  5. package/dist/extensions/package.json +3 -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/skills/design-references/SKILL.md +31 -0
  118. package/dist/skills/design-references/references/clickhouse/DESIGN.md +544 -0
  119. package/dist/themes/adventure.json +84 -0
  120. package/dist/themes/adwaita-dark.json +84 -0
  121. package/dist/themes/arcoiris.json +84 -0
  122. package/dist/themes/arthur.json +84 -0
  123. package/dist/themes/atom.json +84 -0
  124. package/dist/themes/aura.json +84 -0
  125. package/dist/themes/black-metal-bathory.json +84 -0
  126. package/dist/themes/black-metal-burzum.json +84 -0
  127. package/dist/themes/black-metal-khold.json +84 -0
  128. package/dist/themes/box.json +84 -0
  129. package/dist/themes/brogrammer.json +84 -0
  130. package/dist/themes/carbonfox.json +84 -0
  131. package/dist/themes/catppuccin-mocha.json +84 -0
  132. package/dist/themes/citruszest.json +84 -0
  133. package/dist/themes/cursor-dark.json +84 -0
  134. package/dist/themes/cutie-pro.json +84 -0
  135. package/dist/themes/dark-modern.json +84 -0
  136. package/dist/themes/dark-pastel.json +84 -0
  137. package/dist/themes/dimmed-monokai.json +84 -0
  138. package/dist/themes/doom-peacock.json +84 -0
  139. package/dist/themes/dracula-plus.json +84 -0
  140. package/dist/themes/dracula.json +80 -0
  141. package/dist/themes/earthsong.json +84 -0
  142. package/dist/themes/everforest-dark-hard.json +84 -0
  143. package/dist/themes/everforest-dark.json +80 -0
  144. package/dist/themes/fahrenheit.json +84 -0
  145. package/dist/themes/flatland.json +84 -0
  146. package/dist/themes/flexoki-dark.json +84 -0
  147. package/dist/themes/front-end-delight.json +84 -0
  148. package/dist/themes/fun-forrest.json +84 -0
  149. package/dist/themes/galizur.json +84 -0
  150. package/dist/themes/github-dark-colorblind.json +84 -0
  151. package/dist/themes/github-dark-high-contrast.json +84 -0
  152. package/dist/themes/glacier.json +84 -0
  153. package/dist/themes/gruber-darker.json +84 -0
  154. package/dist/themes/gruvbox-dark-hard.json +84 -0
  155. package/dist/themes/gruvbox-dark.json +84 -0
  156. package/dist/themes/gruvbox-material.json +84 -0
  157. package/dist/themes/guezwhoz.json +84 -0
  158. package/dist/themes/hacktober.json +84 -0
  159. package/dist/themes/hardcore.json +84 -0
  160. package/dist/themes/havn-skumring.json +84 -0
  161. package/dist/themes/ic-orange-ppl.json +84 -0
  162. package/dist/themes/iterm2-smoooooth.json +84 -0
  163. package/dist/themes/iterm2-tango-dark.json +84 -0
  164. package/dist/themes/japanesque.json +84 -0
  165. package/dist/themes/jellybeans.json +84 -0
  166. package/dist/themes/kanagawa-wave.json +84 -0
  167. package/dist/themes/kurokula.json +84 -0
  168. package/dist/themes/later-this-evening.json +84 -0
  169. package/dist/themes/lovelace.json +84 -0
  170. package/dist/themes/material-darker.json +84 -0
  171. package/dist/themes/matte-black.json +84 -0
  172. package/dist/themes/mellow.json +84 -0
  173. package/dist/themes/miasma.json +84 -0
  174. package/dist/themes/nord.json +80 -0
  175. package/dist/themes/nvim-dark.json +84 -0
  176. package/dist/themes/one-dark.json +80 -0
  177. package/dist/themes/popping-and-locking.json +84 -0
  178. package/dist/themes/rose-pine.json +80 -0
  179. package/dist/themes/sea-shells.json +84 -0
  180. package/dist/themes/sleepy-hollow.json +84 -0
  181. package/dist/themes/smyck.json +84 -0
  182. package/dist/themes/solarized-dark.json +80 -0
  183. package/dist/themes/tokyo-night.json +80 -0
  184. package/dist/themes/tomorrow-night-bright.json +84 -0
  185. package/dist/themes/tomorrow-night-burns.json +84 -0
  186. package/dist/themes/tomorrow-night.json +84 -0
  187. package/dist/themes/twilight.json +84 -0
  188. package/dist/themes/vague.json +84 -0
  189. package/dist/themes/vesper.json +84 -0
  190. package/dist/themes/xcode-dark-hc.json +84 -0
  191. package/docs/custom-provider.md +0 -1
  192. package/docs/plans/auto-model-routing-extension.md +490 -0
  193. package/package.json +3 -3
  194. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
  195. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
  196. package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
  197. package/dist/extensions/pi-powerline-footer/README.md +0 -513
  198. package/dist/extensions/pi-powerline-footer/banner.png +0 -0
  199. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
  200. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
  201. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
  202. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
  203. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
  204. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
  205. package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
  206. package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
  207. package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
  208. package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
  209. package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
  210. package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
  211. package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
  212. package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
  213. package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
  214. package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
  215. package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
  216. package/dist/extensions/pi-powerline-footer/package.json +0 -49
  217. package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
  218. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
  219. package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
  220. package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
  221. package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
  222. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
  223. package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
  224. package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
  225. package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
  226. package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
  227. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
  228. package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
  229. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
  230. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
  231. package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
  232. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
  233. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
  234. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
  235. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
  236. package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
  237. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
  238. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
  239. package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
  240. package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
  241. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
  242. package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
  243. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
  244. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
  245. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
  246. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
  247. package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
  248. package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
  249. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
  250. package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
  251. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
  252. package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
  253. package/dist/extensions/pi-powerline-footer/theme.json +0 -12
  254. package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
  255. package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
  256. package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
  257. package/dist/extensions/pi-powerline-footer/types.ts +0 -235
  258. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
  259. package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
  260. package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
  261. package/dist/extensions/preview-tools-disabled.test.ts +0 -37
  262. package/dist/extensions/preview-tools-disabled.ts +0 -11
  263. 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
+ }