tmux-ide 2.9.0-beta.19 → 2.9.0-beta.21

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 (90) hide show
  1. package/bin/cli.js +1979 -783
  2. package/package.json +4 -2
  3. package/packages/contracts/src/__tests__/daemon-wire.test.ts +32 -0
  4. package/packages/contracts/src/daemon-events.ts +12 -12
  5. package/packages/contracts/src/daemon-wire.ts +30 -0
  6. package/packages/daemon/dist/command-center/agent-status-watch.js +9 -2
  7. package/packages/daemon/dist/command-center/diagnostics.js +65 -0
  8. package/packages/daemon/dist/command-center/log-stream.js +9 -0
  9. package/packages/daemon/dist/command-center/resources/fleet-preview-route.js +22 -6
  10. package/packages/daemon/dist/command-center/server.js +7 -0
  11. package/packages/daemon/dist/doctor.js +62 -0
  12. package/packages/daemon/dist/lib/__tests__/installed-recovery-fixture.js +400 -0
  13. package/packages/daemon/dist/lib/app-config.js +4 -2
  14. package/packages/daemon/dist/lib/canonical-daemon.js +1 -0
  15. package/packages/daemon/dist/lib/daemon-embed.js +24 -0
  16. package/packages/daemon/dist/lib/daemon-provenance.js +575 -0
  17. package/packages/daemon/dist/lib/headless-daemon.js +1 -0
  18. package/packages/daemon/dist/lib/log-sanitize.js +199 -0
  19. package/packages/daemon/dist/lib/log.js +123 -13
  20. package/packages/daemon/dist/lib/soak-diagnostics.js +124 -0
  21. package/packages/daemon/dist/lib/soak-verdict.js +472 -0
  22. package/packages/daemon/dist/lib/terminal-host-color.js +33 -0
  23. package/packages/daemon/dist/lib/tmux-external-interaction-observer.js +187 -39
  24. package/packages/daemon/dist/lib/tmux-interaction-retention.js +21 -0
  25. package/packages/daemon/dist/lib/workspace-promotion.js +55 -37
  26. package/packages/daemon/dist/terminal/mirror/session-channel.js +2 -1
  27. package/packages/daemon/dist/tui/mirror/automatic-contrast.js +161 -0
  28. package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +9 -3
  29. package/packages/daemon/dist/tui/mirror/pane-surface.jsx +7 -5
  30. package/packages/daemon/dist/tui/mirror/resize-transaction.js +48 -20
  31. package/packages/daemon/dist/tui/mirror/runtime/application-appearance-owner.js +40 -6
  32. package/packages/daemon/dist/tui/mirror/runtime/application-fleet-preview.js +4 -1
  33. package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +63 -42
  34. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +138 -67
  35. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-palette-owner.js +44 -28
  36. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +49 -11
  37. package/packages/daemon/dist/tui/mirror/runtime/semantic-shell-viewport-resize.js +140 -2
  38. package/packages/daemon/dist/tui/mirror/runtime/workspace-terminal-fast-lane.js +3 -1
  39. package/packages/daemon/dist/tui/mirror/semantic-pane-render-source.js +2 -1
  40. package/packages/daemon/dist/tui/mirror/theme.js +4 -31
  41. package/packages/daemon/dist/tui/mirror/workspace/terminal-pane-header.jsx +15 -8
  42. package/packages/daemon/dist/tui/team/wait-receipts.js +112 -33
  43. package/packages/daemon/src/command-center/agent-status-watch.ts +8 -2
  44. package/packages/daemon/src/command-center/diagnostics.ts +75 -0
  45. package/packages/daemon/src/command-center/log-stream.ts +8 -0
  46. package/packages/daemon/src/command-center/resources/fleet-preview-route.ts +27 -5
  47. package/packages/daemon/src/command-center/server.ts +8 -0
  48. package/packages/daemon/src/doctor.ts +70 -0
  49. package/packages/daemon/src/lib/app-config.ts +11 -3
  50. package/packages/daemon/src/lib/canonical-daemon.ts +1 -0
  51. package/packages/daemon/src/lib/daemon-embed.ts +30 -0
  52. package/packages/daemon/src/lib/daemon-provenance.ts +769 -0
  53. package/packages/daemon/src/lib/fleet-preview-model.ts +1 -0
  54. package/packages/daemon/src/lib/headless-daemon.ts +1 -0
  55. package/packages/daemon/src/lib/log-sanitize.ts +248 -0
  56. package/packages/daemon/src/lib/log.ts +165 -14
  57. package/packages/daemon/src/lib/soak-diagnostics.ts +183 -0
  58. package/packages/daemon/src/lib/soak-verdict.ts +785 -0
  59. package/packages/daemon/src/lib/terminal-host-color.ts +43 -0
  60. package/packages/daemon/src/lib/tmux-external-interaction-observer.ts +233 -37
  61. package/packages/daemon/src/lib/tmux-interaction-retention.ts +24 -0
  62. package/packages/daemon/src/lib/workspace-promotion.ts +74 -45
  63. package/packages/daemon/src/terminal/mirror/session-channel.ts +2 -1
  64. package/packages/daemon/src/tui/mirror/automatic-contrast.ts +180 -0
  65. package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +26 -5
  66. package/packages/daemon/src/tui/mirror/pane-surface.tsx +9 -8
  67. package/packages/daemon/src/tui/mirror/resize-transaction.ts +46 -22
  68. package/packages/daemon/src/tui/mirror/runtime/application-appearance-owner.ts +45 -6
  69. package/packages/daemon/src/tui/mirror/runtime/application-fleet-preview.ts +4 -0
  70. package/packages/daemon/src/tui/mirror/runtime/application-fleet-switcher.tsx +2 -1
  71. package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +99 -75
  72. package/packages/daemon/src/tui/mirror/runtime/application-palette-preview.tsx +13 -3
  73. package/packages/daemon/src/tui/mirror/runtime/application-reference-sheet.tsx +125 -0
  74. package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +2 -1
  75. package/packages/daemon/src/tui/mirror/runtime/application-shell-overlays.tsx +199 -157
  76. package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +2 -0
  77. package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +148 -69
  78. package/packages/daemon/src/tui/mirror/runtime/application-terminal-palette-owner.ts +55 -28
  79. package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +59 -17
  80. package/packages/daemon/src/tui/mirror/runtime/semantic-shell-viewport-resize.ts +151 -3
  81. package/packages/daemon/src/tui/mirror/runtime/workspace-terminal-fast-lane.ts +3 -1
  82. package/packages/daemon/src/tui/mirror/semantic-pane-render-source.ts +2 -1
  83. package/packages/daemon/src/tui/mirror/theme.ts +4 -39
  84. package/packages/daemon/src/tui/mirror/workspace/terminal-pane-header.tsx +21 -8
  85. package/packages/daemon/src/tui/team/wait-receipts.ts +115 -40
  86. package/packages/daemon-client/src/terminal-fast-lane.test.ts +18 -0
  87. package/packages/daemon-client/src/terminal-fast-lane.ts +7 -2
  88. package/packages/tmux-bridge/src/index.ts +2 -0
  89. package/packages/tmux-bridge/src/runner.test.ts +41 -0
  90. package/packages/tmux-bridge/src/runner.ts +41 -2
