tmux-ide 2.9.2 → 2.9.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 (267) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +121 -140
  3. package/bin/cli.js +2457 -727
  4. package/package.json +23 -8
  5. package/packages/contracts/src/desktop-host.ts +3 -0
  6. package/packages/contracts/src/saved-machines.ts +19 -0
  7. package/packages/contracts/src/terminal-replica.ts +1 -1
  8. package/packages/core/package.json +3 -2
  9. package/packages/core/src/terminal-delivery.ts +51 -3
  10. package/packages/core/src/terminal-fnv64-wasm.ts +4 -3
  11. package/packages/core/src/terminal-replica-hash-cache.ts +129 -31
  12. package/packages/core/src/terminal-replica-owned-row.ts +27 -0
  13. package/packages/core/src/terminal-replica.ts +13 -28
  14. package/packages/daemon/dist/command-center/daemon-fleet-facts-observer.js +96 -10
  15. package/packages/daemon/dist/command-center/resources/saved-machine-route.js +40 -1
  16. package/packages/daemon/dist/command-center/ws-events.js +5 -24
  17. package/packages/daemon/dist/lib/canonical-daemon-bootstrap.js +8 -0
  18. package/packages/daemon/dist/lib/canonical-daemon.js +28 -2
  19. package/packages/daemon/dist/lib/daemon-embed.js +1 -1
  20. package/packages/daemon/dist/lib/daemon-service-manager.js +122 -0
  21. package/packages/daemon/dist/lib/daemon-service-plan.js +127 -0
  22. package/packages/daemon/dist/lib/daemon-service.js +259 -0
  23. package/packages/daemon/dist/lib/install-origin.js +2 -0
  24. package/packages/daemon/dist/lib/local-fleet-request.js +9 -3
  25. package/packages/daemon/dist/lib/log.js +6 -10
  26. package/packages/daemon/dist/lib/managed-installer-update.js +67 -0
  27. package/packages/daemon/dist/lib/{tui-download-lock.js → private-operation-lock.js} +11 -11
  28. package/packages/daemon/dist/lib/remote-daemon-info.js +3 -2
  29. package/packages/daemon/dist/lib/remote-daemon-lifecycle.js +4 -4
  30. package/packages/daemon/dist/lib/remote-tmux-command.js +10 -0
  31. package/packages/daemon/dist/lib/runtime-namespace.js +28 -16
  32. package/packages/daemon/dist/lib/saved-machines.js +40 -2
  33. package/packages/daemon/dist/lib/ssh-daemon-transport.js +24 -9
  34. package/packages/daemon/dist/lib/tmux-server-owner.js +1 -1
  35. package/packages/daemon/dist/lib/tmux-server-registration.js +21 -3
  36. package/packages/daemon/dist/lib/tui-binary.js +2 -2
  37. package/packages/daemon/dist/lib/update.js +18 -0
  38. package/packages/daemon/dist/lib/workspace-multiplexer-verbs.js +24 -1
  39. package/packages/daemon/dist/machines.js +40 -4
  40. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +2 -2
  41. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
  42. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
  43. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +2 -2
  44. package/packages/daemon/dist/terminal/attachments/direct-websocket.js +4 -1
  45. package/packages/daemon/dist/terminal/attachments/native-runtime.js +22 -13
  46. package/packages/daemon/dist/terminal/mirror/mirror-service.js +101 -3
  47. package/packages/daemon/dist/terminal/mirror/registered-window-guard.js +62 -0
  48. package/packages/daemon/dist/terminal/mirror/session-channel.js +75 -4
  49. package/packages/daemon/dist/terminal/mirror/shared-window-index.js +45 -0
  50. package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +11 -1
  51. package/packages/daemon/dist/terminal/session-runtime/multiplexer-backend.js +1 -0
  52. package/packages/daemon/dist/terminal/session-runtime/registry.js +56 -7
  53. package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-executor.js +3 -1
  54. package/packages/daemon/dist/tui/integrations/claude-agent-teams.js +264 -0
  55. package/packages/daemon/dist/tui/mirror/dialog-model.js +2 -35
  56. package/packages/daemon/dist/tui/mirror/runtime/application-daemon-authority-owner.js +8 -0
  57. package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +20 -2
  58. package/packages/daemon/dist/tui/mirror/runtime/application-machine-agents.js +7 -1
  59. package/packages/daemon/dist/tui/mirror/runtime/application-machine-authority.js +59 -9
  60. package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +27 -19
  61. package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +23 -18
  62. package/packages/daemon/dist/tui/mirror/runtime/application-machine-startup.js +31 -2
  63. package/packages/daemon/dist/tui/mirror/runtime/application-palette-search-owner.js +7 -3
  64. package/packages/daemon/dist/tui/mirror/runtime/application-performance-log.js +3 -0
  65. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-input-ingress.js +146 -2
  66. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +201 -50
  67. package/packages/daemon/dist/tui/mirror/runtime/bounded-performance-record-writer.js +50 -10
  68. package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +6 -5
  69. package/packages/daemon/dist/tui/mirror/runtime/terminal-pane-input-router.js +3 -0
  70. package/packages/daemon/dist/tui/mirror/runtime/workspace-terminal-fast-lane.js +8 -2
  71. package/packages/daemon/dist/tui/mirror/terminal-text.js +35 -0
  72. package/packages/daemon/dist/tui/mirror/workspace/connection-feedback.js +5 -3
  73. package/packages/daemon/src/command-center/daemon-fleet-facts-observer.ts +100 -10
  74. package/packages/daemon/src/command-center/resources/saved-machine-route.ts +48 -1
  75. package/packages/daemon/src/command-center/ws-events.ts +5 -25
  76. package/packages/daemon/src/lib/canonical-daemon-bootstrap.ts +12 -0
  77. package/packages/daemon/src/lib/canonical-daemon.ts +44 -2
  78. package/packages/daemon/src/lib/daemon-embed.ts +1 -0
  79. package/packages/daemon/src/lib/daemon-service-manager.ts +152 -0
  80. package/packages/daemon/src/lib/daemon-service-plan.ts +153 -0
  81. package/packages/daemon/src/lib/daemon-service.ts +301 -0
  82. package/packages/daemon/src/lib/install-origin.ts +5 -0
  83. package/packages/daemon/src/lib/local-fleet-request.ts +19 -3
  84. package/packages/daemon/src/lib/log.ts +13 -9
  85. package/packages/daemon/src/lib/managed-installer-update.ts +79 -0
  86. package/packages/daemon/src/lib/{tui-download-lock.ts → private-operation-lock.ts} +15 -11
  87. package/packages/daemon/src/lib/remote-daemon-info.ts +3 -3
  88. package/packages/daemon/src/lib/remote-daemon-lifecycle.ts +4 -4
  89. package/packages/daemon/src/lib/remote-tmux-command.ts +12 -0
  90. package/packages/daemon/src/lib/runtime-namespace.ts +27 -18
  91. package/packages/daemon/src/lib/saved-machines.ts +40 -1
  92. package/packages/daemon/src/lib/ssh-daemon-transport.ts +25 -7
  93. package/packages/daemon/src/lib/tmux-server-owner.ts +1 -0
  94. package/packages/daemon/src/lib/tmux-server-registration.ts +21 -3
  95. package/packages/daemon/src/lib/tui-binary.ts +2 -2
  96. package/packages/daemon/src/lib/update.ts +24 -0
  97. package/packages/daemon/src/lib/workspace-multiplexer-verbs.ts +22 -0
  98. package/packages/daemon/src/machines.ts +53 -5
  99. package/packages/daemon/src/terminal/attachments/direct-websocket.ts +8 -2
  100. package/packages/daemon/src/terminal/attachments/native-runtime.ts +28 -13
  101. package/packages/daemon/src/terminal/mirror/mirror-service.ts +105 -3
  102. package/packages/daemon/src/terminal/mirror/registered-window-guard.ts +66 -0
  103. package/packages/daemon/src/terminal/mirror/session-channel.ts +92 -4
  104. package/packages/daemon/src/terminal/mirror/shared-window-index.ts +42 -0
  105. package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +12 -1
  106. package/packages/daemon/src/terminal/session-runtime/multiplexer-backend.ts +2 -0
  107. package/packages/daemon/src/terminal/session-runtime/registry.ts +70 -8
  108. package/packages/daemon/src/terminal/session-runtime/semantic-mutation-executor.ts +3 -1
  109. package/packages/daemon/src/tui/integrations/claude-agent-teams.ts +334 -0
  110. package/packages/daemon/src/tui/mirror/dialog-model.ts +2 -32
  111. package/packages/daemon/src/tui/mirror/runtime/application-daemon-authority-owner.ts +8 -0
  112. package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +19 -2
  113. package/packages/daemon/src/tui/mirror/runtime/application-machine-agents.ts +6 -1
  114. package/packages/daemon/src/tui/mirror/runtime/application-machine-authority.ts +60 -11
  115. package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +39 -33
  116. package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +31 -23
  117. package/packages/daemon/src/tui/mirror/runtime/application-machine-startup.ts +38 -2
  118. package/packages/daemon/src/tui/mirror/runtime/application-palette-search-owner.ts +7 -4
  119. package/packages/daemon/src/tui/mirror/runtime/application-performance-log.ts +3 -0
  120. package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +1 -1
  121. package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +47 -46
  122. package/packages/daemon/src/tui/mirror/runtime/application-terminal-input-ingress.ts +165 -2
  123. package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +207 -53
  124. package/packages/daemon/src/tui/mirror/runtime/bounded-performance-record-writer.ts +49 -9
  125. package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +6 -5
  126. package/packages/daemon/src/tui/mirror/runtime/terminal-pane-input-router.ts +4 -0
  127. package/packages/daemon/src/tui/mirror/runtime/workspace-terminal-fast-lane.ts +8 -3
  128. package/packages/daemon/src/tui/mirror/terminal-text.ts +32 -0
  129. package/packages/daemon/src/tui/mirror/workspace/connection-feedback.ts +6 -3
  130. package/packages/daemon-client/src/fleet-connection-status.ts +10 -1
  131. package/packages/daemon-client/src/workspace-client.ts +4 -1
  132. package/skill/SKILL.md +30 -30
  133. package/packages/contracts/src/__tests__/actions-contract.test.ts +0 -119
  134. package/packages/contracts/src/__tests__/agent-graph-overlay.test.ts +0 -428
  135. package/packages/contracts/src/__tests__/app-window-mutation.test.ts +0 -51
  136. package/packages/contracts/src/__tests__/app-window-state.test.ts +0 -34
  137. package/packages/contracts/src/__tests__/application-shell.test.ts +0 -645
  138. package/packages/contracts/src/__tests__/automation-operations.test.ts +0 -78
  139. package/packages/contracts/src/__tests__/causal-cell.test.ts +0 -47
  140. package/packages/contracts/src/__tests__/client-view-state.test.ts +0 -115
  141. package/packages/contracts/src/__tests__/cohesion-fixture.test.ts +0 -106
  142. package/packages/contracts/src/__tests__/commands.test.ts +0 -86
  143. package/packages/contracts/src/__tests__/control.test.ts +0 -154
  144. package/packages/contracts/src/__tests__/daemon-events.test.ts +0 -405
  145. package/packages/contracts/src/__tests__/daemon-resources.test.ts +0 -143
  146. package/packages/contracts/src/__tests__/daemon-wire.test.ts +0 -149
  147. package/packages/contracts/src/__tests__/desktop-host.test.ts +0 -269
  148. package/packages/contracts/src/__tests__/desktop-missions.test.ts +0 -215
  149. package/packages/contracts/src/__tests__/experience-conformance.test.ts +0 -153
  150. package/packages/contracts/src/__tests__/experience-kernel-boundary.test.ts +0 -113
  151. package/packages/contracts/src/__tests__/experience-shell.test.ts +0 -65
  152. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v2.json +0 -35
  153. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v3.json +0 -35
  154. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v4.json +0 -35
  155. package/packages/contracts/src/__tests__/fixtures/scoped-receipt.ts +0 -62
  156. package/packages/contracts/src/__tests__/fleet-agent-graph.test.ts +0 -231
  157. package/packages/contracts/src/__tests__/fleet-catalog.test.ts +0 -270
  158. package/packages/contracts/src/__tests__/fleet-lifecycle.test.ts +0 -62
  159. package/packages/contracts/src/__tests__/focus-overlay.test.ts +0 -110
  160. package/packages/contracts/src/__tests__/ide-config.test.ts +0 -64
  161. package/packages/contracts/src/__tests__/interaction-evidence.test.ts +0 -288
  162. package/packages/contracts/src/__tests__/interaction-receipt-v2.test.ts +0 -72
  163. package/packages/contracts/src/__tests__/issue-error.test.ts +0 -78
  164. package/packages/contracts/src/__tests__/mission-projections.test.ts +0 -186
  165. package/packages/contracts/src/__tests__/missions.test.ts +0 -272
  166. package/packages/contracts/src/__tests__/multiplexer-verbs.test.ts +0 -242
  167. package/packages/contracts/src/__tests__/pane-appearance.test.ts +0 -146
  168. package/packages/contracts/src/__tests__/pane-read-result.test.ts +0 -55
  169. package/packages/contracts/src/__tests__/pane-stream.test.ts +0 -536
  170. package/packages/contracts/src/__tests__/pane-widget-marker.test.ts +0 -327
  171. package/packages/contracts/src/__tests__/performance-metrics.test.ts +0 -73
  172. package/packages/contracts/src/__tests__/performance-qualification.test.ts +0 -137
  173. package/packages/contracts/src/__tests__/rich-card-widget.test.ts +0 -29
  174. package/packages/contracts/src/__tests__/session-runtime.test.ts +0 -512
  175. package/packages/contracts/src/__tests__/startup-readiness.test.ts +0 -409
  176. package/packages/contracts/src/__tests__/terminal-attachment-stream.test.ts +0 -72
  177. package/packages/contracts/src/__tests__/terminal-attachments.test.ts +0 -329
  178. package/packages/contracts/src/__tests__/terminal-delivery.test.ts +0 -88
  179. package/packages/contracts/src/__tests__/terminal-runtime-inventory.test.ts +0 -68
  180. package/packages/contracts/src/__tests__/terminals.test.ts +0 -87
  181. package/packages/contracts/src/__tests__/tmux-server-scope.test.ts +0 -166
  182. package/packages/contracts/src/__tests__/transport-contract-loading.test.ts +0 -91
  183. package/packages/contracts/src/__tests__/visual-tokens.test.ts +0 -224
  184. package/packages/contracts/src/__tests__/window-links.test.ts +0 -243
  185. package/packages/contracts/src/__tests__/workspace-changes-resource.test.ts +0 -317
  186. package/packages/contracts/src/__tests__/workspace-changes-view.test.ts +0 -122
  187. package/packages/contracts/src/__tests__/workspace-config.test.ts +0 -294
  188. package/packages/contracts/src/__tests__/workspace-files-resource.test.ts +0 -261
  189. package/packages/contracts/src/__tests__/workspace-files-tree.test.ts +0 -147
  190. package/packages/contracts/src/__tests__/workspace-pane-creation.test.ts +0 -249
  191. package/packages/contracts/src/__tests__/workspace-promotion.test.ts +0 -94
  192. package/packages/contracts/src/__tests__/workspace-resource-identity.test.ts +0 -90
  193. package/packages/contracts/src/daemon-resource-request.test.ts +0 -192
  194. package/packages/contracts/src/native-operation-identity.test.ts +0 -23
  195. package/packages/contracts/src/workspace-admission.test.ts +0 -21
  196. package/packages/contracts/src/workspace-catalog-resource.test.ts +0 -146
  197. package/packages/core/src/agent-provisioning.test.ts +0 -42
  198. package/packages/core/src/application-shell-session.test.ts +0 -105
  199. package/packages/core/src/interaction-evidence.test.ts +0 -134
  200. package/packages/core/src/interaction-flow.test.ts +0 -66
  201. package/packages/core/src/interaction-receipts.test.ts +0 -671
  202. package/packages/core/src/navigator.test.ts +0 -34
  203. package/packages/core/src/optimistic-projection-conformance.test.ts +0 -75
  204. package/packages/core/src/optimistic-projection.test.ts +0 -118
  205. package/packages/core/src/performance-metrics.test.ts +0 -121
  206. package/packages/core/src/performance-qualification.test.ts +0 -283
  207. package/packages/core/src/saved-machines.test.ts +0 -80
  208. package/packages/core/src/terminal-compact-cold.test.ts +0 -87
  209. package/packages/core/src/terminal-conformance.test.ts +0 -48
  210. package/packages/core/src/terminal-delivery-cache-lifetime.test.ts +0 -167
  211. package/packages/core/src/terminal-delivery-encode-cooperative.test.ts +0 -140
  212. package/packages/core/src/terminal-delivery-representation-hash.test.ts +0 -77
  213. package/packages/core/src/terminal-delivery.test.ts +0 -1471
  214. package/packages/core/src/terminal-fnv64-wasm.test.ts +0 -61
  215. package/packages/core/src/terminal-replica-cold.test.ts +0 -31
  216. package/packages/core/src/terminal-replica-cooperative.test.ts +0 -170
  217. package/packages/core/src/terminal-replica-hash-cache.test.ts +0 -283
  218. package/packages/core/src/terminal-replica.test.ts +0 -347
  219. package/packages/core/src/workspace-selection.test.ts +0 -63
  220. package/packages/daemon/dist/__tests__/support.js +0 -33
  221. package/packages/daemon/dist/lib/__tests__/fixtures/workspace-state-writer.js +0 -58
  222. package/packages/daemon/dist/lib/__tests__/installed-recovery-fixture.js +0 -400
  223. package/packages/daemon/dist/terminal/__tests__/MockPtyAdapter.js +0 -182
  224. package/packages/daemon/dist/terminal/mirror/__tests__/scripted-channel.js +0 -98
  225. package/packages/daemon/dist/terminal/mirror/__tests__/simulated-channel.js +0 -156
  226. package/packages/daemon/src/tui/mirror/__snapshots__/activity-surface-renderer.test.tsx.snap +0 -42
  227. package/packages/daemon/src/tui/mirror/__snapshots__/changes-terminal-surface-renderer.test.tsx.snap +0 -267
  228. package/packages/daemon/src/tui/mirror/__snapshots__/home-files-surface-renderer.test.tsx.snap +0 -267
  229. package/packages/daemon/src/tui/mirror/__snapshots__/missions-surface-renderer.test.tsx.snap +0 -134
  230. package/packages/daemon/src/tui/mirror/__snapshots__/recipes-gallery-renderer.test.tsx.snap +0 -141
  231. package/packages/daemon/src/tui/mirror/__snapshots__/shell-chrome-renderer.test.tsx.snap +0 -134
  232. package/packages/daemon/src/tui/mirror/runtime/__snapshots__/application-shell-view-renderer.test.tsx.snap +0 -87
  233. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/agent-terminal-canvas-renderer.test.tsx.snap +0 -348
  234. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/application-shell-renderer.test.tsx.snap +0 -134
  235. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/command-palette-surface-renderer.test.tsx.snap +0 -255
  236. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/pane-frame-renderer.test.tsx.snap +0 -525
  237. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/terminal-pane-header-polish-renderer.test.tsx.snap +0 -31
  238. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/workbench-shell-renderer.test.tsx.snap +0 -134
  239. package/packages/daemon-client/src/application-shell-session.test.ts +0 -196
  240. package/packages/daemon-client/src/automation-client.test.ts +0 -214
  241. package/packages/daemon-client/src/bootstrap-coordinator.test.ts +0 -145
  242. package/packages/daemon-client/src/connection-supervisor.test.ts +0 -109
  243. package/packages/daemon-client/src/direct-application-shell-transport.test.ts +0 -941
  244. package/packages/daemon-client/src/first-latest-coordinator.test.ts +0 -175
  245. package/packages/daemon-client/src/fleet-dial-scheduler.test.ts +0 -114
  246. package/packages/daemon-client/src/fleet-environments.test.ts +0 -40
  247. package/packages/daemon-client/src/generation-bound-store-timers.test.ts +0 -74
  248. package/packages/daemon-client/src/owner-action-client.test.ts +0 -245
  249. package/packages/daemon-client/src/pane-stream-client.test.ts +0 -2841
  250. package/packages/daemon-client/src/pane-stream-clock-calibration.test.ts +0 -207
  251. package/packages/daemon-client/src/pane-stream-loopback-benchmark.test.ts +0 -51
  252. package/packages/daemon-client/src/push-resource-session.test.ts +0 -509
  253. package/packages/daemon-client/src/resource-replica.test.ts +0 -186
  254. package/packages/daemon-client/src/runtime-resource-ledger.test.ts +0 -18
  255. package/packages/daemon-client/src/scoped-tmux-server-transport.test.ts +0 -114
  256. package/packages/daemon-client/src/terminal-fast-lane.test.ts +0 -835
  257. package/packages/daemon-client/src/tmux-server-client.test.ts +0 -279
  258. package/packages/daemon-client/src/tmux-server-interaction-events.test.ts +0 -230
  259. package/packages/daemon-client/src/workspace-catalog-v2.test.ts +0 -109
  260. package/packages/daemon-client/src/workspace-client-boundaries.test.ts +0 -59
  261. package/packages/daemon-client/src/workspace-client-operations.test.ts +0 -96
  262. package/packages/daemon-client/src/workspace-client.test.ts +0 -2912
  263. package/packages/daemon-client/src/workspace-event-supervisor.test.ts +0 -1086
  264. package/packages/daemon-client/src/workspace-pane-client.test.ts +0 -80
  265. package/packages/presentation/src/presentation-contract.test.ts +0 -63
  266. package/packages/tmux-bridge/src/runner.test.ts +0 -614
  267. package/packages/tmux-bridge/src/targeting.test.ts +0 -107
