tmux-ide 2.9.2 → 2.9.3

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/README.md +1 -1
  2. package/bin/cli.js +2071 -625
  3. package/package.json +14 -5
  4. package/packages/contracts/src/desktop-host.ts +3 -0
  5. package/packages/contracts/src/saved-machines.ts +19 -0
  6. package/packages/contracts/src/terminal-replica.ts +1 -1
  7. package/packages/core/package.json +3 -2
  8. package/packages/core/src/terminal-delivery.ts +51 -3
  9. package/packages/core/src/terminal-fnv64-wasm.ts +4 -3
  10. package/packages/core/src/terminal-replica-hash-cache.ts +129 -31
  11. package/packages/core/src/terminal-replica-owned-row.ts +27 -0
  12. package/packages/core/src/terminal-replica.ts +13 -28
  13. package/packages/daemon/dist/command-center/daemon-fleet-facts-observer.js +96 -10
  14. package/packages/daemon/dist/command-center/resources/saved-machine-route.js +40 -1
  15. package/packages/daemon/dist/command-center/ws-events.js +5 -24
  16. package/packages/daemon/dist/lib/canonical-daemon-bootstrap.js +8 -0
  17. package/packages/daemon/dist/lib/canonical-daemon.js +28 -2
  18. package/packages/daemon/dist/lib/daemon-embed.js +1 -1
  19. package/packages/daemon/dist/lib/daemon-service-manager.js +122 -0
  20. package/packages/daemon/dist/lib/daemon-service-plan.js +127 -0
  21. package/packages/daemon/dist/lib/daemon-service.js +259 -0
  22. package/packages/daemon/dist/lib/install-origin.js +2 -0
  23. package/packages/daemon/dist/lib/local-fleet-request.js +9 -3
  24. package/packages/daemon/dist/lib/log.js +6 -10
  25. package/packages/daemon/dist/lib/managed-installer-update.js +67 -0
  26. package/packages/daemon/dist/lib/{tui-download-lock.js → private-operation-lock.js} +11 -11
  27. package/packages/daemon/dist/lib/remote-daemon-info.js +3 -2
  28. package/packages/daemon/dist/lib/remote-daemon-lifecycle.js +4 -4
  29. package/packages/daemon/dist/lib/remote-tmux-command.js +10 -0
  30. package/packages/daemon/dist/lib/runtime-namespace.js +28 -16
  31. package/packages/daemon/dist/lib/saved-machines.js +40 -2
  32. package/packages/daemon/dist/lib/ssh-daemon-transport.js +24 -9
  33. package/packages/daemon/dist/lib/tmux-server-owner.js +1 -1
  34. package/packages/daemon/dist/lib/tmux-server-registration.js +21 -3
  35. package/packages/daemon/dist/lib/tui-binary.js +2 -2
  36. package/packages/daemon/dist/lib/update.js +18 -0
  37. package/packages/daemon/dist/lib/workspace-multiplexer-verbs.js +24 -1
  38. package/packages/daemon/dist/machines.js +40 -4
  39. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +2 -2
  40. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
  41. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
  42. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +2 -2
  43. package/packages/daemon/dist/terminal/attachments/direct-websocket.js +4 -1
  44. package/packages/daemon/dist/terminal/attachments/native-runtime.js +22 -13
  45. package/packages/daemon/dist/terminal/mirror/mirror-service.js +101 -3
  46. package/packages/daemon/dist/terminal/mirror/registered-window-guard.js +62 -0
  47. package/packages/daemon/dist/terminal/mirror/session-channel.js +75 -4
  48. package/packages/daemon/dist/terminal/mirror/shared-window-index.js +45 -0
  49. package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +11 -1
  50. package/packages/daemon/dist/terminal/session-runtime/multiplexer-backend.js +1 -0
  51. package/packages/daemon/dist/terminal/session-runtime/registry.js +56 -7
  52. package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-executor.js +3 -1
  53. package/packages/daemon/dist/tui/mirror/dialog-model.js +2 -35
  54. package/packages/daemon/dist/tui/mirror/runtime/application-daemon-authority-owner.js +8 -0
  55. package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +20 -2
  56. package/packages/daemon/dist/tui/mirror/runtime/application-machine-agents.js +7 -1
  57. package/packages/daemon/dist/tui/mirror/runtime/application-machine-authority.js +59 -9
  58. package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +27 -19
  59. package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +23 -18
  60. package/packages/daemon/dist/tui/mirror/runtime/application-machine-startup.js +31 -2
  61. package/packages/daemon/dist/tui/mirror/runtime/application-palette-search-owner.js +7 -3
  62. package/packages/daemon/dist/tui/mirror/runtime/application-performance-log.js +3 -0
  63. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-input-ingress.js +146 -2
  64. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +201 -50
  65. package/packages/daemon/dist/tui/mirror/runtime/bounded-performance-record-writer.js +50 -10
  66. package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +6 -5
  67. package/packages/daemon/dist/tui/mirror/runtime/terminal-pane-input-router.js +3 -0
  68. package/packages/daemon/dist/tui/mirror/runtime/workspace-terminal-fast-lane.js +8 -2
  69. package/packages/daemon/dist/tui/mirror/terminal-text.js +35 -0
  70. package/packages/daemon/dist/tui/mirror/workspace/connection-feedback.js +5 -3
  71. package/packages/daemon/src/command-center/daemon-fleet-facts-observer.ts +100 -10
  72. package/packages/daemon/src/command-center/resources/saved-machine-route.ts +48 -1
  73. package/packages/daemon/src/command-center/ws-events.ts +5 -25
  74. package/packages/daemon/src/lib/canonical-daemon-bootstrap.ts +12 -0
  75. package/packages/daemon/src/lib/canonical-daemon.ts +44 -2
  76. package/packages/daemon/src/lib/daemon-embed.ts +1 -0
  77. package/packages/daemon/src/lib/daemon-service-manager.ts +152 -0
  78. package/packages/daemon/src/lib/daemon-service-plan.ts +153 -0
  79. package/packages/daemon/src/lib/daemon-service.ts +301 -0
  80. package/packages/daemon/src/lib/install-origin.ts +5 -0
  81. package/packages/daemon/src/lib/local-fleet-request.ts +19 -3
  82. package/packages/daemon/src/lib/log.ts +13 -9
  83. package/packages/daemon/src/lib/managed-installer-update.ts +79 -0
  84. package/packages/daemon/src/lib/{tui-download-lock.ts → private-operation-lock.ts} +15 -11
  85. package/packages/daemon/src/lib/remote-daemon-info.ts +3 -3
  86. package/packages/daemon/src/lib/remote-daemon-lifecycle.ts +4 -4
  87. package/packages/daemon/src/lib/remote-tmux-command.ts +12 -0
  88. package/packages/daemon/src/lib/runtime-namespace.ts +27 -18
  89. package/packages/daemon/src/lib/saved-machines.ts +40 -1
  90. package/packages/daemon/src/lib/ssh-daemon-transport.ts +25 -7
  91. package/packages/daemon/src/lib/tmux-server-owner.ts +1 -0
  92. package/packages/daemon/src/lib/tmux-server-registration.ts +21 -3
  93. package/packages/daemon/src/lib/tui-binary.ts +2 -2
  94. package/packages/daemon/src/lib/update.ts +24 -0
  95. package/packages/daemon/src/lib/workspace-multiplexer-verbs.ts +22 -0
  96. package/packages/daemon/src/machines.ts +53 -5
  97. package/packages/daemon/src/terminal/attachments/direct-websocket.ts +8 -2
  98. package/packages/daemon/src/terminal/attachments/native-runtime.ts +28 -13
  99. package/packages/daemon/src/terminal/mirror/mirror-service.ts +105 -3
  100. package/packages/daemon/src/terminal/mirror/registered-window-guard.ts +66 -0
  101. package/packages/daemon/src/terminal/mirror/session-channel.ts +92 -4
  102. package/packages/daemon/src/terminal/mirror/shared-window-index.ts +42 -0
  103. package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +12 -1
  104. package/packages/daemon/src/terminal/session-runtime/multiplexer-backend.ts +2 -0
  105. package/packages/daemon/src/terminal/session-runtime/registry.ts +70 -8
  106. package/packages/daemon/src/terminal/session-runtime/semantic-mutation-executor.ts +3 -1
  107. package/packages/daemon/src/tui/mirror/dialog-model.ts +2 -32
  108. package/packages/daemon/src/tui/mirror/runtime/application-daemon-authority-owner.ts +8 -0
  109. package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +19 -2
  110. package/packages/daemon/src/tui/mirror/runtime/application-machine-agents.ts +6 -1
  111. package/packages/daemon/src/tui/mirror/runtime/application-machine-authority.ts +60 -11
  112. package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +39 -33
  113. package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +31 -23
  114. package/packages/daemon/src/tui/mirror/runtime/application-machine-startup.ts +38 -2
  115. package/packages/daemon/src/tui/mirror/runtime/application-palette-search-owner.ts +7 -4
  116. package/packages/daemon/src/tui/mirror/runtime/application-performance-log.ts +3 -0
  117. package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +1 -1
  118. package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +47 -46
  119. package/packages/daemon/src/tui/mirror/runtime/application-terminal-input-ingress.ts +165 -2
  120. package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +207 -53
  121. package/packages/daemon/src/tui/mirror/runtime/bounded-performance-record-writer.ts +49 -9
  122. package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +6 -5
  123. package/packages/daemon/src/tui/mirror/runtime/terminal-pane-input-router.ts +4 -0
  124. package/packages/daemon/src/tui/mirror/runtime/workspace-terminal-fast-lane.ts +8 -3
  125. package/packages/daemon/src/tui/mirror/terminal-text.ts +32 -0
  126. package/packages/daemon/src/tui/mirror/workspace/connection-feedback.ts +6 -3
  127. package/packages/daemon-client/src/fleet-connection-status.ts +10 -1
  128. package/packages/daemon-client/src/workspace-client.ts +4 -1
  129. package/packages/contracts/src/__tests__/actions-contract.test.ts +0 -119
  130. package/packages/contracts/src/__tests__/agent-graph-overlay.test.ts +0 -428
  131. package/packages/contracts/src/__tests__/app-window-mutation.test.ts +0 -51
  132. package/packages/contracts/src/__tests__/app-window-state.test.ts +0 -34
  133. package/packages/contracts/src/__tests__/application-shell.test.ts +0 -645
  134. package/packages/contracts/src/__tests__/automation-operations.test.ts +0 -78
  135. package/packages/contracts/src/__tests__/causal-cell.test.ts +0 -47
  136. package/packages/contracts/src/__tests__/client-view-state.test.ts +0 -115
  137. package/packages/contracts/src/__tests__/cohesion-fixture.test.ts +0 -106
  138. package/packages/contracts/src/__tests__/commands.test.ts +0 -86
  139. package/packages/contracts/src/__tests__/control.test.ts +0 -154
  140. package/packages/contracts/src/__tests__/daemon-events.test.ts +0 -405
  141. package/packages/contracts/src/__tests__/daemon-resources.test.ts +0 -143
  142. package/packages/contracts/src/__tests__/daemon-wire.test.ts +0 -149
  143. package/packages/contracts/src/__tests__/desktop-host.test.ts +0 -269
  144. package/packages/contracts/src/__tests__/desktop-missions.test.ts +0 -215
  145. package/packages/contracts/src/__tests__/experience-conformance.test.ts +0 -153
  146. package/packages/contracts/src/__tests__/experience-kernel-boundary.test.ts +0 -113
  147. package/packages/contracts/src/__tests__/experience-shell.test.ts +0 -65
  148. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v2.json +0 -35
  149. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v3.json +0 -35
  150. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v4.json +0 -35
  151. package/packages/contracts/src/__tests__/fixtures/scoped-receipt.ts +0 -62
  152. package/packages/contracts/src/__tests__/fleet-agent-graph.test.ts +0 -231
  153. package/packages/contracts/src/__tests__/fleet-catalog.test.ts +0 -270
  154. package/packages/contracts/src/__tests__/fleet-lifecycle.test.ts +0 -62
  155. package/packages/contracts/src/__tests__/focus-overlay.test.ts +0 -110
  156. package/packages/contracts/src/__tests__/ide-config.test.ts +0 -64
  157. package/packages/contracts/src/__tests__/interaction-evidence.test.ts +0 -288
  158. package/packages/contracts/src/__tests__/interaction-receipt-v2.test.ts +0 -72
  159. package/packages/contracts/src/__tests__/issue-error.test.ts +0 -78
  160. package/packages/contracts/src/__tests__/mission-projections.test.ts +0 -186
  161. package/packages/contracts/src/__tests__/missions.test.ts +0 -272
  162. package/packages/contracts/src/__tests__/multiplexer-verbs.test.ts +0 -242
  163. package/packages/contracts/src/__tests__/pane-appearance.test.ts +0 -146
  164. package/packages/contracts/src/__tests__/pane-read-result.test.ts +0 -55
  165. package/packages/contracts/src/__tests__/pane-stream.test.ts +0 -536
  166. package/packages/contracts/src/__tests__/pane-widget-marker.test.ts +0 -327
  167. package/packages/contracts/src/__tests__/performance-metrics.test.ts +0 -73
  168. package/packages/contracts/src/__tests__/performance-qualification.test.ts +0 -137
  169. package/packages/contracts/src/__tests__/rich-card-widget.test.ts +0 -29
  170. package/packages/contracts/src/__tests__/session-runtime.test.ts +0 -512
  171. package/packages/contracts/src/__tests__/startup-readiness.test.ts +0 -409
  172. package/packages/contracts/src/__tests__/terminal-attachment-stream.test.ts +0 -72
  173. package/packages/contracts/src/__tests__/terminal-attachments.test.ts +0 -329
  174. package/packages/contracts/src/__tests__/terminal-delivery.test.ts +0 -88
  175. package/packages/contracts/src/__tests__/terminal-runtime-inventory.test.ts +0 -68
  176. package/packages/contracts/src/__tests__/terminals.test.ts +0 -87
  177. package/packages/contracts/src/__tests__/tmux-server-scope.test.ts +0 -166
  178. package/packages/contracts/src/__tests__/transport-contract-loading.test.ts +0 -91
  179. package/packages/contracts/src/__tests__/visual-tokens.test.ts +0 -224
  180. package/packages/contracts/src/__tests__/window-links.test.ts +0 -243
  181. package/packages/contracts/src/__tests__/workspace-changes-resource.test.ts +0 -317
  182. package/packages/contracts/src/__tests__/workspace-changes-view.test.ts +0 -122
  183. package/packages/contracts/src/__tests__/workspace-config.test.ts +0 -294
  184. package/packages/contracts/src/__tests__/workspace-files-resource.test.ts +0 -261
  185. package/packages/contracts/src/__tests__/workspace-files-tree.test.ts +0 -147
  186. package/packages/contracts/src/__tests__/workspace-pane-creation.test.ts +0 -249
  187. package/packages/contracts/src/__tests__/workspace-promotion.test.ts +0 -94
  188. package/packages/contracts/src/__tests__/workspace-resource-identity.test.ts +0 -90
  189. package/packages/contracts/src/daemon-resource-request.test.ts +0 -192
  190. package/packages/contracts/src/native-operation-identity.test.ts +0 -23
  191. package/packages/contracts/src/workspace-admission.test.ts +0 -21
  192. package/packages/contracts/src/workspace-catalog-resource.test.ts +0 -146
  193. package/packages/core/src/agent-provisioning.test.ts +0 -42
  194. package/packages/core/src/application-shell-session.test.ts +0 -105
  195. package/packages/core/src/interaction-evidence.test.ts +0 -134
  196. package/packages/core/src/interaction-flow.test.ts +0 -66
  197. package/packages/core/src/interaction-receipts.test.ts +0 -671
  198. package/packages/core/src/navigator.test.ts +0 -34
  199. package/packages/core/src/optimistic-projection-conformance.test.ts +0 -75
  200. package/packages/core/src/optimistic-projection.test.ts +0 -118
  201. package/packages/core/src/performance-metrics.test.ts +0 -121
  202. package/packages/core/src/performance-qualification.test.ts +0 -283
  203. package/packages/core/src/saved-machines.test.ts +0 -80
  204. package/packages/core/src/terminal-compact-cold.test.ts +0 -87
  205. package/packages/core/src/terminal-conformance.test.ts +0 -48
  206. package/packages/core/src/terminal-delivery-cache-lifetime.test.ts +0 -167
  207. package/packages/core/src/terminal-delivery-encode-cooperative.test.ts +0 -140
  208. package/packages/core/src/terminal-delivery-representation-hash.test.ts +0 -77
  209. package/packages/core/src/terminal-delivery.test.ts +0 -1471
  210. package/packages/core/src/terminal-fnv64-wasm.test.ts +0 -61
  211. package/packages/core/src/terminal-replica-cold.test.ts +0 -31
  212. package/packages/core/src/terminal-replica-cooperative.test.ts +0 -170
  213. package/packages/core/src/terminal-replica-hash-cache.test.ts +0 -283
  214. package/packages/core/src/terminal-replica.test.ts +0 -347
  215. package/packages/core/src/workspace-selection.test.ts +0 -63
  216. package/packages/daemon/dist/__tests__/support.js +0 -33
  217. package/packages/daemon/dist/lib/__tests__/fixtures/workspace-state-writer.js +0 -58
  218. package/packages/daemon/dist/lib/__tests__/installed-recovery-fixture.js +0 -400
  219. package/packages/daemon/dist/terminal/__tests__/MockPtyAdapter.js +0 -182
  220. package/packages/daemon/dist/terminal/mirror/__tests__/scripted-channel.js +0 -98
  221. package/packages/daemon/dist/terminal/mirror/__tests__/simulated-channel.js +0 -156
  222. package/packages/daemon/src/tui/mirror/__snapshots__/activity-surface-renderer.test.tsx.snap +0 -42
  223. package/packages/daemon/src/tui/mirror/__snapshots__/changes-terminal-surface-renderer.test.tsx.snap +0 -267
  224. package/packages/daemon/src/tui/mirror/__snapshots__/home-files-surface-renderer.test.tsx.snap +0 -267
  225. package/packages/daemon/src/tui/mirror/__snapshots__/missions-surface-renderer.test.tsx.snap +0 -134
  226. package/packages/daemon/src/tui/mirror/__snapshots__/recipes-gallery-renderer.test.tsx.snap +0 -141
  227. package/packages/daemon/src/tui/mirror/__snapshots__/shell-chrome-renderer.test.tsx.snap +0 -134
  228. package/packages/daemon/src/tui/mirror/runtime/__snapshots__/application-shell-view-renderer.test.tsx.snap +0 -87
  229. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/agent-terminal-canvas-renderer.test.tsx.snap +0 -348
  230. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/application-shell-renderer.test.tsx.snap +0 -134
  231. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/command-palette-surface-renderer.test.tsx.snap +0 -255
  232. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/pane-frame-renderer.test.tsx.snap +0 -525
  233. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/terminal-pane-header-polish-renderer.test.tsx.snap +0 -31
  234. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/workbench-shell-renderer.test.tsx.snap +0 -134
  235. package/packages/daemon-client/src/application-shell-session.test.ts +0 -196
  236. package/packages/daemon-client/src/automation-client.test.ts +0 -214
  237. package/packages/daemon-client/src/bootstrap-coordinator.test.ts +0 -145
  238. package/packages/daemon-client/src/connection-supervisor.test.ts +0 -109
  239. package/packages/daemon-client/src/direct-application-shell-transport.test.ts +0 -941
  240. package/packages/daemon-client/src/first-latest-coordinator.test.ts +0 -175
  241. package/packages/daemon-client/src/fleet-dial-scheduler.test.ts +0 -114
  242. package/packages/daemon-client/src/fleet-environments.test.ts +0 -40
  243. package/packages/daemon-client/src/generation-bound-store-timers.test.ts +0 -74
  244. package/packages/daemon-client/src/owner-action-client.test.ts +0 -245
  245. package/packages/daemon-client/src/pane-stream-client.test.ts +0 -2841
  246. package/packages/daemon-client/src/pane-stream-clock-calibration.test.ts +0 -207
  247. package/packages/daemon-client/src/pane-stream-loopback-benchmark.test.ts +0 -51
  248. package/packages/daemon-client/src/push-resource-session.test.ts +0 -509
  249. package/packages/daemon-client/src/resource-replica.test.ts +0 -186
  250. package/packages/daemon-client/src/runtime-resource-ledger.test.ts +0 -18
  251. package/packages/daemon-client/src/scoped-tmux-server-transport.test.ts +0 -114
  252. package/packages/daemon-client/src/terminal-fast-lane.test.ts +0 -835
  253. package/packages/daemon-client/src/tmux-server-client.test.ts +0 -279
  254. package/packages/daemon-client/src/tmux-server-interaction-events.test.ts +0 -230
  255. package/packages/daemon-client/src/workspace-catalog-v2.test.ts +0 -109
  256. package/packages/daemon-client/src/workspace-client-boundaries.test.ts +0 -59
  257. package/packages/daemon-client/src/workspace-client-operations.test.ts +0 -96
  258. package/packages/daemon-client/src/workspace-client.test.ts +0 -2912
  259. package/packages/daemon-client/src/workspace-event-supervisor.test.ts +0 -1086
  260. package/packages/daemon-client/src/workspace-pane-client.test.ts +0 -80
  261. package/packages/presentation/src/presentation-contract.test.ts +0 -63
  262. package/packages/tmux-bridge/src/runner.test.ts +0 -614
  263. package/packages/tmux-bridge/src/targeting.test.ts +0 -107