@@ -0,0 +1,125 @@
1
+ /* @jsxImportSource @opentui/solid */
2
+ import { createSignal, For } from "solid-js";
3
+ import type { SemanticThemeSnapshot } from "../theme.ts";
4
+ import { Dialog } from "../ui/dialog.tsx";
5
+ import { TuiButton } from "../ui/button.tsx";
6
+ import { useKeyboardRoute } from "../ui/keyboard-router.tsx";
7
+ import { clipTerminal } from "../terminal-text.ts";
8
+
9
+ const shortcuts = [
10
+ "APPLICATION",
11
+ "F1 Home · F2 Terminals",
12
+ "F5 Commands · F6 Sessions across machines",
13
+ "F7 Agent attention · Ctrl+G Machine sidebar",
14
+ "IN THE COMMAND / SESSION MENU",
15
+ "Type to search · ↑/↓ choose · Enter activate",
16
+ "Ctrl+Space Toggle search / navigation mode",
17
+ "Navigation mode: j/k move · g/G first/last · i search",
18
+ "PageUp/PageDown page · Ctrl+U/D half page",
19
+ "Ctrl+H Local / all hosts · Ctrl+F Favorite session",
20
+ "Ctrl+←/→ Browse windows without activating them",
21
+ "Ctrl+P Show / hide preview · Ctrl+E Expand / restore",
22
+ "Ctrl+N New session · Ctrl+X Confirm close session",
23
+ "Ctrl+R Retry selected host",
24
+ "Ctrl+K Shortcuts · Ctrl+B What's new",
25
+ "Esc Close sheet / menu and return to your terminal",
26
+ ];
27
+ const changes = [
28
+ "LATEST CHANGES",
29
+ "Agent previews follow the highlighted pane in the session.",
30
+ "Pane identity isolates cached previews and late responses.",
31
+ "Shortcuts and release history are available offline here.",
32
+ "",
33
+ "2.9.0-beta.18",
34
+ "Shared F5/F6 switcher with side-by-side previews in wide terminals.",
35
+ "Cached previews appear immediately while a fresh snapshot loads.",
36
+ "Fuzzy search, match highlighting, favorites and recent sessions.",
37
+ "Window browsing, expanded previews and bounded background work.",
38
+ "Create / confirm close on the selected host without leaving the menu.",
39
+ "",
40
+ "2.9.0-beta.17",
41
+ "Fleet navigation, passive window previews and activity summaries.",
42
+ "Adaptive refresh and exact-host actions across machines.",
43
+ "",
44
+ "Full release history: github.com/wavyrai/tmux-ide/releases",
45
+ ];
46
+
47
+ export function ApplicationReferenceSheet(props: {
48
+ page: "shortcuts" | "changes";
49
+ width: number;
50
+ height: number;
51
+ theme: SemanticThemeSnapshot;
52
+ onClose: () => void;
53
+ }) {
54
+ const [page, setPage] = createSignal(props.page);
55
+ const [offset, setOffset] = createSignal(0);
56
+ const rows = () => (page() === "shortcuts" ? shortcuts : changes);
57
+ const capacity = () => Math.max(1, Math.min(24, props.height - 8));
58
+ const move = (delta: number) =>
59
+ setOffset((v) => Math.max(0, Math.min(rows().length - capacity(), v + delta)));
60
+ const change = (next: "shortcuts" | "changes") => {
61
+ setPage(next);
62
+ setOffset(0);
63
+ };
64
+ useKeyboardRoute((event) => {
65
+ event.preventDefault();
66
+ event.stopPropagation();
67
+ if (event.eventType !== "press") return true;
68
+ const key = event.name.toLowerCase();
69
+ if (key === "escape") props.onClose();
70
+ else if (key === "tab") change(page() === "shortcuts" ? "changes" : "shortcuts");
71
+ else if (key === "down" || key === "j") move(1);
72
+ else if (key === "up" || key === "k") move(-1);
73
+ else if (key === "pagedown") move(capacity());
74
+ else if (key === "pageup") move(-capacity());
75
+ return true;
76
+ });
77
+ return (
78
+ <Dialog
79
+ theme={props.theme}
80
+ viewportWidth={props.width}
81
+ viewportHeight={props.height}
82
+ width={Math.min(88, Math.max(1, props.width - 4))}
83
+ height={Math.min(props.height, capacity() + 6)}
84
+ title={page() === "shortcuts" ? "Keyboard shortcuts" : "What's new"}
85
+ footer="Tab switch sheet · ↑↓ scroll · Esc back"
86
+ zIndex={100}
87
+ onDismiss={props.onClose}
88
+ >
89
+ <box height={1} flexDirection="row" gap={1}>
90
+ <TuiButton
91
+ theme={props.theme}
92
+ label="Shortcuts"
93
+ size="compact"
94
+ onPress={() => change("shortcuts")}
95
+ />
96
+ <TuiButton
97
+ theme={props.theme}
98
+ label="What's new"
99
+ size="compact"
100
+ onPress={() => change("changes")}
101
+ />
102
+ <TuiButton theme={props.theme} label="Back" size="compact" onPress={props.onClose} />
103
+ </box>
104
+ <box
105
+ flexDirection="column"
106
+ height={capacity()}
107
+ overflow="hidden"
108
+ onMouseScroll={(event) => {
109
+ event.preventDefault();
110
+ move(event.scroll.direction === "up" ? -3 : 3);
111
+ }}
112
+ >
113
+ <For each={rows().slice(offset(), offset() + capacity())}>
114
+ {(line) => (
115
+ <text
116
+ height={1}
117
+ fg={props.theme.roles.text.primary}
118
+ content={clipTerminal(line, Math.max(1, Math.min(88, props.width - 4) - 4))}
119
+ />
120
+ )}
121
+ </For>
122
+ </box>
123
+ </Dialog>
124
+ );
125
+ }
@@ -441,7 +441,7 @@ export async function startApplicationRoot(options: StartApplicationRootOptions
441
441
  if (
442
442
  (shell().semantic?.focus.palette.open || shell().localPaletteOpen) &&
443
443
  event.ctrl &&
444
- ["left", "right", "p", "e", "n", "x", "f", "r"].includes(name) &&
444
+ ["left", "right", "p", "e", "n", "x", "f", "r", "k", "b"].includes(name) &&
445
445
  componentKeyboardRoutes.route(event)
446
446
  )
447
447
  return;
@@ -622,6 +622,7 @@ export async function startApplicationRoot(options: StartApplicationRootOptions
622
622
  })}