@@ -1,75 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- runOptimisticProjectionConformance,
4
- type OptimisticConformanceFixture,
5
- } from "./optimistic-projection-conformance.ts";
6
-
7
- interface Intent {
8
- readonly client: "react" | "opentui" | "sdk";
9
- readonly delta: number;
10
- }
11
- const options = {
12
- predict: (value: number, intent: Intent) => value + intent.delta,
13
- terminalHistoryLimit: 8,
14
- };
15
- const fixture: OptimisticConformanceFixture<number, Intent> = {
16
- name: "ordered-interleaving-receipts-and-generation",
17
- committed: { generation: "daemon-a", revision: 7, value: 10 },
18
- commands: [
19
- {
20
- type: "enqueue",
21
- operationId: "react-1",
22
- intent: { client: "react", delta: 2 },
23
- acceptedAtMs: 1,
24
- deadlineAtMs: 100,
25
- },
26
- {
27
- type: "enqueue",
28
- operationId: "tui-1",
29
- intent: { client: "opentui", delta: 3 },
30
- acceptedAtMs: 2,
31
- deadlineAtMs: 100,
32
- },
33
- {
34
- type: "enqueue",
35
- operationId: "sdk-1",
36
- intent: { client: "sdk", delta: -1 },
37
- acceptedAtMs: 3,
38
- deadlineAtMs: 100,
39
- },
40
- { type: "receipt", operationId: "tui-1", phase: "observed" },
41
- { type: "receipt", operationId: "tui-1", phase: "rejected" },
42
- {
43
- type: "replace",
44
- committed: { generation: "daemon-b", revision: 0, value: 12 },
45
- observedOperationIds: ["react-1"],
46
- nowMs: 10,
47
- },
48
- { type: "receipt", operationId: "sdk-1", phase: "observed" },
49
- { type: "receipt", operationId: "react-1", phase: "observed" },
50
- ],
51
- };
52
- describe("React/OpenTUI/SDK optimistic projection conformance", () => {
53
- it("produces identical ordered observations for each adapter label", () => {
54
- const traces = (["react", "opentui", "sdk"] as const).map(() =>
55
- runOptimisticProjectionConformance(fixture, options),
56
- );
57
- expect(traces[1]).toEqual(traces[0]);
58
- expect(traces[2]).toEqual(traces[0]);
59
- expect(traces[0]).toContainEqual(
60
- expect.objectContaining({ derived: 14, pending: ["react-1", "tui-1", "sdk-1"] }),
61
- );
62
- expect(traces[0]).toContainEqual(
63
- expect.objectContaining({
64
- generation: "daemon-b",
65
- revision: 0,
66
- derived: 11,
67
- pending: ["sdk-1"],
68
- }),
69
- );
70
- expect(traces[0]!.at(-1)).toEqual(expect.objectContaining({ derived: 12, pending: [] }));
71
- });
72
- it("does not republish duplicate or reordered terminal receipts", () => {
73
- expect(runOptimisticProjectionConformance(fixture, options)).toHaveLength(7);
74
- });
75
- });
@@ -1,118 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import {
4
- createOptimisticProjection,
5
- deriveOptimisticProjection,
6
- enqueueOptimisticOperation,
7
- expireOptimisticOperations,
8
- reconcileOptimisticOperation,
9
- replaceCommittedProjection,
10
- } from "./optimistic-projection.ts";
11
-
12
- interface CounterIntent {
13
- readonly client: string;
14
- readonly delta: number;
15
- }
16
-
17
- const options = {
18
- predict: (value: number, intent: CounterIntent) => value + intent.delta,
19
- terminalHistoryLimit: 4,
20
- };
21
-
22
- function enqueue(
23
- state: ReturnType<typeof createOptimisticProjection<number, CounterIntent>>,
24
- operationId: string,
25
- client: string,
26
- delta: number,
27
- ) {
28
- return enqueueOptimisticOperation(state, {
29
- operationId,
30
- intent: { client, delta },
31
- acceptedAtMs: 10,
32
- deadlineAtMs: 1_000,
33
- });
34
- }
35
-
36
- describe("committed/pending/derived projection", () => {
37
- it("applies interleaved client predictions in admission order", () => {
38
- let state = createOptimisticProjection<number, CounterIntent>({
39
- generation: "daemon-a",
40
- revision: 1,
41
- value: 10,
42
- });
43
- state = enqueue(state, "web-1", "web", 2);
44
- state = enqueue(state, "tui-1", "tui", 3);
45
- state = enqueue(state, "sdk-1", "sdk", -1);
46
- expect(deriveOptimisticProjection(state, options)).toBe(14);
47
- expect(state.pending.map(({ operationId }) => operationId)).toEqual([
48
- "web-1",
49
- "tui-1",
50
- "sdk-1",
51
- ]);
52
- });
53
-
54
- it("settles duplicate and reordered terminal receipts exactly once", () => {
55
- let state = createOptimisticProjection<number, CounterIntent>({
56
- generation: "daemon-a",
57
- revision: 1,
58
- value: 0,
59
- });
60
- state = enqueue(state, "web-1", "web", 4);
61
- state = enqueue(state, "tui-1", "tui", 5);
62
- state = reconcileOptimisticOperation(state, "tui-1", "observed", options);
63
- const settled = state;
64
- state = reconcileOptimisticOperation(state, "tui-1", "rejected", options);
65
- expect(state).toBe(settled);
66
- expect(deriveOptimisticProjection(state, options)).toBe(4);
67
-
68
- const terminalFirst = reconcileOptimisticOperation(
69
- createOptimisticProjection<number, CounterIntent>({
70
- generation: "daemon-a",
71
- revision: 1,
72
- value: 0,
73
- }),
74
- "late-admission",
75
- "observed",
76
- options,
77
- );
78
- expect(enqueue(terminalFirst, "late-admission", "sdk", 10)).toBe(terminalFirst);
79
- });
80
-
81
- it("retains safe pending intent across generations until observed or expired", () => {
82
- let state = createOptimisticProjection<number, CounterIntent>({
83
- generation: "daemon-a",
84
- revision: 8,
85
- value: 10,
86
- });
87
- state = enqueue(state, "web-1", "web", 2);
88
- state = enqueue(state, "tui-1", "tui", 3);
89
- state = replaceCommittedProjection(
90
- state,
91
- { generation: "daemon-b", revision: 0, value: 12 },
92
- { observedOperationIds: ["web-1"], nowMs: 20 },
93
- );
94
- expect(state.pending.map(({ operationId }) => operationId)).toEqual(["tui-1"]);
95
- expect(deriveOptimisticProjection(state, options)).toBe(15);
96
- expect(enqueue(state, "web-1", "web", 99)).toBe(state);
97
- state = expireOptimisticOperations(state, 1_000, options);
98
- expect(state.pending).toEqual([]);
99
- expect(deriveOptimisticProjection(state, options)).toBe(12);
100
- });
101
-
102
- it("rejects stale same-generation commits and duplicate operation admission", () => {
103
- let state = createOptimisticProjection<number, CounterIntent>({
104
- generation: "daemon-a",
105
- revision: 5,
106
- value: 10,
107
- });
108
- state = enqueue(state, "same", "web", 1);
109
- expect(enqueue(state, "same", "web", 99)).toBe(state);
110
- expect(
111
- replaceCommittedProjection(
112
- state,
113
- { generation: "daemon-a", revision: 4, value: 999 },
114
- { nowMs: 20 },
115
- ),
116
- ).toBe(state);
117
- });
118
- });
@@ -1,121 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { LocalPerformanceAggregator } from "./performance-metrics.ts";
4
-
5
- const authority = {
6
- daemonInstanceId: "00000000-0000-4000-8000-000000000001",
7
- workspaceName: "workspace",
8
- generation: "00000000-0000-4000-8000-000000000001",
9
- incarnation: "pane-a:1",
10
- } as const;
11
-
12
- function aggregator(capacity = 4): LocalPerformanceAggregator {
13
- return new LocalPerformanceAggregator({ source: "opentui", authority, sampleCapacity: capacity });
14
- }
15
-
16
- describe("LocalPerformanceAggregator", () => {
17
- it("is explicitly opt-in and disabled record paths are allocation-free no-ops", () => {
18
- const metrics = aggregator();
19
- metrics.recordFrame(Number.NaN);
20
- metrics.recordDirtyRows(-1);
21
- metrics.recordParse(Number.NaN);
22
- metrics.recordPaint(-1);
23
- metrics.recordQueueDepth(-1, -1);
24
- metrics.recordRevisionLag(-1);
25
- metrics.recordReseed();
26
- expect(metrics.enabled).toBe(false);
27
- expect(metrics.snapshot()).toBeNull();
28
- expect(metrics.disable()).toBe(false);
29
- });
30
-
31
- it("retains a fixed-capacity nearest-rank window and exact local summaries", () => {
32
- const metrics = aggregator(4);
33
- expect(metrics.enable()).toBe(true);
34
- expect(metrics.enable()).toBe(false);
35
- for (const value of [100, 2, 4, 8, 16]) metrics.recordParse(value);
36
- for (const value of [1, 3, 5]) metrics.recordDirtyRows(value);
37
- metrics.recordPaint(2.5);
38
- metrics.recordFrame(20);
39
- metrics.recordQueueDepth(3, 8);
40
- metrics.recordQueueDepth(5, 12);
41
- metrics.recordQueueDepth(2, 6);
42
- metrics.recordRevisionLag(4);
43
- metrics.recordRevisionLag(1);
44
- metrics.recordReseed();
45
-
46
- expect(metrics.snapshot()).toMatchObject({
47
- source: "opentui",
48
- activeFps: 50,
49
- dirtyRows: { count: 3, latest: 5, p50: 3, p95: 5, max: 5 },
50
- parseMs: { count: 4, latest: 16, p50: 4, p95: 16, max: 16 },
51
- paintMs: { count: 1, latest: 2.5, p50: 2.5, p95: 2.5, max: 2.5 },
52
- queueDepth: {
53
- current: 2,
54
- peak: 5,
55
- capacity: { current: 6, peak: 12 },
56
- },
57
- revisionLag: { current: 1, peak: 4 },
58
- reseeds: 1,
59
- });
60
- });
61
-
62
- it("resets measurements on a new activation without reusing the sequence", () => {
63
- const metrics = aggregator();
64
- metrics.enable();
65
- metrics.recordParse(3);
66
- const before = metrics.snapshot()!;
67
- expect(before.sampleSequence).toBe(1);
68
- expect(metrics.disable()).toBe(true);
69
- expect(metrics.snapshot()).toBeNull();
70
- expect(metrics.enable()).toBe(true);
71
- const reset = metrics.snapshot()!;
72
- expect(reset.sampleSequence).toBe(before.sampleSequence);
73
- expect(reset.parseMs.count).toBe(0);
74
- expect(reset.activeFps).toBeNull();
75
- expect(reset.reseeds).toBe(0);
76
- metrics.recordReseed();
77
- expect(metrics.snapshot()!.sampleSequence).toBeGreaterThan(before.sampleSequence);
78
- });
79
-
80
- it("advances sequence only for admitted events, never for snapshot reads", () => {
81
- const metrics = aggregator();
82
- metrics.enable();
83
- expect(metrics.snapshot()!.sampleSequence).toBe(0);
84
- expect(metrics.snapshot()!.sampleSequence).toBe(0);
85
- metrics.recordFrame(10);
86
- metrics.recordDirtyRows(1);
87
- metrics.recordParse(1);
88
- metrics.recordPaint(1);
89
- metrics.recordQueueDepth(0, 4);
90
- metrics.recordRevisionLag(0);
91
- metrics.recordReseed();
92
- expect(metrics.snapshot()!.sampleSequence).toBe(7);
93
- expect(metrics.snapshot()!.sampleSequence).toBe(7);
94
- });
95
-
96
- it("preserves unavailable capacities and revision lag instead of inventing zero", () => {
97
- const metrics = aggregator();
98
- metrics.enable();
99
- expect(metrics.snapshot()).toMatchObject({
100
- queueDepth: { capacity: { current: null, peak: null } },
101
- revisionLag: { current: null, peak: null },
102
- });
103
- metrics.recordQueueDepth(4);
104
- metrics.recordQueueDepth(1, 8);
105
- expect(metrics.snapshot()).toMatchObject({
106
- queueDepth: { current: 1, peak: 4, capacity: { current: 8, peak: null } },
107
- });
108
- });
109
-
110
- it("validates enabled observations without timing or I/O of its own", () => {
111
- const metrics = aggregator();
112
- metrics.enable();
113
- expect(() => metrics.recordFrame(0)).toThrow(/positive/u);
114
- expect(() => metrics.recordFrame(Number.MIN_VALUE)).toThrow(/finite frame rate/u);
115
- expect(() => metrics.recordDirtyRows(1.5)).toThrow(/integer/u);
116
- expect(() => metrics.recordParse(Number.NaN)).toThrow(/finite/u);
117
- expect(() => metrics.recordPaint(-1)).toThrow(/non-negative/u);
118
- expect(() => metrics.recordQueueDepth(2, 1)).toThrow(/capacity/u);
119
- expect(() => metrics.recordRevisionLag(-1)).toThrow(/non-negative/u);
120
- });
121
- });
@@ -1,283 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import {
4
- PERFORMANCE_STAGE_ORDER,
5
- type ClientConvergenceObservationV1,
6
- type MutationQualificationAcceptanceV1,
7
- type MutationTerminalOutcomeV1,
8
- type PerformanceTraceV1,
9
- } from "@tmux-ide/contracts";
10
-
11
- import {
12
- createStateConvergenceIdentity,
13
- deterministicPercentile,
14
- evaluateMutationOutcomes,
15
- evaluatePerformanceBudget,
16
- evaluateQueueBounds,
17
- evaluateQueuePlateau,
18
- evaluateSlowClientIsolation,
19
- evaluateStateConvergence,
20
- } from "./performance-qualification.ts";
21
- import { blankTerminalReplicaSnapshot, hashTerminalReplicaSnapshot } from "./terminal-replica.ts";
22
-
23
- const generation = "00000000-0000-4000-8000-000000000001";
24
-
25
- function trace(index: number, durationMicros: number): PerformanceTraceV1 {
26
- return {
27
- version: 1,
28
- traceId: `00000000-0000-4000-8000-${String(index).padStart(12, "0")}`,
29
- scenario: "leading-edge-input",
30
- stages: PERFORMANCE_STAGE_ORDER.map((stage, stageIndex) => {
31
- const local = stage === "input" || stage === "paint";
32
- const startedAtMicros =
33
- stage === "input"
34
- ? 1_000_000
35
- : stage === "paint"
36
- ? 1_000_000 + durationMicros - 100
37
- : stageIndex * 10_000;
38
- return {
39
- version: 1,
40
- stage,
41
- processId: local ? "web-a" : "daemon-a",
42
- clockId: local ? "browser" : "node",
43
- clockKind: local ? "performance-now" : "hrtime",
44
- startedAtMicros,
45
- endedAtMicros: startedAtMicros + (local ? 100 : stageIndex + 1_000),
46
- };
47
- }),
48
- localInputToPaint: {
49
- version: 1,
50
- processId: "web-a",
51
- clockId: "browser",
52
- clockKind: "performance-now",
53
- startedAtMicros: 1_000_000,
54
- endedAtMicros: 1_000_000 + durationMicros,
55
- },
56
- };
57
- }
58
-
59
- describe("performance qualification", () => {
60
- it("uses deterministic nearest-rank percentiles and the exact 60 Hz budget", () => {
61
- const values = Array.from({ length: 20 }, (_, index) => index + 1);
62
- expect(deterministicPercentile(values, 0.95)).toBe(19);
63
- expect(deterministicPercentile([...values].reverse(), 0.95)).toBe(19);
64
- expect(() => deterministicPercentile([1, Number.NaN], 0.95)).toThrow(/finite/u);
65
-
66
- const atBudget = evaluatePerformanceBudget(
67
- Array.from({ length: 20 }, (_, index) => trace(index + 1, index === 19 ? 30_000 : 16_670)),
68
- );
69
- expect(atBudget.inputToPaint.p95Ms).toBe(16.67);
70
- expect(atBudget.passed).toBe(true);
71
- expect(evaluatePerformanceBudget([trace(99, 16_669)]).passed).toBe(true);
72
- const overBudget = evaluatePerformanceBudget(
73
- Array.from({ length: 20 }, (_, index) => trace(index + 21, index < 19 ? 16_671 : 30_000)),
74
- );
75
- expect(overBudget.passed).toBe(false);
76
- });
77
-
78
- it("summarizes each stage from local spans and is input-order invariant", () => {
79
- const base = trace(1, 8_000);
80
- const crossClock: PerformanceTraceV1 = {
81
- ...base,
82
- stages: base.stages.map((span, index) =>
83
- index === 1
84
- ? { ...span, startedAtMicros: 9_000_000_000, endedAtMicros: 9_000_001_000 }
85
- : span,
86
- ),
87
- };
88
- const forward = [crossClock, trace(2, 12_000), trace(3, 10_000)];
89
- expect(evaluatePerformanceBudget(forward)).toEqual(
90
- evaluatePerformanceBudget([...forward].reverse()),
91
- );
92
- expect(evaluatePerformanceBudget(forward).stages.tmux.p95Ms).toBe(1.001);
93
- expect(evaluatePerformanceBudget(forward).stages.paint.p95Ms).toBe(0.1);
94
- });
95
-
96
- it("preserves the canonical terminal hash and converges 2/4/8 clients", () => {
97
- const snapshot = blankTerminalReplicaSnapshot(4, 2);
98
- const canonicalStateHash = hashTerminalReplicaSnapshot(snapshot);
99
- const expected = createStateConvergenceIdentity(generation, "pane-a:1", 42, canonicalStateHash);
100
- expect(expected.stateHash).toBe(canonicalStateHash);
101
- expect(expected.hashAlgorithm).toBe("fnv1a64-v1");
102
- expect(() =>
103
- createStateConvergenceIdentity(generation, "pane-a:1", 42, "not-a-hash"),
104
- ).toThrow();
105
- for (const count of [2, 4, 8]) {
106
- const observations: ClientConvergenceObservationV1[] = Array.from(
107
- { length: count },
108
- (_, index) => ({
109
- version: 1,
110
- clientId: `client-${index}`,
111
- disposition: "healthy",
112
- identity: expected,
113
- }),
114
- );
115
- const result = evaluateStateConvergence(expected, observations.reverse());
116
- expect(result.converged).toBe(true);
117
- expect(result.matchingClientIds).toEqual(
118
- Array.from({ length: count }, (_, index) => `client-${index}`).sort(),
119
- );
120
- }
121
-
122
- const divergent = createStateConvergenceIdentity(
123
- generation,
124
- "pane-a:1",
125
- 43,
126
- hashTerminalReplicaSnapshot(blankTerminalReplicaSnapshot(5, 2)),
127
- );
128
- expect(
129
- evaluateStateConvergence(expected, [
130
- { version: 1, clientId: "slow", disposition: "slow", identity: divergent },
131
- { version: 1, clientId: "healthy", disposition: "healthy", identity: expected },
132
- ]),
133
- ).toMatchObject({ converged: true, excludedClientIds: ["slow"] });
134
- const newerIncarnation = createStateConvergenceIdentity(
135
- generation,
136
- "pane-a:2",
137
- 42,
138
- canonicalStateHash,
139
- );
140
- expect(
141
- evaluateStateConvergence(expected, [
142
- { version: 1, clientId: "healthy", disposition: "healthy", identity: newerIncarnation },
143
- ]),
144
- ).toMatchObject({ converged: false, divergentClientIds: ["healthy"] });
145
- });
146
-
147
- it("qualifies bounded slow-client queues and healthy-client p95 isolation", () => {
148
- expect(
149
- evaluateQueueBounds([
150
- {
151
- version: 1,
152
- clientId: "slow",
153
- disposition: "slow",
154
- queue: "terminal",
155
- sampleOrdinal: 0,
156
- depthItems: 32,
157
- capacityItems: 32,
158
- bytes: 4_096,
159
- capacityBytes: 4_096,
160
- coalesced: 100,
161
- dropped: 2,
162
- },
163
- ]),
164
- ).toEqual({ bounded: true, maxDepthItems: 32, maxBytes: 4096, coalesced: 100, dropped: 2 });
165
-
166
- const baseline = [trace(1, 10_000), trace(2, 11_000)];
167
- expect(
168
- evaluateSlowClientIsolation(baseline, [trace(3, 10_500), trace(4, 11_500)], 0.5),
169
- ).toEqual({
170
- passed: true,
171
- baselineP95Ms: 11,
172
- contendedP95Ms: 11.5,
173
- regressionMs: 0.5,
174
- allowedRegressionMs: 0.5,
175
- });
176
-
177
- const queueSample = (sampleOrdinal: number, depthItems: number, bytes: number) => ({
178
- version: 1 as const,
179
- clientId: "slow",
180
- disposition: "slow" as const,
181
- queue: "terminal" as const,
182
- sampleOrdinal,
183
- depthItems,
184
- capacityItems: 32,
185
- bytes,
186
- capacityBytes: 4_096,
187
- coalesced: 0,
188
- dropped: 0,
189
- });
190
- expect(
191
- evaluateQueuePlateau([
192
- queueSample(0, 8, 1_024),
193
- queueSample(1, 8, 1_024),
194
- queueSample(2, 8, 1_024),
195
- queueSample(3, 8, 1_024),
196
- ]),
197
- ).toMatchObject({ plateaued: true, itemSlopePerSample: 0, byteSlopePerSample: 0 });
198
- expect(
199
- evaluateQueuePlateau([
200
- queueSample(0, 1, 100),
201
- queueSample(1, 2, 200),
202
- queueSample(2, 3, 300),
203
- queueSample(3, 4, 400),
204
- ]),
205
- ).toMatchObject({ plateaued: false, itemSlopePerSample: 1, byteSlopePerSample: 100 });
206
- expect(() =>
207
- evaluateQueuePlateau([
208
- queueSample(0, 1, 100),
209
- queueSample(1, 2, 200),
210
- queueSample(1, 3, 300),
211
- queueSample(3, 4, 400),
212
- ]),
213
- ).toThrow(/ordinals must increase/u);
214
- });
215
-
216
- it("rejects mutation limbo, duplicate terminals and unknown outcomes", () => {
217
- const accepted = (index: number): MutationQualificationAcceptanceV1 => ({
218
- version: 1,
219
- mutationId: `10000000-0000-4000-8000-${String(index).padStart(12, "0")}`,
220
- processId: "daemon-a",
221
- clockId: "node-hrtime",
222
- clockKind: "hrtime",
223
- acceptedAtMicros: index * 100,
224
- deadlineAtMicros: index * 100 + 1_000,
225
- });
226
- const outcome = (
227
- acceptance: MutationQualificationAcceptanceV1,
228
- status: "observed" | "rejected" | "timed-out",
229
- ): MutationTerminalOutcomeV1 => ({
230
- version: 1,
231
- mutationId: acceptance.mutationId,
232
- processId: acceptance.processId,
233
- clockId: acceptance.clockId,
234
- clockKind: acceptance.clockKind,
235
- occurredAtMicros:
236
- status === "timed-out" ? acceptance.deadlineAtMicros : acceptance.acceptedAtMicros + 1,
237
- status,
238
- ...(status === "observed"
239
- ? {
240
- identity: createStateConvergenceIdentity(
241
- generation,
242
- "pane-a:1",
243
- 1,
244
- hashTerminalReplicaSnapshot(blankTerminalReplicaSnapshot(1, 1)),
245
- ),
246
- }
247
- : { reason: status }),
248
- });
249
- const a = accepted(1);
250
- const b = accepted(2);
251
- expect(evaluateMutationOutcomes([a, b], [outcome(a, "observed")])).toMatchObject({
252
- complete: false,
253
- limboMutationIds: [b.mutationId],
254
- });
255
- expect(
256
- evaluateMutationOutcomes([a, b], [outcome(a, "observed"), outcome(b, "timed-out")]),
257
- ).toMatchObject({ complete: true, terminalCount: 2 });
258
- expect(
259
- evaluateMutationOutcomes([a], [outcome(a, "rejected"), outcome(a, "timed-out")]),
260
- ).toMatchObject({ complete: false, duplicateMutationIds: [a.mutationId] });
261
- const unknown = accepted(3);
262
- expect(evaluateMutationOutcomes([a], [outcome(unknown, "rejected")])).toMatchObject({
263
- complete: false,
264
- limboMutationIds: [a.mutationId],
265
- unknownMutationIds: [unknown.mutationId],
266
- });
267
- expect(
268
- evaluateMutationOutcomes(
269
- [a],
270
- [{ ...outcome(a, "observed"), occurredAtMicros: a.deadlineAtMicros + 1 }],
271
- ),
272
- ).toMatchObject({ complete: false, lateMutationIds: [a.mutationId] });
273
- expect(
274
- evaluateMutationOutcomes([a], [{ ...outcome(a, "rejected"), clockId: "other-clock" }]),
275
- ).toMatchObject({ complete: false, clockDomainMismatchMutationIds: [a.mutationId] });
276
- expect(
277
- evaluateMutationOutcomes(
278
- [a],
279
- [{ ...outcome(a, "timed-out"), occurredAtMicros: a.deadlineAtMicros - 1 }],
280
- ),
281
- ).toMatchObject({ complete: false, prematureTimeoutMutationIds: [a.mutationId] });
282
- });
283
- });
@@ -1,80 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { SavedMachineRegistrySchema } from "@tmux-ide/contracts";
3
- import { changeSavedMachines, machineResourceKey } from "./saved-machines.ts";
4
- const id = "a1ea9939-7496-4a45-bae2-7e611aecc011";
5
- const other = "a1ea9939-7496-4a45-bae2-7e611aecc012";
6
- const machine = { id, label: "Build host", sshTarget: "dev@build", enabled: true };
7
- const initial = { version: 1 as const, machines: [machine] };
8
- describe("saved machine identity", () => {
9
- it("preserves identity through edits and leaves the original immutable", () => {
10
- const result = changeSavedMachines(initial, {
11
- type: "update",
12
- id,
13
- patch: { label: "New name", enabled: false },
14
- });
15
- expect(result.machines[0]).toEqual({ ...machine, label: "New name", enabled: false });
16
- expect(initial.machines[0]).toEqual(machine);
17
- expect(changeSavedMachines(result, { type: "remove", id }).machines).toEqual([]);
18
- expect(() => changeSavedMachines(result, { type: "remove", id: other })).toThrow("not found");
19
- });
20
- it("rejects collisions, reserved Local labels, credentials, unknown versions and excess records", () => {
21
- for (const extra of [
22
- { ...machine },
23
- { ...machine, id: other, label: "BUILD HOST" },
24
- { ...machine, id: other, label: "Local" },
25
- ]) {
26
- expect(() => changeSavedMachines(initial, { type: "add", machine: extra })).toThrow();
27
- }
28
- expect(() => SavedMachineRegistrySchema.parse({ ...initial, version: 2 })).toThrow();
29
- expect(() =>
30
- SavedMachineRegistrySchema.parse({
31
- version: 1,
32
- machines: Array.from({ length: 65 }, (_, index) => ({
33
- ...machine,
34
- id: `a1ea9939-7496-4a45-bae2-${index.toString(16).padStart(12, "0")}`,
35
- label: `Host ${index}`,
36
- })),
37
- }),
38
- ).toThrow();
39
- for (const field of ["password", "privateKey", "token"]) {
40
- expect(() =>
41
- SavedMachineRegistrySchema.parse({
42
- ...initial,
43
- machines: [{ ...machine, [field]: "secret" }],
44
- }),
45
- ).toThrow();
46
- }
47
- expect(() => changeSavedMachines(initial, { type: "remove", id: "local" })).toThrow();
48
- });
49
- it("accepts SSH aliases and destinations without allowing command or credential syntax", () => {
50
- for (const sshTarget of ["work", "user@server.example", "user@[::1]"]) {
51
- expect(
52
- SavedMachineRegistrySchema.parse({ ...initial, machines: [{ ...machine, sshTarget }] })
53
- .machines[0]?.sshTarget,
54
- ).toBe(sshTarget);
55
- }
56
- for (const sshTarget of [
57
- "-oProxyCommand=evil",
58
- "host;cmd",
59
- "host\ncmd",
60
- "ssh://host",
61
- "user:password@host",
62
- "host command",
63
- "$(command)",
64
- ]) {
65
- expect(() =>
66
- SavedMachineRegistrySchema.parse({ ...initial, machines: [{ ...machine, sshTarget }] }),
67
- ).toThrow();
68
- }
69
- });
70
- it("namespaces identical tmux IDs across machines and resource kinds", () => {
71
- const keys = [
72
- machineResourceKey("local", "pane", "%1"),
73
- machineResourceKey(id, "pane", "%1"),
74
- machineResourceKey(other, "pane", "%1"),
75
- machineResourceKey(id, "session", "%1"),
76
- ];
77
- expect(new Set(keys).size).toBe(4);
78
- expect(machineResourceKey(id.toUpperCase(), "pane", "%1")).toBe(keys[1]);
79
- });
80
- });