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,294 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { WorkspaceConfigV1SchemaZ } from "../workspace-config.ts";
3
- import type { WorkspaceAppLayoutNode } from "../workspace-config.ts";
4
-
5
- describe("WorkspaceConfigV1SchemaZ", () => {
6
- it("accepts a minimal versioned workspace", () => {
7
- expect(WorkspaceConfigV1SchemaZ.parse({ version: 1 })).toEqual({ version: 1 });
8
- });
9
-
10
- it("accepts unique workspace-safe pane ids and rejects duplicates", () => {
11
- expect(
12
- WorkspaceConfigV1SchemaZ.parse({
13
- version: 1,
14
- terminal: { rows: [{ panes: [{ id: "agent-1" }, { id: "shell" }] }] },
15
- }).terminal?.rows[0]?.panes.map((pane) => pane.id),
16
- ).toEqual(["agent-1", "shell"]);
17
- expect(() =>
18
- WorkspaceConfigV1SchemaZ.parse({
19
- version: 1,
20
- terminal: {
21
- rows: [{ panes: [{ id: "duplicate" }] }, { panes: [{ id: "duplicate" }] }],
22
- },
23
- }),
24
- ).toThrow(/Duplicate pane id/u);
25
- });
26
-
27
- it("keeps harnesses, adapters, commands, and models provider-neutral", () => {
28
- const result = WorkspaceConfigV1SchemaZ.parse({
29
- version: 1,
30
- name: "Mixed agents",
31
- terminal: {
32
- rows: [
33
- {
34
- size: "70%",
35
- panes: [{ title: "Lead", command: "claude", focus: true }],
36
- },
37
- ],
38
- theme: { accent: "colour75" },
39
- },
40
- app: {
41
- views: [
42
- { id: "home", panel: "home" },
43
- { id: "terminal", title: "IDE", panel: "terminals" },
44
- { id: "mission-board", panel: "missions" },
45
- ],
46
- },
47
- harnesses: {
48
- claude: { adapter: "claude-code", command: ["claude"] },
49
- codex: { adapter: "codex", command: ["codex", "--ask-for-approval", "never"] },
50
- droid: { adapter: "droid", command: "droid" },
51
- custom: {
52
- adapter: "acme/custom-v4",
53
- command: ["my-review-agent", "--interactive"],
54
- env: { REVIEW_MODE: "strict" },
55
- },
56
- },
57
- agents: {
58
- lead: { harness: "claude", model: "claude-fable-5", role: "manager" },
59
- worker: { harness: "codex", model: "gpt-5.5", role: "implementer" },
60
- researcher: { harness: "droid", model: "anything/new", role: "researcher" },
61
- reviewer: { harness: "custom", role: "reviewer" },
62
- },
63
- missions: {
64
- manager: "lead",
65
- workers: ["worker", "researcher"],
66
- reviewer: "reviewer",
67
- isolation: "worktree",
68
- max_concurrent_tasks: 2,
69
- },
70
- });
71
-
72
- expect(result.harnesses?.custom?.adapter).toBe("acme/custom-v4");
73
- expect(result.agents?.researcher?.model).toBe("anything/new");
74
- });
75
-
76
- it("rejects unknown keys at every object boundary", () => {
77
- expect(() => WorkspaceConfigV1SchemaZ.parse({ version: 1, versoin: 1 })).toThrow();
78
- expect(() =>
79
- WorkspaceConfigV1SchemaZ.parse({
80
- version: 1,
81
- harnesses: { custom: { adapter: "generic", command: ["agent"], typo: true } },
82
- }),
83
- ).toThrow();
84
- expect(() =>
85
- WorkspaceConfigV1SchemaZ.parse({
86
- version: 1,
87
- terminal: { rows: [{ panes: [{ command: "shell", task: "legacy metadata" }] }] },
88
- }),
89
- ).toThrow();
90
- });
91
-
92
- it("rejects invalid panel, role, isolation, and concurrency values", () => {
93
- const invalidValues = [
94
- { version: 1, app: { views: [{ id: "bad", panel: "unknown" }] } },
95
- {
96
- version: 1,
97
- harnesses: { h: { adapter: "custom", command: "agent" } },
98
- agents: { a: { harness: "h", role: "lead" } },
99
- },
100
- { version: 1, missions: { isolation: "container" } },
101
- { version: 1, missions: { max_concurrent_tasks: 0 } },
102
- { version: 1, missions: { max_concurrent_tasks: 1.5 } },
103
- ];
104
-
105
- for (const value of invalidValues) {
106
- expect(WorkspaceConfigV1SchemaZ.safeParse(value).success).toBe(false);
107
- }
108
- });
109
-
110
- it("rejects broken harness and mission agent references with useful paths", () => {
111
- const result = WorkspaceConfigV1SchemaZ.safeParse({
112
- version: 1,
113
- agents: {
114
- worker: { harness: "missing-harness", role: "implementer" },
115
- },
116
- missions: {
117
- manager: "missing-manager",
118
- workers: ["worker", "missing-worker"],
119
- reviewer: "missing-reviewer",
120
- },
121
- });
122
-
123
- expect(result.success).toBe(false);
124
- if (result.success) return;
125
- expect(result.error.issues.map((issue) => issue.path.join("."))).toEqual(
126
- expect.arrayContaining([
127
- "agents.worker.harness",
128
- "missions.manager",
129
- "missions.workers.1",
130
- "missions.reviewer",
131
- ]),
132
- );
133
- });
134
-
135
- it("requires unique stable view IDs", () => {
136
- const result = WorkspaceConfigV1SchemaZ.safeParse({
137
- version: 1,
138
- app: {
139
- views: [
140
- { id: "ide", panel: "terminals" },
141
- { id: "ide", panel: "files" },
142
- ],
143
- },
144
- });
145
-
146
- expect(result.success).toBe(false);
147
- if (result.success) return;
148
- expect(result.error.issues[0]?.message).toContain("Duplicate view id");
149
- });
150
-
151
- it("accepts composite split/tab application views while preserving shorthand views", () => {
152
- const result = WorkspaceConfigV1SchemaZ.parse({
153
- version: 1,
154
- app: {
155
- views: [
156
- { id: "home", panel: "home" },
157
- {
158
- id: "ide",
159
- title: "IDE",
160
- layout: {
161
- type: "split",
162
- id: "ide-root",
163
- direction: "horizontal",
164
- weights: [72, 28],
165
- children: [
166
- { type: "panel", id: "terminal-main", panel: "terminals", min_size: 40 },
167
- {
168
- type: "tabs",
169
- id: "inspection-dock",
170
- active: "files-tab",
171
- children: [
172
- { type: "panel", id: "files-tab", panel: "files", min_size: 24 },
173
- { type: "panel", id: "diff-tab", panel: "diff", min_size: 24 },
174
- ],
175
- },
176
- ],
177
- },
178
- },
179
- ],
180
- },
181
- });
182
-
183
- expect(result.app?.views[0]).toEqual({ id: "home", panel: "home" });
184
- expect(result.app?.views[1]).toMatchObject({ id: "ide", title: "IDE" });
185
- });
186
-
187
- it("rejects invalid composite layout trees with precise paths", () => {
188
- const cases = [
189
- {
190
- value: {
191
- version: 1,
192
- app: {
193
- views: [
194
- {
195
- id: "bad",
196
- layout: {
197
- type: "split",
198
- id: "root",
199
- direction: "horizontal",
200
- weights: [1],
201
- children: [
202
- { type: "panel", id: "a", panel: "files" },
203
- { type: "panel", id: "b", panel: "diff" },
204
- ],
205
- },
206
- },
207
- ],
208
- },
209
- },
210
- path: "app.views.0.layout.weights",
211
- },
212
- {
213
- value: {
214
- version: 1,
215
- app: {
216
- views: [
217
- {
218
- id: "bad",
219
- layout: {
220
- type: "tabs",
221
- id: "tabs",
222
- active: "missing",
223
- children: [{ type: "panel", id: "a", panel: "files" }],
224
- },
225
- },
226
- ],
227
- },
228
- },
229
- path: "app.views.0.layout.active",
230
- },
231
- {
232
- value: {
233
- version: 1,
234
- app: {
235
- views: [
236
- {
237
- id: "bad",
238
- layout: {
239
- type: "split",
240
- id: "root",
241
- direction: "vertical",
242
- children: [
243
- { type: "panel", id: "dup", panel: "files" },
244
- { type: "panel", id: "dup", panel: "diff" },
245
- ],
246
- },
247
- },
248
- ],
249
- },
250
- },
251
- path: "app.views.0.layout.children.1.id",
252
- },
253
- ];
254
-
255
- for (const item of cases) {
256
- const result = WorkspaceConfigV1SchemaZ.safeParse(item.value);
257
- expect(result.success).toBe(false);
258
- if (!result.success) {
259
- expect(result.error.issues.map((issue) => issue.path.join("."))).toContain(item.path);
260
- }
261
- }
262
- });
263
-
264
- it("rejects composite layouts beyond the depth and node-count bounds", () => {
265
- let deep: WorkspaceAppLayoutNode = { type: "panel", id: "deep-leaf", panel: "files" };
266
- for (let index = 0; index < 8; index += 1) {
267
- deep = { type: "tabs", id: `deep-${index}`, children: [deep] };
268
- }
269
-
270
- let nextId = 0;
271
- const broad = (depth: number): WorkspaceAppLayoutNode => {
272
- const id = `node-${nextId++}`;
273
- if (depth === 0) return { type: "panel", id, panel: "files" };
274
- return {
275
- type: "split",
276
- id,
277
- direction: "horizontal",
278
- children: Array.from({ length: 4 }, () => broad(depth - 1)),
279
- };
280
- };
281
-
282
- const issuesFor = (layout: WorkspaceAppLayoutNode) => {
283
- const result = WorkspaceConfigV1SchemaZ.safeParse({
284
- version: 1,
285
- app: { views: [{ id: "bounded", layout }] },
286
- });
287
- expect(result.success).toBe(false);
288
- return result.success ? [] : result.error.issues.map((issue) => issue.message);
289
- };
290
-
291
- expect(issuesFor(deep)).toContain("Composite view layout must not be deeper than 8 nodes");
292
- expect(issuesFor(broad(3))).toContain("Composite view layout must not exceed 64 nodes");
293
- });
294
- });
@@ -1,261 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- WORKSPACE_FILE_PREVIEW_RESOURCE_VERSION,
4
- WORKSPACE_FILES_CATALOG_RESOURCE_VERSION,
5
- WorkspaceFileEntrySchemaZ,
6
- WorkspaceFilePreviewEnvelopeV1SchemaZ,
7
- WorkspaceFilesCatalogEnvelopeV1SchemaZ,
8
- WorkspaceFilesCatalogResourceV1SchemaZ,
9
- } from "../workspace-files-resource.ts";
10
-
11
- const daemon = {
12
- protocolVersion: 1,
13
- productVersion: "2.8.0",
14
- instanceId: "9bcf33b0-c837-4a94-b5e8-c0977f54464f",
15
- startedAt: "2026-07-21T12:00:00.000Z",
16
- };
17
-
18
- const fileId = (token: string) => `file.${token.padEnd(16, "0")}`;
19
- const filesRev = (token: string) => `files-rev.${token.padEnd(16, "0")}`;
20
-
21
- const rootId = fileId("root");
22
- const srcId = fileId("src");
23
- const readmeId = fileId("readme");
24
-
25
- const srcEntry = {
26
- id: srcId,
27
- parentId: rootId,
28
- name: "src",
29
- relativePath: "src",
30
- kind: "directory" as const,
31
- hidden: false,
32
- ignored: false,
33
- hasChildren: true,
34
- gitStatus: null,
35
- };
36
-
37
- const readmeEntry = {
38
- id: readmeId,
39
- parentId: rootId,
40
- name: "README.md",
41
- relativePath: "README.md",
42
- kind: "file" as const,
43
- hidden: false,
44
- ignored: false,
45
- hasChildren: false,
46
- gitStatus: "modified" as const,
47
- };
48
-
49
- const rootCatalog = {
50
- status: "ready" as const,
51
- workspaceName: "tmux-ide",
52
- revision: filesRev("gen1"),
53
- rootId,
54
- directory: { id: rootId, name: "tmux-ide", relativePath: null, parentId: null },
55
- breadcrumbs: [{ id: rootId, label: "tmux-ide" }],
56
- entries: [srcEntry, readmeEntry],
57
- totalEntries: 2,
58
- truncated: false,
59
- };
60
-
61
- describe("workspace files catalog resource", () => {
62
- it("accepts a well-formed root listing and its envelope", () => {
63
- expect(WorkspaceFilesCatalogResourceV1SchemaZ.safeParse(rootCatalog).success).toBe(true);
64
- expect(
65
- WorkspaceFilesCatalogEnvelopeV1SchemaZ.safeParse({
66
- version: WORKSPACE_FILES_CATALOG_RESOURCE_VERSION,
67
- daemon,
68
- resource: rootCatalog,
69
- }).success,
70
- ).toBe(true);
71
- });
72
-
73
- it("accepts a nested directory listing with canonical child paths", () => {
74
- const nested = {
75
- ...rootCatalog,
76
- rootId,
77
- directory: { id: srcId, name: "src", relativePath: "src", parentId: rootId },
78
- breadcrumbs: [
79
- { id: rootId, label: "tmux-ide" },
80
- { id: srcId, label: "src" },
81
- ],
82
- entries: [
83
- {
84
- id: fileId("index"),
85
- parentId: srcId,
86
- name: "index.ts",
87
- relativePath: "src/index.ts",
88
- kind: "file" as const,
89
- hidden: false,
90
- ignored: false,
91
- hasChildren: false,
92
- gitStatus: null,
93
- },
94
- ],
95
- totalEntries: 1,
96
- };
97
- expect(WorkspaceFilesCatalogResourceV1SchemaZ.safeParse(nested).success).toBe(true);
98
- });
99
-
100
- it("rejects entries that lie about their path, parent, or children", () => {
101
- expect(
102
- WorkspaceFileEntrySchemaZ.safeParse({ ...readmeEntry, relativePath: "docs/OTHER.md" })
103
- .success,
104
- ).toBe(false);
105
- expect(WorkspaceFileEntrySchemaZ.safeParse({ ...readmeEntry, hasChildren: true }).success).toBe(
106
- false,
107
- );
108
- // A file cannot advertise children even when its path and name agree.
109
- expect(
110
- WorkspaceFileEntrySchemaZ.safeParse({ ...readmeEntry, kind: "file", hasChildren: true })
111
- .success,
112
- ).toBe(false);
113
- // Extra keys — including a reserved record key held as an own property — are
114
- // refused by the strict object. A computed key avoids the prototype-setter form.
115
- expect(
116
- WorkspaceFileEntrySchemaZ.safeParse({ ...readmeEntry, ["__proto__"]: "polluted" }).success,
117
- ).toBe(false);
118
- });
119
-
120
- it("enforces catalog-wide invariants", () => {
121
- // A child that is not parented by the listed directory.
122
- expect(
123
- WorkspaceFilesCatalogResourceV1SchemaZ.safeParse({
124
- ...rootCatalog,
125
- entries: [{ ...readmeEntry, parentId: fileId("other") }],
126
- totalEntries: 1,
127
- }).success,
128
- ).toBe(false);
129
- // Duplicate ids.
130
- expect(
131
- WorkspaceFilesCatalogResourceV1SchemaZ.safeParse({
132
- ...rootCatalog,
133
- entries: [srcEntry, { ...readmeEntry, id: srcId }],
134
- }).success,
135
- ).toBe(false);
136
- // truncated must describe the omission implied by totalEntries.
137
- expect(
138
- WorkspaceFilesCatalogResourceV1SchemaZ.safeParse({
139
- ...rootCatalog,
140
- totalEntries: 9,
141
- truncated: false,
142
- }).success,
143
- ).toBe(false);
144
- // The first breadcrumb must be the workspace root.
145
- expect(
146
- WorkspaceFilesCatalogResourceV1SchemaZ.safeParse({
147
- ...rootCatalog,
148
- breadcrumbs: [{ id: srcId, label: "src" }],
149
- }).success,
150
- ).toBe(false);
151
- // Only the root may omit relative path and parent identity.
152
- expect(
153
- WorkspaceFilesCatalogResourceV1SchemaZ.safeParse({
154
- ...rootCatalog,
155
- directory: { id: srcId, name: "src", relativePath: null, parentId: null },
156
- }).success,
157
- ).toBe(false);
158
- });
159
-
160
- it("rejects an unavailable catalog missing its reason", () => {
161
- expect(
162
- WorkspaceFilesCatalogResourceV1SchemaZ.safeParse({
163
- status: "unavailable",
164
- workspaceName: "tmux-ide",
165
- reason: "directory-not-found",
166
- message: "gone",
167
- retryable: true,
168
- }).success,
169
- ).toBe(true);
170
- expect(
171
- WorkspaceFilesCatalogResourceV1SchemaZ.safeParse({
172
- status: "unavailable",
173
- workspaceName: "tmux-ide",
174
- reason: "not-a-real-reason",
175
- message: "gone",
176
- retryable: true,
177
- }).success,
178
- ).toBe(false);
179
- });
180
- });
181
-
182
- describe("workspace file preview resource", () => {
183
- const base = {
184
- workspaceName: "tmux-ide",
185
- catalogRevision: filesRev("gen1"),
186
- fileId: readmeId,
187
- name: "README.md",
188
- relativePath: "README.md",
189
- };
190
-
191
- it("accepts a complete text preview whose line count matches its content", () => {
192
- const preview = {
193
- status: "ready" as const,
194
- ...base,
195
- encoding: "utf-8" as const,
196
- languageHint: "markdown",
197
- content: "line one\nline two\nline three",
198
- totalBytes: 28,
199
- totalLines: 3,
200
- truncated: false,
201
- };
202
- expect(
203
- WorkspaceFilePreviewEnvelopeV1SchemaZ.safeParse({
204
- version: WORKSPACE_FILE_PREVIEW_RESOURCE_VERSION,
205
- daemon,
206
- resource: preview,
207
- }).success,
208
- ).toBe(true);
209
-
210
- // A complete preview must report its rendered line count honestly.
211
- expect(
212
- WorkspaceFilePreviewEnvelopeV1SchemaZ.safeParse({
213
- version: WORKSPACE_FILE_PREVIEW_RESOURCE_VERSION,
214
- daemon,
215
- resource: { ...preview, totalLines: 99 },
216
- }).success,
217
- ).toBe(false);
218
- });
219
-
220
- it("rejects NUL bytes inside a text preview", () => {
221
- const preview = {
222
- status: "ready" as const,
223
- ...base,
224
- encoding: "utf-8" as const,
225
- languageHint: null,
226
- content: `has${String.fromCharCode(0)}nul`,
227
- totalBytes: 7,
228
- totalLines: 1,
229
- truncated: false,
230
- };
231
- expect(
232
- WorkspaceFilePreviewEnvelopeV1SchemaZ.safeParse({
233
- version: WORKSPACE_FILE_PREVIEW_RESOURCE_VERSION,
234
- daemon,
235
- resource: preview,
236
- }).success,
237
- ).toBe(false);
238
- });
239
-
240
- it("accepts binary and too-large previews", () => {
241
- expect(
242
- WorkspaceFilePreviewEnvelopeV1SchemaZ.safeParse({
243
- version: WORKSPACE_FILE_PREVIEW_RESOURCE_VERSION,
244
- daemon,
245
- resource: { status: "binary", ...base, totalBytes: 4096, mediaType: "image/png" },
246
- }).success,
247
- ).toBe(true);
248
- expect(
249
- WorkspaceFilePreviewEnvelopeV1SchemaZ.safeParse({
250
- version: WORKSPACE_FILE_PREVIEW_RESOURCE_VERSION,
251
- daemon,
252
- resource: {
253
- status: "too-large",
254
- ...base,
255
- totalBytes: 5_000_000,
256
- limitBytes: 524_288,
257
- },
258
- }).success,
259
- ).toBe(true);
260
- });
261
- });
@@ -1,147 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import type { WorkspaceFileEntry } from "../workspace-files-resource.ts";
3
- import {
4
- flattenWorkspaceFileTree,
5
- moveWorkspaceFileSelection,
6
- sortWorkspaceFileEntries,
7
- toggleWorkspaceFileExpansion,
8
- type WorkspaceFileTreeCatalog,
9
- } from "../workspace-files-tree.ts";
10
-
11
- const fileId = (token: string) => `file.${token.padEnd(16, "0")}`;
12
- const rootId = fileId("root");
13
- const srcId = fileId("src");
14
- const assetsId = fileId("assets");
15
-
16
- const entry = (
17
- over: Partial<WorkspaceFileEntry> & Pick<WorkspaceFileEntry, "id" | "name">,
18
- ): WorkspaceFileEntry => ({
19
- parentId: rootId,
20
- relativePath: over.name,
21
- kind: "file",
22
- hidden: false,
23
- ignored: false,
24
- hasChildren: false,
25
- gitStatus: null,
26
- ...over,
27
- });
28
-
29
- const rootEntries: WorkspaceFileEntry[] = [
30
- entry({ id: srcId, name: "src", kind: "directory", hasChildren: true, relativePath: "src" }),
31
- entry({
32
- id: assetsId,
33
- name: "Assets",
34
- kind: "directory",
35
- hasChildren: true,
36
- relativePath: "Assets",
37
- }),
38
- entry({ id: fileId("readme"), name: "README.md", relativePath: "README.md" }),
39
- entry({ id: fileId("app"), name: "app.ts", relativePath: "app.ts" }),
40
- ];
41
-
42
- const srcCatalog: WorkspaceFileTreeCatalog = {
43
- directoryId: srcId,
44
- entries: [
45
- entry({
46
- id: fileId("index"),
47
- parentId: srcId,
48
- name: "index.ts",
49
- relativePath: "src/index.ts",
50
- }),
51
- ],
52
- };
53
-
54
- const catalogs: WorkspaceFileTreeCatalog[] = [
55
- { directoryId: rootId, entries: rootEntries },
56
- srcCatalog,
57
- ];
58
-
59
- describe("workspace file tree projection", () => {
60
- it("orders directories before files, case-insensitively", () => {
61
- const names = sortWorkspaceFileEntries(rootEntries).map((e) => e.name);
62
- expect(names).toEqual(["Assets", "src", "app.ts", "README.md"]);
63
- });
64
-
65
- it("flattens only expanded, loaded directories", () => {
66
- const collapsed = flattenWorkspaceFileTree({ rootId, catalogs });
67
- expect(collapsed.rows.map((r) => r.name)).toEqual(["Assets", "src", "app.ts", "README.md"]);
68
- const srcRow = collapsed.rows.find((r) => r.id === srcId)!;
69
- expect(srcRow.expandable).toBe(true);
70
- expect(srcRow.expanded).toBe(false);
71
- expect(srcRow.childrenLoaded).toBe(true);
72
- // The Assets directory has no loaded catalog yet.
73
- expect(collapsed.rows.find((r) => r.id === assetsId)!.childrenLoaded).toBe(false);
74
-
75
- const expanded = flattenWorkspaceFileTree({ rootId, catalogs, expandedIds: [srcId] });
76
- const indexRow = expanded.rows.find((r) => r.name === "index.ts");
77
- expect(indexRow?.depth).toBe(1);
78
- expect(expanded.rows.find((r) => r.id === srcId)!.expanded).toBe(true);
79
- });
80
-
81
- it("reports and preserves the selection through the flatten", () => {
82
- const view = flattenWorkspaceFileTree({ rootId, catalogs, selectedId: srcId });
83
- expect(view.selectedId).toBe(srcId);
84
- expect(view.rows[view.selectedIndex]!.id).toBe(srcId);
85
- // A selection that is not visible collapses to none.
86
- const gone = flattenWorkspaceFileTree({ rootId, catalogs, selectedId: fileId("ghost") });
87
- expect(gone.selectedId).toBeNull();
88
- expect(gone.selectedIndex).toBe(-1);
89
- });
90
-
91
- it("guards against a catalog cycle instead of looping forever", () => {
92
- const loopId = fileId("loop");
93
- const cyclic: WorkspaceFileTreeCatalog[] = [
94
- {
95
- directoryId: rootId,
96
- entries: [
97
- entry({
98
- id: loopId,
99
- name: "loop",
100
- kind: "directory",
101
- hasChildren: true,
102
- relativePath: "loop",
103
- }),
104
- ],
105
- },
106
- {
107
- directoryId: loopId,
108
- entries: [
109
- entry({
110
- id: rootId,
111
- parentId: loopId,
112
- name: "back",
113
- kind: "directory",
114
- hasChildren: true,
115
- relativePath: "loop/back",
116
- }),
117
- ],
118
- },
119
- ];
120
- const view = flattenWorkspaceFileTree({
121
- rootId,
122
- catalogs: cyclic,
123
- expandedIds: [loopId, rootId],
124
- });
125
- expect(view.truncated).toBe(true);
126
- // The walk stops when it revisits the root rather than recursing endlessly.
127
- expect(view.rows.length).toBeLessThan(5);
128
- });
129
-
130
- it("toggles expansion immutably", () => {
131
- const source = new Set([srcId]);
132
- const expanded = toggleWorkspaceFileExpansion(source, assetsId);
133
- expect(expanded.has(assetsId)).toBe(true);
134
- expect(source.has(assetsId)).toBe(false);
135
- expect(toggleWorkspaceFileExpansion(expanded, srcId, false).has(srcId)).toBe(false);
136
- expect(toggleWorkspaceFileExpansion(source, srcId).has(srcId)).toBe(false);
137
- });
138
-
139
- it("moves the selection and clamps at the ends", () => {
140
- const rows = flattenWorkspaceFileTree({ rootId, catalogs }).rows;
141
- const first = rows[0]!.id;
142
- expect(moveWorkspaceFileSelection(rows, null, "down")).toBe(first);
143
- expect(moveWorkspaceFileSelection(rows, first, "up")).toBe(first);
144
- expect(moveWorkspaceFileSelection(rows, first, "down")).toBe(rows[1]!.id);
145
- expect(moveWorkspaceFileSelection([], null, "down")).toBeNull();
146
- });
147
- });