623
623
  onResizePreview={recoverHostFocus(interaction.previewPaneResize)}
624
624
  onResizePane={recoverHostFocus(interaction.resizePane)}
625
+ onCancelResize={interaction.cancelPaneResize}
625
626
  onResizePointerIngress={recoverHostFocus.optional(resizeIngress)}
626
627
  onWheelObservation={tuiPerfWheelObservation}
627
628
  onTerminalInput={recoverHostFocus((paneId, input) =>
@@ -1,3 +1,4 @@
1
+ import { ApplicationReferenceSheet } from "./application-reference-sheet.tsx";
1
2
  import { ApplicationFleetSessionActions } from "./application-fleet-session-actions.tsx";
2
3
  import { ApplicationPalettePreview } from "./application-palette-preview.tsx";
3
4
  /* @jsxImportSource @opentui/solid */
@@ -106,6 +107,12 @@ export function MinimalPalette(props: {
106
107
  }): JSX.Element {
107
108
  const [expanded, setExpanded] = createSignal(false);
108
109
  const [modal, setModal] = createSignal(false);
110
+ const [sheet, setSheet] = createSignal<"shortcuts" | "changes">();
111
+ const openSheet = (page: "shortcuts" | "changes" | undefined) => {
112
+ setSheet(page);
113
+ setModal(!!page);
114
+ props.onModalChange?.(!!page);
115
+ };
109
116
  const [lastHost, setLastHost] = createSignal<ApplicationPaletteCommand>();
110
117
  createEffect(() => {
111
118
  const c = props.commands[props.selected];
@@ -181,10 +188,11 @@ export function MinimalPalette(props: {
181
188
  )
182
189
  return false;
183
190
  const key = event.name.toLowerCase();
184
- if (!["f", "r"].includes(key)) return false;
191
+ if (!["f", "r", "k", "b"].includes(key)) return false;
185
192
  event.preventDefault();
186
193
  event.stopPropagation();
187
194
  if (event.eventType !== "press") return true;
195
+ if (key === "k" || key === "b") openSheet(key === "k" ? "shortcuts" : "changes");
188
196
  const c = selectedCommand();
189
197
  if (c && key === "f") props.onFavorite?.(c);
190
198
  if (typeof c === "object" && c.fleet && key === "r")
@@ -212,176 +220,203 @@ export function MinimalPalette(props: {
212
220
  };
213
221
  });
214
222
  return (
215
- <Dialog
216
- theme={props.theme}
217
- viewportWidth={props.width}
218
- viewportHeight={props.height}
219
- width={width()}
220
- height={height()}
221
- title={props.title ?? (innerWidth() >= 15 ? "Command palette" : "Commands")}
222
- {...(height() >= 9
223
- ? { footer: props.keyboardHint ?? "↑↓ choose · Enter run · Esc close" }
224
- : {})}
225
- active={props.active}
226
- zIndex={props.zIndex}
227
- onDismiss={props.onClose}
228
- >
229
- <text
230
- height={1}
231
- width={innerWidth()}
232
- fg={props.theme.roles.text.link}
233
- content={clipTerminal(
234
- props.query
235
- ? `/ ${clipTerminalEnd(`${props.query}▏`, innerWidth() - 2)}`
236
- : "/ Search commands…",
237
- innerWidth(),
238
- )}
239
- />
240
- <Show when={height() >= 12}>
241
- <box height={1} flexDirection="row" gap={1} overflow="hidden">
242
- <Badge theme={props.theme} label={`${props.commands.length} matches`} />
243
- <Show when={hasPreview()}>
244
- <Badge theme={props.theme} label="PASSIVE PREVIEW" tone="neutral" />
245
- </Show>
246
- </box>
247
- </Show>
248
- <box
249
- height={bodyHeight()}
250
- flexDirection={sideBySide() ? "row" : "column"}
251
- gap={sideBySide() ? 1 : 0}
252
- overflow="hidden"
223
+ <>
224
+ <Dialog
225
+ theme={props.theme}
226
+ viewportWidth={props.width}
227
+ viewportHeight={props.height}
228
+ width={width()}
229
+ height={height()}
230
+ title={props.title ?? (innerWidth() >= 15 ? "Command palette" : "Commands")}
231
+ {...(height() >= 9
232
+ ? { footer: props.keyboardHint ?? "↑↓ choose · Enter run · Esc close" }
233
+ : {})}
234
+ active={props.active !== false && !sheet()}
235
+ zIndex={props.zIndex}
236
+ onDismiss={props.onClose}
253
237
  >
254
- <Show when={!expanded()}>
255
- <box
256
- width={listWidth()}
257
- height={visibleCapacity()}
258
- flexDirection="column"
259
- overflow="hidden"
260
- onMouseScroll={(event) => {
261
- if (props.active === false || !props.commands.length) return;
262
- event.preventDefault();
263
- event.stopPropagation();
264
- props.onSelect?.(
265
- Math.max(
266
- 0,
267
- Math.min(
268
- props.commands.length - 1,
269
- props.selected + (event.scroll?.direction === "up" ? -1 : 1),
238
+ <text
239
+ height={1}
240
+ width={innerWidth()}
241
+ fg={props.theme.roles.text.link}
242
+ content={clipTerminal(
243
+ props.query
244
+ ? `/ ${clipTerminalEnd(`${props.query}▏`, innerWidth() - 2)}`
245
+ : "/ Search commands…",
246
+ innerWidth(),
247
+ )}
248
+ />
249
+ <Show when={height() >= 12}>
250
+ <box height={1} flexDirection="row" gap={1} overflow="hidden">
251
+ <TuiButton
252
+ theme={props.theme}
253
+ label="Shortcuts ^K"
254
+ size="compact"
255
+ onPress={() => openSheet("shortcuts")}
256
+ />
257
+ <TuiButton
258
+ theme={props.theme}
259
+ label="What's new ^B"
260
+ size="compact"
261
+ onPress={() => openSheet("changes")}
262
+ />
263
+ <Badge theme={props.theme} label={`${props.commands.length} matches`} />
264
+ <Show when={hasPreview()}>
265
+ <Badge theme={props.theme} label="PASSIVE PREVIEW" tone="neutral" />
266
+ </Show>
267
+ </box>
268
+ </Show>
269
+ <box
270
+ height={bodyHeight()}
271
+ flexDirection={sideBySide() ? "row" : "column"}
272
+ gap={sideBySide() ? 1 : 0}
273
+ overflow="hidden"
274
+ >
275
+ <Show when={!expanded()}>
276
+ <box
277
+ width={listWidth()}
278
+ height={visibleCapacity()}
279
+ flexDirection="column"
280
+ overflow="hidden"
281
+ onMouseScroll={(event) => {
282
+ if (props.active === false || !props.commands.length) return;
283
+ event.preventDefault();
284
+ event.stopPropagation();
285
+ props.onSelect?.(
286
+ Math.max(
287
+ 0,
288
+ Math.min(
289
+ props.commands.length - 1,
290
+ props.selected + (event.scroll?.direction === "up" ? -1 : 1),
291
+ ),
270
292
  ),
271
- ),
272
- );
273
- }}
274
- >
275
- <Show
276
- when={props.commands.length > 0}
277
- fallback={
278
- <text
279
- height={1}
280
- width={innerWidth()}
281
- fg={props.theme.roles.text.muted}
282
- content={clipTerminal("No matches · Ctrl+U clear", innerWidth())}
283
- />
284
- }
293
+ );
294
+ }}
285
295
  >
286
- <For each={commandRows().map((row) => applicationCommandDescription(row.command).id)}>
287
- {(id) => {
288
- const row = () =>
289
- commandRows().find(
290
- (row) => applicationCommandDescription(row.command).id === id,
291
- )!;
292
- return (
293
- <OverlayListRow
294
- theme={props.theme}
295
- id={id}
296
- label={`${typeof row().command === "object" && (row().command as { fleet?: { favorite?: boolean } }).fleet?.favorite ? "★ " : ""}${commandLabel(row().command)}`}
297
- query={props.query}
298
- width={listWidth()}
299
- selected={props.selected === row().index}
300
- reserveMarker={innerWidth() >= 16}
301
- disabled={
302
- props.active === false || Boolean(props.disabledReason?.(row().command))
303
- }
304
- danger={props.closeArmed && row().command === "close-pane"}
305
- onHighlight={() => props.onSelect?.(row().index)}
306
- onPress={() => {
307
- props.onSelect?.(row().index);
308
- props.onActivate(row().command);
309
- }}
310
- />
311
- );
296
+ <Show
297
+ when={props.commands.length > 0}
298
+ fallback={
299
+ <text
300
+ height={1}
301
+ width={innerWidth()}
302
+ fg={props.theme.roles.text.muted}
303
+ content={clipTerminal("No matches · Ctrl+U clear", innerWidth())}
304
+ />
305
+ }
306
+ >
307
+ <For
308
+ each={commandRows().map((row) => applicationCommandDescription(row.command).id)}
309
+ >
310
+ {(id) => {
311
+ const row = () =>
312
+ commandRows().find(
313
+ (row) => applicationCommandDescription(row.command).id === id,
314
+ )!;
315
+ return (
316
+ <OverlayListRow
317
+ theme={props.theme}
318
+ id={id}
319
+ label={`${typeof row().command === "object" && (row().command as { fleet?: { favorite?: boolean } }).fleet?.favorite ? "★ " : ""}${commandLabel(row().command)}`}
320
+ query={props.query}
321
+ width={listWidth()}
322
+ selected={props.selected === row().index}
323
+ reserveMarker={innerWidth() >= 16}
324
+ disabled={
325
+ props.active === false || Boolean(props.disabledReason?.(row().command))
326
+ }
327
+ danger={props.closeArmed && row().command === "close-pane"}
328
+ onHighlight={() => props.onSelect?.(row().index)}
329
+ onPress={() => {
330
+ props.onSelect?.(row().index);
331
+ props.onActivate(row().command);
332
+ }}
333
+ />
334
+ );
335
+ }}
336
+ </For>
337
+ </Show>
338
+ </box>
339
+ </Show>
340
+ <Show when={sideBySide()}>
341
+ <text
342
+ width={1}
343
+ height={bodyHeight()}
344
+ fg={props.theme.roles.text.muted}
345
+ content={Array(bodyHeight()).fill("│").join("\n")}
346
+ />
347
+ </Show>
348
+ <Show when={hasPreview()}>
349
+ <ApplicationPalettePreview
350
+ command={selectedCommand()}
351
+ width={previewWidth()}
352
+ height={previewHeight()}
353
+ theme={props.theme}
354
+ active={!modal() && props.active !== false && props.previewActive !== false}
355
+ onExpandedChange={setExpanded}
356
+ />
357
+ </Show>
358
+ </box>
359
+ <Show when={hasPreview()}>
360
+ <box height={2} flexDirection="row" gap={1} overflow="hidden">
361
+ <ApplicationFleetSessionActions
362
+ command={selectedCommand()}
363
+ width={props.width}
364
+ height={props.height}
365
+ theme={props.theme}
366
+ initialName={props.commands.length === 0 ? props.query : undefined}
367
+ active={props.active !== false && props.previewActive !== false}
368
+ onModalChange={(open) => {
369
+ setModal(open);
370
+ props.onModalChange?.(open);
371
+ }}
372
+ />
373
+ <Show when={favorite() !== undefined && props.onFavorite}>
374
+ <KeyHint
375
+ theme={props.theme}
376
+ keys="^F"
377
+ label={favorite() ? "Unfavorite" : "Favorite"}
378
+ disabled={props.active === false || props.previewActive === false}
379
+ onPress={() => {
380
+ const c = selectedCommand();
381
+ if (c) props.onFavorite?.(c);
312
382
  }}
313
- </For>
383
+ />
314
384
  </Show>
315
385
  </box>
316
386
  </Show>
317
- <Show when={sideBySide()}>
387
+ <Show when={height() >= 9}>
318
388
  <text
319
- width={1}
320
- height={bodyHeight()}
389
+ height={1}
390
+ width={innerWidth()}
321
391
  fg={props.theme.roles.text.muted}
322
- content={Array(bodyHeight()).fill("│").join("\n")}
323
- />
324
- </Show>
325
- <Show when={hasPreview()}>
326
- <ApplicationPalettePreview
327
- command={selectedCommand()}
328
- width={previewWidth()}
329
- height={previewHeight()}
330
- theme={props.theme}
331
- active={!modal() && props.active !== false && props.previewActive !== false}
332
- onExpandedChange={setExpanded}
392
+ content={clipTerminal(
393
+ (() => {
394
+ const command = props.commands[props.selected];
395
+ if (!command) return "Try an action, agent or session name";
396
+ return (
397
+ props.disabledReason?.(command) ??
398
+ (props.closeArmed
399
+ ? "Closes the pane and its running process"
400
+ : `${props.selected + 1}/${props.commands.length} · ${applicationCommandDescription(command).detail}`)
401
+ );
402
+ })(),
403
+ innerWidth(),
404
+ )}
333
405
  />
334
406
  </Show>
335
- </box>
336
- <Show when={hasPreview()}>
337
- <box height={2} flexDirection="row" gap={1} overflow="hidden">
338
- <ApplicationFleetSessionActions
339
- command={selectedCommand()}
407
+ </Dialog>
408
+ <Show when={sheet()}>
409
+ {(page) => (
410
+ <ApplicationReferenceSheet
411
+ page={page()}
340
412
  width={props.width}
341
413
  height={props.height}
342
414
  theme={props.theme}
343
- initialName={props.commands.length === 0 ? props.query : undefined}
344
- active={props.active !== false && props.previewActive !== false}
345
- onModalChange={(open) => {
346
- setModal(open);
347
- props.onModalChange?.(open);
348
- }}
415
+ onClose={() => openSheet(undefined)}
349
416
  />
350
- <Show when={favorite() !== undefined && props.onFavorite}>
351
- <KeyHint
352
- theme={props.theme}
353
- keys="^F"
354
- label={favorite() ? "Unfavorite" : "Favorite"}
355
- disabled={props.active === false || props.previewActive === false}
356
- onPress={() => {
357
- const c = selectedCommand();
358
- if (c) props.onFavorite?.(c);
359
- }}
360
- />
361
- </Show>
362
- </box>
363
- </Show>
364
- <Show when={height() >= 9}>
365
- <text
366
- height={1}
367
- width={innerWidth()}
368
- fg={props.theme.roles.text.muted}
369
- content={clipTerminal(
370
- (() => {
371
- const command = props.commands[props.selected];
372
- if (!command) return "Try an action, agent or session name";
373
- return (
374
- props.disabledReason?.(command) ??
375
- (props.closeArmed
376
- ? "Closes the pane and its running process"
377
- : `${props.selected + 1}/${props.commands.length} · ${applicationCommandDescription(command).detail}`)
378
- );
379
- })(),
380
- innerWidth(),
381
- )}
382
- />
417
+ )}
383
418
  </Show>
384
- </Dialog>
419
+ </>
385
420
  );
386
421
  }
387
422
 
@@ -458,7 +493,7 @@ export function AppearanceDialog(props: {
458
493
  viewportWidth={props.width}
459
494
  viewportHeight={props.height}
460
495
  width={width()}
461
- height={Math.min(18, props.height)}
496
+ height={Math.min(19, props.height)}
462
497
  title="Appearance"
463
498
  footer={width() < 40 ? "Enter save · Esc back" : "↑↓ preview · Enter save · Esc cancel"}
464
499
  active={props.active}
@@ -470,10 +505,17 @@ export function AppearanceDialog(props: {
470
505
  fg={props.owner.theme().roles.text.primary}
471
506
  content={`Search: ${props.owner.pickerQuery() || "type to filter"}`}
472
507
  />
508
+ <TuiButton
509
+ theme={props.owner.theme()}
510
+ label={`^A Contrast: ${props.owner.automaticContrast() ? "On" : "Off"}`}
511
+ size="compact"
512
+ disabled={props.active === false}
513
+ onPress={props.owner.toggleAutomaticContrast}
514
+ />
473
515
  <For
474
516
  each={(() => {
475
517
  const options = props.owner.pickerOptions();
476
- const count = Math.max(1, Math.min(10, props.height - 8));
518
+ const count = Math.max(1, Math.min(10, props.height - 9));
477
519
  const index = options.findIndex((p) => p.id === props.owner.pickerSelection());
478
520
  const start = Math.max(
479
521
  0,
@@ -125,6 +125,7 @@ export interface ApplicationShellViewProps {
125
125
  readonly onSelectPane: TerminalWorkspaceProps["onSelectPane"];
126
126
  readonly onResizePreview: TerminalWorkspaceProps["onResizePreview"];
127
127
  readonly onResizePane: TerminalWorkspaceProps["onResizePane"];
128
+ readonly onCancelResize?: TerminalWorkspaceProps["onCancelResize"];
128
129
  readonly onResizePointerIngress?: TerminalWorkspaceProps["onResizePointerIngress"];
129
130
  readonly onWheelObservation?: TerminalWorkspaceProps["onWheelObservation"];
130
131
  readonly onTerminalInput?: TerminalWorkspaceProps["onTerminalInput"];
@@ -517,6 +518,7 @@ export function ApplicationShellView(props: ApplicationShellViewProps): JSX.Elem
517
518
  }}
518
519
  onResizePreview={props.onResizePreview}
519
520
  onResizePane={props.onResizePane}
521
+ onCancelResize={props.onCancelResize}
520
522
  onResizePointerIngress={props.onResizePointerIngress}
521
523
  onWheelObservation={props.onWheelObservation}
522
524
  onTerminalInput={props.onTerminalInput}