@@ -1,327 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import {
4
- WIDGET_MARKER_CONCEAL_PREFIX,
5
- WIDGET_MARKER_CONCEAL_SUFFIX,
6
- WIDGET_MARKER_MAX_PAYLOAD_CHARACTERS,
7
- WIDGET_MARKER_SENTINEL,
8
- WidgetMarkerTooLargeError,
9
- createWidgetMarkerByteWatcher,
10
- decodeWidgetMarkerLine,
11
- detectWidgetMarker,
12
- detectWidgetMarkerFromReplicaRows,
13
- encodeWidgetMarkerLine,
14
- widgetLogicalLines,
15
- widgetMarkerAnnouncement,
16
- widgetMarkerDigest,
17
- type WidgetCellRow,
18
- } from "../pane-widget-marker.ts";
19
-
20
- /** A row of ordinary text, padded out the way a real grid pads its rows. */
21
- function row(text: string, options: { wrapped?: boolean; columns?: number } = {}): WidgetCellRow {
22
- const columns = options.columns ?? Math.max(text.length, 80);
23
- const cells = [...text];
24
- while (cells.length < columns) cells.push(" ");
25
- return { cells, wrapped: options.wrapped ?? false };
26
- }
27
-
28
- /** Split `text` across rows of `columns` cells, marking continuations wrapped. */
29
- function wrappedRows(text: string, columns: number): WidgetCellRow[] {
30
- const characters = [...text];
31
- const rows: WidgetCellRow[] = [];
32
- for (let offset = 0; offset < characters.length; offset += columns) {
33
- const slice = characters.slice(offset, offset + columns);
34
- while (slice.length < columns) slice.push(" ");
35
- rows.push({ cells: slice, wrapped: offset > 0 });
36
- }
37
- return rows;
38
- }
39
-
40
- describe("widget marker grammar", () => {
41
- it("round-trips an id and its arguments", () => {
42
- const line = encodeWidgetMarkerLine("markdown", { text: "# Plan", title: "Plan" });
43
- expect(decodeWidgetMarkerLine(line)).toEqual({
44
- id: "markdown",
45
- args: { text: "# Plan", title: "Plan" },
46
- });
47
- });
48
-
49
- it("carries no whitespace or shell-significant characters in its payload", () => {
50
- const line = encodeWidgetMarkerLine("markdown", {
51
- text: "a b\tc\nd 'e' \"f\" $g `h` |i| >j< \\k\\",
52
- });
53
- const payload = line.split(" ")[2]!;
54
- expect(payload).toMatch(/^[A-Za-z0-9_-]+$/u);
55
- });
56
-
57
- it("survives non-ASCII arguments byte for byte", () => {
58
- const text = "Ünïcode — emoji 👩‍👩‍👧‍👦, CJK 日本語, RTL עברית";
59
- const decoded = decodeWidgetMarkerLine(encodeWidgetMarkerLine("markdown", { text }));
60
- expect(decoded).toEqual({ id: "markdown", args: { text } });
61
- });
62
-
63
- it("encodes an argument-less marker without a payload", () => {
64
- expect(decodeWidgetMarkerLine(encodeWidgetMarkerLine("markdown"))).toEqual({
65
- id: "markdown",
66
- args: null,
67
- });
68
- });
69
-
70
- it("wraps the announcement in conceal so a plain terminal shows nothing", () => {
71
- const announcement = widgetMarkerAnnouncement("markdown", { text: "x" });
72
- // SGR 8 conceal on, SGR 0 off, and a newline so the marker owns its line.
73
- expect(announcement.startsWith(WIDGET_MARKER_CONCEAL_PREFIX)).toBe(true);
74
- expect(announcement.endsWith(`${WIDGET_MARKER_CONCEAL_SUFFIX}\n`)).toBe(true);
75
- expect(WIDGET_MARKER_CONCEAL_PREFIX).toBe("\u001b[8m");
76
- });
77
-
78
- it("refuses a payload over the marker ceiling rather than emitting a truncated one", () => {
79
- const oversized = "x".repeat(WIDGET_MARKER_MAX_PAYLOAD_CHARACTERS);
80
- expect(() => encodeWidgetMarkerLine("markdown", { text: oversized })).toThrow(
81
- WidgetMarkerTooLargeError,
82
- );
83
- });
84
-
85
- it("rejects an invalid widget id at encode time", () => {
86
- expect(() => encodeWidgetMarkerLine("Markdown")).toThrow();
87
- expect(() => encodeWidgetMarkerLine("../etc/passwd")).toThrow();
88
- });
89
- });
90
-
91
- /*
92
- * The collision surface.
93
- *
94
- * Every case here is a line ordinary output could plausibly contain. If any of
95
- * them decoded, a user running `cat`, `grep` or a test runner over a file that
96
- * mentions this feature would have their pane hijacked — which is the failure
97
- * the digest and the field count exist to make impossible.
98
- */
99
- describe("widget marker near-misses", () => {
100
- const valid = encodeWidgetMarkerLine("markdown", { text: "hello" });
101
-
102
- it("ignores prose that merely mentions the sentinel", () => {
103
- expect(decodeWidgetMarkerLine(`The ${WIDGET_MARKER_SENTINEL} sentinel opts a pane in.`)).toBe(
104
- null,
105
- );
106
- });
107
-
108
- it("ignores the sentinel with a plausible but unsigned payload", () => {
109
- expect(decodeWidgetMarkerLine(`${WIDGET_MARKER_SENTINEL} markdown aGVsbG8 00000000`)).toBe(
110
- null,
111
- );
112
- });
113
-
114
- it("ignores a marker whose payload was edited after signing", () => {
115
- const [sentinel, id, payload, digest] = valid.split(" ");
116
- const tampered = `${sentinel} ${id} ${payload!.slice(0, -1)}A ${digest}`;
117
- expect(decodeWidgetMarkerLine(tampered)).toBe(null);
118
- });
119
-
120
- it("ignores a marker whose id was swapped for another widget's", () => {
121
- const [sentinel, , payload, digest] = valid.split(" ");
122
- expect(decodeWidgetMarkerLine(`${sentinel} image ${payload} ${digest}`)).toBe(null);
123
- });
124
-
125
- it("ignores extra fields appended to a valid marker", () => {
126
- expect(decodeWidgetMarkerLine(`${valid} extra`)).toBe(null);
127
- });
128
-
129
- it("ignores a valid marker quoted inside a longer line", () => {
130
- expect(decodeWidgetMarkerLine(`$ echo "${valid}"`)).toBe(null);
131
- });
132
-
133
- it("ignores a future grammar version", () => {
134
- expect(decodeWidgetMarkerLine(valid.replace("/1", "/2"))).toBe(null);
135
- });
136
-
137
- it("ignores a payload that is signed but is not JSON", () => {
138
- // Base64url of "not json at all", correctly digested: the last gate.
139
- const payload = "bm90IGpzb24gYXQgYWxs";
140
- const line = `${WIDGET_MARKER_SENTINEL} markdown ${payload} ${widgetMarkerDigest("markdown", payload)}`;
141
- expect(decodeWidgetMarkerLine(line)).toBe(null);
142
- });
143
-
144
- it("accepts a marker indented or padded by the surrounding output", () => {
145
- expect(decodeWidgetMarkerLine(` ${valid} `)).toEqual({
146
- id: "markdown",
147
- args: { text: "hello" },
148
- });
149
- });
150
- });
151
-
152
- describe("logical lines from cell rows", () => {
153
- it("joins wrapped rows back into the line that was printed", () => {
154
- const marker = encodeWidgetMarkerLine("markdown", { text: "a fairly long document body" });
155
- const rows = wrappedRows(marker, 24);
156
- expect(rows.length).toBeGreaterThan(1);
157
- expect(widgetLogicalLines(rows)).toEqual([marker]);
158
- });
159
-
160
- it("keeps unwrapped rows as separate lines", () => {
161
- expect(widgetLogicalLines([row("one"), row("two")])).toEqual(["one", "two"]);
162
- });
163
-
164
- it("drops the grid's trailing pad cells but keeps interior spaces", () => {
165
- expect(widgetLogicalLines([row("a b", { columns: 40 })])).toEqual(["a b"]);
166
- });
167
-
168
- /*
169
- * Rule 10, as a test. A cell can hold several UTF-16 units, and the trailing
170
- * half of a wide glyph is an EMPTY cell whose characters already belong to
171
- * the cell before it. Reassembling by cell reproduces the line; reassembling
172
- * by string offset would double every wide character.
173
- */
174
- it("reassembles rows containing wide and multi-unit cells", () => {
175
- const rows: WidgetCellRow[] = [
176
- { cells: ["日", "", "本", "", "語", "", "!"], wrapped: false },
177
- { cells: ["👩‍👩‍👧‍👦", "", " ", "o", "k"], wrapped: true },
178
- ];
179
- expect(widgetLogicalLines(rows)).toEqual(["日本語!👩‍👩‍👧‍👦 ok"]);
180
- });
181
-
182
- it("finds a marker that emoji pushed onto a second wrapped row", () => {
183
- const marker = encodeWidgetMarkerLine("markdown", { text: "ok" });
184
- const rows = [
185
- { cells: ["🎉", "", " ", "d", "o", "n", "e"], wrapped: false },
186
- ...wrappedRows(marker, 30),
187
- ];
188
- expect(detectWidgetMarker(rows)?.id).toBe("markdown");
189
- });
190
- });
191
-
192
- describe("detectWidgetMarker", () => {
193
- it("returns null for a pane of ordinary output", () => {
194
- expect(detectWidgetMarker([row("$ ls"), row("README.md package.json"), row("$ ")])).toBe(null);
195
- });
196
-
197
- it("returns null for an empty pane", () => {
198
- expect(detectWidgetMarker([])).toBe(null);
199
- });
200
-
201
- it("finds a marker below ordinary scrollback", () => {
202
- const marker = encodeWidgetMarkerLine("markdown", { text: "# Title" });
203
- const found = detectWidgetMarker([row("$ tmux-ide widget markdown plan.md"), row(marker)]);
204
- expect(found).toEqual({ id: "markdown", args: { text: "# Title" }, lineIndex: 1 });
205
- });
206
-
207
- /*
208
- * Bug this catches: a pane that rendered one widget, then another, keeps
209
- * showing the first because detection stopped at the oldest match in
210
- * scrollback.
211
- */
212
- it("prefers the newest marker when a pane rendered more than one", () => {
213
- const first = encodeWidgetMarkerLine("markdown", { text: "first" });
214
- const second = encodeWidgetMarkerLine("markdown", { text: "second" });
215
- expect(detectWidgetMarker([row(first), row("$ "), row(second)])?.args).toEqual({
216
- text: "second",
217
- });
218
- });
219
-
220
- it("stops finding a marker once the screen has been cleared", () => {
221
- const marker = encodeWidgetMarkerLine("markdown", { text: "x" });
222
- expect(detectWidgetMarker([row(marker)])).not.toBe(null);
223
- // What a Ctrl-C trap leaves behind: a cleared grid and a fresh prompt.
224
- expect(detectWidgetMarker([row("$ ")])).toBe(null);
225
- });
226
- });
227
-
228
- describe("detectWidgetMarkerFromReplicaRows", () => {
229
- it("reads retained canonical cells directly, including wrapped multi-cell graphemes", () => {
230
- const marker = encodeWidgetMarkerLine("markdown", { text: "canonical" });
231
- const rows = wrappedRows(marker, 24).map((source) => ({
232
- wrapped: source.wrapped,
233
- cells: source.cells.map((grapheme) => ({ grapheme })),
234
- }));
235
- expect(detectWidgetMarkerFromReplicaRows(rows)).toEqual({
236
- id: "markdown",
237
- args: { text: "canonical" },
238
- lineIndex: 0,
239
- });
240
- });
241
- });
242
-
243
- /*
244
- * Truncation fails CLOSED.
245
- *
246
- * The marker can be longer than the pane's whole seed window, and the mirror
247
- * path reseeds from `capture-pane -S -2000`: a marker that has scrolled partly
248
- * out of that window comes back with its head missing. Every one of these must
249
- * read as ordinary terminal output. A widget rendered from half a payload is
250
- * strictly worse than no widget — the user loses the pane AND the text that was
251
- * in it, with nothing on screen saying why.
252
- */
253
- describe("a truncated marker", () => {
254
- const marker = encodeWidgetMarkerLine("markdown", { text: "a document long enough to wrap" });
255
-
256
- it("fails closed when the seed window cut off its head", () => {
257
- const rows = wrappedRows(marker, 40);
258
- expect(rows.length).toBeGreaterThan(2);
259
- // What a reseed delivers when the marker started above the window: the
260
- // first surviving row is a continuation, and has nothing to continue.
261
- const survived = rows.slice(2);
262
- expect(survived[0]!.wrapped).toBe(true);
263
- expect(detectWidgetMarker(survived)).toBe(null);
264
- });
265
-
266
- it("fails closed when only its tail survives", () => {
267
- expect(detectWidgetMarker(wrappedRows(marker, 40).slice(-1))).toBe(null);
268
- });
269
-
270
- it("fails closed while it is still arriving", () => {
271
- // Mid-stream: the digest has not been written yet, so there is no fourth
272
- // field and nothing to verify the first three against.
273
- const partial = marker.slice(0, marker.length - 9);
274
- expect(decodeWidgetMarkerLine(partial)).toBe(null);
275
- expect(detectWidgetMarker(wrappedRows(partial, 40))).toBe(null);
276
- });
277
-
278
- it("fails closed on a digest that survived but a payload that did not", () => {
279
- const [sentinel, id, payload, digest] = marker.split(" ");
280
- const clipped = `${sentinel} ${id} ${payload!.slice(0, payload!.length - 200)} ${digest}`;
281
- expect(decodeWidgetMarkerLine(clipped)).toBe(null);
282
- });
283
-
284
- /*
285
- * The head-truncation case has one more shape worth naming: the survivor is a
286
- * base64url fragment, and base64url is a subset of the characters a marker's
287
- * own fields use. It must not be mistaken for a marker even so.
288
- */
289
- it("does not mistake a surviving payload fragment for a marker of its own", () => {
290
- const payload = marker.split(" ")[2]!;
291
- expect(decodeWidgetMarkerLine(payload.slice(500, 900))).toBe(null);
292
- });
293
- });
294
-
295
- describe("the byte watcher that gates the scan", () => {
296
- const encoder = new TextEncoder();
297
-
298
- it("fires on the sentinel and stays quiet on ordinary output", () => {
299
- const watcher = createWidgetMarkerByteWatcher();
300
- expect(watcher.observe(encoder.encode("$ pnpm test\nok\n"))).toBe(false);
301
- expect(
302
- watcher.observe(encoder.encode(widgetMarkerAnnouncement("markdown", { text: "x" }))),
303
- ).toBe(true);
304
- });
305
-
306
- /*
307
- * Bug this catches: the transport splits the marker across two frames — which
308
- * it is free to do at any byte — and the pane silently never becomes a widget
309
- * for exactly the users whose output is large enough to be chunked.
310
- */
311
- it("fires when the sentinel is split across two chunks", () => {
312
- const watcher = createWidgetMarkerByteWatcher();
313
- const line = widgetMarkerAnnouncement("markdown", { text: "x" });
314
- const split = 12;
315
- expect(watcher.observe(encoder.encode(line.slice(0, split)))).toBe(false);
316
- expect(watcher.observe(encoder.encode(line.slice(split)))).toBe(true);
317
- });
318
-
319
- it("survives a multi-byte character straddling a chunk boundary", () => {
320
- const watcher = createWidgetMarkerByteWatcher();
321
- const bytes = encoder.encode("日本語 output");
322
- expect(() => {
323
- watcher.observe(bytes.subarray(0, 2));
324
- watcher.observe(bytes.subarray(2));
325
- }).not.toThrow();
326
- });
327
- });
@@ -1,73 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { LocalPerformanceSnapshotV1SchemaZ } from "../performance-metrics.ts";
4
-
5
- const emptyDistribution = {
6
- count: 0,
7
- latest: null,
8
- p50: null,
9
- p95: null,
10
- max: null,
11
- } as const;
12
-
13
- function snapshot() {
14
- return {
15
- version: 1,
16
- source: "opentui",
17
- sampleSequence: 0,
18
- authority: {
19
- daemonInstanceId: null,
20
- workspaceName: null,
21
- generation: null,
22
- incarnation: null,
23
- },
24
- activeFps: null,
25
- dirtyRows: emptyDistribution,
26
- parseMs: emptyDistribution,
27
- paintMs: emptyDistribution,
28
- queueDepth: { current: 0, peak: 0, capacity: { current: null, peak: null } },
29
- revisionLag: { current: null, peak: null },
30
- reseeds: 0,
31
- } as const;
32
- }
33
-
34
- describe("local performance snapshot contract", () => {
35
- it("accepts and freezes the idle snapshot without inventing unavailable values", () => {
36
- const parsed = LocalPerformanceSnapshotV1SchemaZ.parse(snapshot());
37
- expect(parsed.activeFps).toBeNull();
38
- expect(parsed.revisionLag.current).toBeNull();
39
- expect(Object.isFrozen(parsed)).toBe(true);
40
- expect(Object.isFrozen(parsed.authority)).toBe(true);
41
- expect(Object.isFrozen(parsed.queueDepth.capacity)).toBe(true);
42
- });
43
-
44
- it("rejects malformed, unsafe, and internally inconsistent measurements", () => {
45
- expect(() =>
46
- LocalPerformanceSnapshotV1SchemaZ.parse({ ...snapshot(), activeFps: Number.NaN }),
47
- ).toThrow();
48
- expect(() =>
49
- LocalPerformanceSnapshotV1SchemaZ.parse({
50
- ...snapshot(),
51
- parseMs: { count: 1, latest: 2, p50: 3, p95: 2, max: 3 },
52
- }),
53
- ).toThrow(/ordered/u);
54
- expect(() =>
55
- LocalPerformanceSnapshotV1SchemaZ.parse({
56
- ...snapshot(),
57
- queueDepth: { current: 3, peak: 3, capacity: { current: 2, peak: 2 } },
58
- }),
59
- ).toThrow(/capacity/u);
60
- expect(() =>
61
- LocalPerformanceSnapshotV1SchemaZ.parse({
62
- ...snapshot(),
63
- revisionLag: { current: 1, peak: null },
64
- }),
65
- ).toThrow(/requires peak/u);
66
- expect(() =>
67
- LocalPerformanceSnapshotV1SchemaZ.parse({
68
- ...snapshot(),
69
- sampleSequence: Number.MAX_SAFE_INTEGER + 1,
70
- }),
71
- ).toThrow();
72
- });
73
- });
@@ -1,137 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import {
4
- ClientQueueMetricV1SchemaZ,
5
- MutationTerminalOutcomeV1SchemaZ,
6
- PERFORMANCE_STAGE_ORDER,
7
- PerformanceTraceV1SchemaZ,
8
- } from "../performance-qualification.ts";
9
-
10
- function span(stage: (typeof PERFORMANCE_STAGE_ORDER)[number], index: number) {
11
- return {
12
- version: 1,
13
- stage,
14
- processId: stage === "input" || stage === "paint" ? "web-a" : "daemon-a",
15
- clockId: stage === "input" || stage === "paint" ? "browser-monotonic" : "node-hrtime",
16
- clockKind: stage === "input" || stage === "paint" ? "performance-now" : "hrtime",
17
- startedAtMicros: index * 100,
18
- endedAtMicros: index * 100 + 50,
19
- } as const;
20
- }
21
-
22
- describe("performance qualification contracts", () => {
23
- it("accepts ordered local spans without comparing clocks across processes", () => {
24
- const value = PerformanceTraceV1SchemaZ.parse({
25
- version: 1,
26
- traceId: "00000000-0000-4000-8000-000000000002",
27
- scenario: "leading-edge-input",
28
- stages: PERFORMANCE_STAGE_ORDER.map(span),
29
- localInputToPaint: {
30
- version: 1,
31
- processId: "web-a",
32
- clockId: "browser-monotonic",
33
- clockKind: "performance-now",
34
- startedAtMicros: 0,
35
- endedAtMicros: 20_000,
36
- },
37
- });
38
- expect(value.stages.map(({ stage }) => stage)).toEqual(PERFORMANCE_STAGE_ORDER);
39
- expect(Object.isFrozen(value)).toBe(true);
40
- });
41
-
42
- it("rejects malformed monotonic spans and incomplete/reordered pipelines", () => {
43
- const base = {
44
- version: 1,
45
- traceId: "00000000-0000-4000-8000-000000000002",
46
- scenario: "input",
47
- stages: PERFORMANCE_STAGE_ORDER.map(span),
48
- localInputToPaint: {
49
- version: 1,
50
- processId: "web-a",
51
- clockId: "browser-monotonic",
52
- clockKind: "performance-now",
53
- startedAtMicros: 20,
54
- endedAtMicros: 10,
55
- },
56
- };
57
- expect(() => PerformanceTraceV1SchemaZ.parse(base)).toThrow(/ends before/u);
58
- expect(() =>
59
- PerformanceTraceV1SchemaZ.parse({
60
- ...base,
61
- localInputToPaint: { ...base.localInputToPaint, endedAtMicros: 30 },
62
- stages: [...base.stages].reverse(),
63
- }),
64
- ).toThrow(/expected input/u);
65
- expect(() =>
66
- PerformanceTraceV1SchemaZ.parse({
67
- ...base,
68
- localInputToPaint: {
69
- ...base.localInputToPaint,
70
- processId: "other-client",
71
- endedAtMicros: 30,
72
- },
73
- }),
74
- ).toThrow(/share one client clock domain/u);
75
- expect(() =>
76
- PerformanceTraceV1SchemaZ.parse({
77
- ...base,
78
- localInputToPaint: {
79
- ...base.localInputToPaint,
80
- startedAtMicros: 1,
81
- endedAtMicros: 60_000,
82
- },
83
- }),
84
- ).toThrow(/contain the local input and paint/u);
85
- const causal = PERFORMANCE_STAGE_ORDER.map(span);
86
- causal[0] = { ...causal[0]!, endedAtMicros: causal[5]!.startedAtMicros + 1 };
87
- expect(() =>
88
- PerformanceTraceV1SchemaZ.parse({
89
- ...base,
90
- stages: causal,
91
- localInputToPaint: {
92
- ...base.localInputToPaint,
93
- startedAtMicros: 0,
94
- endedAtMicros: 60_000,
95
- },
96
- }),
97
- ).toThrow(/input must complete before local paint/u);
98
- });
99
-
100
- it("bounds queue dimensions and controlled vocabularies", () => {
101
- const queue = {
102
- version: 1,
103
- clientId: "web-a",
104
- disposition: "slow",
105
- queue: "terminal",
106
- sampleOrdinal: 0,
107
- depthItems: 4,
108
- capacityItems: 3,
109
- bytes: 4,
110
- capacityBytes: 8,
111
- coalesced: 0,
112
- dropped: 0,
113
- } as const;
114
- expect(() => ClientQueueMetricV1SchemaZ.parse(queue)).toThrow(/exceeds capacity/u);
115
- expect(() =>
116
- ClientQueueMetricV1SchemaZ.parse({ ...queue, depthItems: 1, queue: "misc" }),
117
- ).toThrow();
118
- expect(() =>
119
- MutationTerminalOutcomeV1SchemaZ.parse({
120
- version: 1,
121
- mutationId: "00000000-0000-4000-8000-000000000003",
122
- status: "done",
123
- }),
124
- ).toThrow();
125
- expect(() =>
126
- MutationTerminalOutcomeV1SchemaZ.parse({
127
- version: 1,
128
- mutationId: "00000000-0000-4000-8000-000000000003",
129
- processId: "daemon-a",
130
- clockId: "node-hrtime",
131
- clockKind: "hrtime",
132
- occurredAtMicros: 10,
133
- status: "observed",
134
- }),
135
- ).toThrow(/observed requires identity/u);
136
- });
137
- });
@@ -1,29 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { RichCardWidgetArgsSchemaZ, richCardTextFallback } from "../rich-card-widget.ts";
4
-
5
- describe("renderer-neutral rich card", () => {
6
- it("has a useful plain-text fallback for the TUI", () => {
7
- const card = RichCardWidgetArgsSchemaZ.parse({
8
- title: "Build",
9
- subtitle: "Release candidate",
10
- items: [
11
- { type: "badge", text: "Tests passed", tone: "success" },
12
- { type: "progress", label: "Coverage", value: 91.6 },
13
- { type: "button", label: "Rerun", input: "pnpm test" },
14
- ],
15
- });
16
- expect(richCardTextFallback(card)).toBe(
17
- "Build\nRelease candidate\nTests passed\nCoverage: 92%\n[Rerun]",
18
- );
19
- });
20
-
21
- it("refuses executable or unknown component types", () => {
22
- expect(
23
- RichCardWidgetArgsSchemaZ.safeParse({
24
- title: "Unsafe",
25
- items: [{ type: "html", html: "<script>" }],
26
- }).success,
27
- ).toBe(false);
28
- });
29
- });