tmux-ide 2.9.0-beta.36 → 2.9.0-beta.37

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.
package/bin/cli.js CHANGED
@@ -11279,7 +11279,7 @@ var require_package = __commonJS({
11279
11279
  "package.json"(exports, module) {
11280
11280
  module.exports = {
11281
11281
  name: "tmux-ide",
11282
- version: "2.9.0-beta.36",
11282
+ version: "2.9.0-beta.37",
11283
11283
  description: "A visual, agent-aware IDE for any tmux session, with optional workspace presets",
11284
11284
  type: "module",
11285
11285
  bin: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmux-ide",
3
- "version": "2.9.0-beta.36",
3
+ "version": "2.9.0-beta.37",
4
4
  "description": "A visual, agent-aware IDE for any tmux session, with optional workspace presets",
5
5
  "type": "module",
6
6
  "bin": {
@@ -91,6 +91,11 @@ export function ApplicationTerminalWorkspace(props) {
91
91
  ? clampTerminalViewportOrigin(snapshot, { cols: frame.width, rows: frame.contentHeight }, origin, snapshot.history.length)
92
92
  : origin;
93
93
  }, (paneId) => props.adapter.retainPaneView?.(paneId) ?? null);
94
+ createEffect(() => {
95
+ const paneId = props.focusedPane;
96
+ props.onScrollbackChange?.(paneId !== null && scrollback.offset(paneId) > 0);
97
+ });
98
+ onCleanup(() => props.onScrollbackChange?.(false));
94
99
  const selectionViewport = (paneId, frame) => props.adapter.renderSource.supportsViewportOrigin
95
100
  ? {
96
101
  cols: frame.width,
@@ -16,7 +16,8 @@ export function createApplicationTransientNoteOwner(options) {
16
16
  publish(note) {
17
17
  clear();
18
18
  options.write(note);
19
- if (!note)
19
+ if (!note ||
20
+ /\b(?:failed|failure|error|unavailable|disconnected|reconnecting|recovering|read.only|denied)\b/iu.test(note))
20
21
  return;
21
22
  timer = clock.setTimeout(() => {
22
23
  timer = null;
@@ -222,9 +222,10 @@ export function shellStatusLine(variant, input, width) {
222
222
  }
223
223
  return `${out}…`;
224
224
  }
225
- function meaningfulStatusMessage(message) {
225
+ export function meaningfulStatusMessage(message) {
226
226
  const value = message?.trim();
227
- if (!value || /^(?:ready|live|connected)$/iu.test(value))
227
+ if (!value ||
228
+ /^(?:ready|live|connected|live tmux sessions? discovered|\d+ sessions? live)$/iu.test(value))
228
229
  return null;
229
230
  return value;
230
231
  }
@@ -264,7 +265,12 @@ export function contextStatusPresentation(input) {
264
265
  : input.connectionState === "recovering"
265
266
  ? "Recovering"
266
267
  : "Disconnected";
267
- const activityLabel = transient ?? notification ?? connectionLabel;
268
+ const persistentProblem = notification && contextStatusTone(notification, "connected").tone === "blocked";
269
+ const activityLabel = input.connectionState !== "connected"
270
+ ? (notification ?? connectionLabel)
271
+ : persistentProblem
272
+ ? notification
273
+ : (transient ?? notification ?? connectionLabel);
268
274
  const activityTone = contextStatusTone(activityLabel, input.connectionState);
269
275
  const location = input.variant === "wide"
270
276
  ? [
@@ -1,7 +1,7 @@
1
1
  // Bun Snapshot v1, https://bun.sh/docs/test/snapshots
2
2
 
3
3
  exports[`ShellChrome OpenTUI renderer renders deterministic %sx%s compact shell chrome 1`] = `
4
- " ⌂ !❯ ▤ ± ◆ ready ⧉ web !blocked F5 palette
4
+ " ⌂ !❯ ▤ ± ◆ ⧉ web !blocked F5 palette
5
5
  tmux ┌──────────────────────────────────────────────────────────┐
6
6
  ● web │ › compact workspace │
7
7
  ○ api │● Active view and keyboard focus are selected terminal│
@@ -24,11 +24,11 @@ exports[`ShellChrome OpenTUI renderer renders deterministic %sx%s compact shell
24
24
  │ │
25
25
  │ │
26
26
  └──────────────────────────────────────────────────────────┘
27
- F5 · ^q quit F6 Sessions F7 Attention F5 Commands"
27
+ F5 · ^q quit F6 Sessions F7 Attention F10 Sidebar F5 Commands"
28
28
  `;
29
29
 
30
30
  exports[`ShellChrome OpenTUI renderer renders deterministic %sx%s standard shell chrome 1`] = `
31
- " ▸ tmux-ide ⌂ Home !❯ Terminal ▤ Files ± Diff ◆ Missions ready ⧉ web !blocked F5 palette
31
+ " ▸ tmux-ide ⌂ Home !❯ Terminal ▤ Files ± Diff ◆ Missions ⧉ web !blocked F5 palette
32
32
  tmux-ide ┌──────────────────────────────────────────────────────────────────────────────────────────┐
33
33
  ● web │ › standard workspace │
34
34
  ○ api │● Active view and keyboard focus are selected terminal│
@@ -67,11 +67,11 @@ exports[`ShellChrome OpenTUI renderer renders deterministic %sx%s standard shell
67
67
  │ │
68
68
  │ │
69
69
  └──────────────────────────────────────────────────────────────────────────────────────────┘
70
- F5 palette · ^q quit F6 Sessions F7 Attention F5 Commands"
70
+ F5 palette · ^q quit F6 Sessions F7 Attention F10 Sidebar F5 Commands"
71
71
  `;
72
72
 
73
73
  exports[`ShellChrome OpenTUI renderer renders deterministic %sx%s wide shell chrome 1`] = `
74
- " ▸ tmux-ide F1 ⌂ Home F2 !❯ Terminal F3 ▤ Files F4 ± Diff F6 ◆ Missions ready ⧉ web !blocked F5 palette
74
+ " ▸ tmux-ide F1 ⌂ Home F2 !❯ Terminal F3 ▤ Files F4 ± Diff F6 ◆ Missions ⧉ web !blocked F5 palette
75
75
  tmux-ide ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
76
76
  ● web │ › wide workspace │
77
77
  ○ api │● Active view and keyboard focus are selected terminal│
@@ -130,5 +130,5 @@ exports[`ShellChrome OpenTUI renderer renders deterministic %sx%s wide shell chr
130
130
  │ │
131
131
  │ │
132
132
  └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
133
- F5 palette · ^q quit F6 Sessions F7 Attention F5 Commands"
133
+ F5 palette · ^q quit F6 Sessions F7 Attention F10 Sidebar F5 Commands"
134
134
  `;
@@ -1,7 +1,7 @@
1
1
  // Bun Snapshot v1, https://bun.sh/docs/test/snapshots
2
2
 
3
3
  exports[`production ApplicationShellView retains shell, window strip, pane chrome, and canvas at %sx%s 1`] = `
4
- " ⌂ ●❯ live
4
+ " ⌂ ●❯ Local · main
5
5
  tmux ● main ● workin… +
6
6
  ● main ● Codex ● working ⋯
7
7
  ○ website CANONICAL-CELL
@@ -13,11 +13,11 @@ exports[`production ApplicationShellView retains shell, window strip, pane chrom
13
13
 
14
14
  …
15
15
 
16
- F6 Sessions F7 Attention F5 Commands"
16
+ F6 Sessions F7 Attention F10 Sidebar F5 Commands"
17
17
  `;
18
18
 
19
19
  exports[`production ApplicationShellView retains shell, window strip, pane chrome, and canvas at %sx%s 2`] = `
20
- " tmux-ide ⌂ Home ●❯ Terminals live
20
+ " tmux-ide ⌂ Home ●❯ Terminals Local · main
21
21
  tmux-ide ● main ● workin… + New window
22
22
  ● main ● Codex ● working ⋯
23
23
  ○ website CANONICAL-CELL
@@ -29,11 +29,11 @@ exports[`production ApplicationShellView retains shell, window strip, pane chrom
29
29
 
30
30
  …
31
31
 
32
- F6 Sessions F7 Attention F5 Commands"
32
+ F6 Sessions F7 Attention F10 Sidebar F5 Commands"
33
33
  `;
34
34
 
35
35
  exports[`production ApplicationShellView retains shell, window strip, pane chrome, and canvas at %sx%s 3`] = `
36
- " tmux-ide F1 ⌂ Home F2 ●❯ Terminals live
36
+ " tmux-ide F1 ⌂ Home F2 ●❯ Terminals Local · main
37
37
  tmux-ide ● main ● workin… + New window
38
38
  ● main ● Codex ● working ⋯
39
39
  ○ website CANONICAL-CELL
@@ -45,11 +45,11 @@ exports[`production ApplicationShellView retains shell, window strip, pane chrom
45
45
 
46
46
  …
47
47
 
48
- F6 Sessions F7 Attention F5 Commands"
48
+ F6 Sessions F7 Attention F10 Sidebar F5 Commands"
49
49
  `;
50
50
 
51
51
  exports[`production ApplicationShellView reacts palette geometry through narrow live resizes 1`] = `
52
- " ⌂ ●❯ live
52
+ " ⌂ ●❯
53
53
  t Command palette esc
54
54
  m Search commands…
55
55
  2 Home F1
@@ -61,7 +61,7 @@ exports[`production ApplicationShellView reacts palette geometry through narrow
61
61
  `;
62
62
 
63
63
  exports[`production ApplicationShellView reacts palette geometry through narrow live resizes 2`] = `
64
- " ⌂ ●❯ live
64
+ " ⌂ ●❯
65
65
  tCommand palette
66
66
  m …rch commands…
67
67
  2 Home F1w
@@ -71,7 +71,7 @@ exports[`production ApplicationShellView reacts palette geometry through narrow
71
71
  `;
72
72
 
73
73
  exports[`production ApplicationShellView preserves canonical attention while prioritizing mixed window activity 1`] = `
74
- " tmux-ide ⌂ Home ●❯ Terminals live
74
+ " tmux-ide ⌂ Home ●❯ Terminals Local · main
75
75
  tmux-ide ! main ! workin… + New window
76
76
  ● main ● Codex ● working ⋯ ! Scout ! idle ⋯
77
77
  CANONICAL-CELL CANONICAL-CELL
@@ -83,5 +83,5 @@ exports[`production ApplicationShellView preserves canonical attention while pri
83
83
 
84
84
  …
85
85
 
86
- F6 Sessions F7 Attention F5 Commands"
86
+ F6 Sessions F7 Attention F10 Sidebar F5 Commands"
87
87
  `;
@@ -10,7 +10,7 @@ import type { ApplicationAppearanceOwner } from "./application-appearance-owner.
10
10
  /* @jsxImportSource @opentui/solid */
11
11
  import type { ApplicationShellProjectionV1 } from "@tmux-ide/contracts";
12
12
  import type { Accessor, ComponentProps, JSX } from "solid-js";
13
- import { Show, createMemo } from "solid-js";
13
+ import { Show, createMemo, createSignal } from "solid-js";
14
14
 
15
15
  import { friendlySessionLabel } from "../terminal-text.ts";
16
16
  import { ApplicationShell } from "../workspace/application-shell-view.tsx";
@@ -163,6 +163,7 @@ export interface ApplicationShellViewProps {
163
163
 
164
164
  /** Pure Solid composition over the WorkspaceClient semantic projection. */
165
165
  export function ApplicationShellView(props: ApplicationShellViewProps): JSX.Element {
166
+ const [scrollback, setScrollback] = createSignal(false);
166
167
  const projection = createMemo(() => {
167
168
  const semantic = props.semantic();
168
169
  if (!semantic) return null;
@@ -395,14 +396,13 @@ export function ApplicationShellView(props: ApplicationShellViewProps): JSX.Elem
395
396
  >
396
397
  <ApplicationShell
397
398
  footerContext={props.surface() === "home" ? "home" : "terminals"}
399
+ scrollback={props.surface() === "terminals" && scrollback()}
398
400
  rightChips={
399
- props.machineLabel
401
+ props.surface() === "terminals" && props.dimensions().width >= 60
400
402
  ? [
401
403
  {
402
- id: "machine",
403
- label: `SSH ${props.machineLabel}`,
404
- context: true,
405
- textColor: props.machineColor,
404
+ id: "workspace-context",
405
+ label: ` ${props.machineLabel ?? "Local"} · ${friendlySessionLabel(shell.activeSession)} `,
406
406
  },
407
407
  ]
408
408
  : undefined
@@ -413,6 +413,11 @@ export function ApplicationShellView(props: ApplicationShellViewProps): JSX.Elem
413
413
  onHelp={() => props.onSetPaletteOpen(true, "mouse")}
414
414
  note={
415
415
  props.bootstrapNote() ??
416
+ (props.copyFeedback
417
+ ? props.copyFeedback.copied
418
+ ? "Copied"
419
+ : "Copy unavailable"
420
+ : null) ??
416
421
  (props.generationStatus() === "read-only"
417
422
  ? `Read-only input · select a pane to request control · shared tmux ${props.layout().current?.cols ?? "?"}×${props.layout().current?.rows ?? "?"}`
418
423
  : props.generationStatus())
@@ -517,6 +522,7 @@ export function ApplicationShellView(props: ApplicationShellViewProps): JSX.Elem
517
522
  >
518
523
  {(source) => (
519
524
  <ApplicationTerminalWorkspace
525
+ onScrollbackChange={setScrollback}
520
526
  layout={props.layout}
521
527
  adapter={source.adapter}
522
528
  rendererEpoch={source.rendererEpoch}
@@ -148,6 +148,7 @@ export function beginApplicationMouseIngress(
148
148
  }
149
149
 
150
150
  export interface ApplicationTerminalWorkspaceProps {
151
+ readonly onScrollbackChange?: (active: boolean) => void;
151
152
  readonly paneInteractions?: Accessor<ReadonlyMap<string, PaneInteractionProjection>>;
152
153
  readonly layout: Accessor<OpenTuiWorkspaceLayoutSnapshot>;
153
154
  readonly adapter: PaneScopedTerminalAdapter;
@@ -318,6 +319,12 @@ export function ApplicationTerminalWorkspace(props: ApplicationTerminalWorkspace
318
319
  },
319
320
  (paneId) => props.adapter.retainPaneView?.(paneId) ?? null,
320
321
  );
322
+
323
+ createEffect(() => {
324
+ const paneId = props.focusedPane;
325
+ props.onScrollbackChange?.(paneId !== null && scrollback.offset(paneId) > 0);
326
+ });
327
+ onCleanup(() => props.onScrollbackChange?.(false));
321
328
  const selectionViewport = (paneId: string, frame: OpenTuiPaneFrame) =>
322
329
  props.adapter.renderSource.supportsViewportOrigin
323
330
  ? {
@@ -31,7 +31,13 @@ export function createApplicationTransientNoteOwner(options: {
31
31
  publish(note) {
32
32
  clear();
33
33
  options.write(note);
34
- if (!note) return;
34
+ if (
35
+ !note ||
36
+ /\b(?:failed|failure|error|unavailable|disconnected|reconnecting|recovering|read.only|denied)\b/iu.test(
37
+ note,
38
+ )
39
+ )
40
+ return;
35
41
  timer = clock.setTimeout(() => {
36
42
  timer = null;
37
43
  if (options.read() === note) options.write(null);
@@ -2,6 +2,7 @@
2
2
  import { For, Show } from "solid-js";
3
3
  import {
4
4
  contextStatusPresentation,
5
+ meaningfulStatusMessage,
5
6
  developmentChromeLabel,
6
7
  shellNavigationPresentation,
7
8
  shellSurfaceTabs,
@@ -106,7 +107,14 @@ export function ShellTabBar(props: ShellTabBarProps) {
106
107
  />
107
108
  )}
108
109
  </For>
109
- <For each={props.note ? [props.note] : []}>
110
+ <For
111
+ each={
112
+ meaningfulStatusMessage(props.note) &&
113
+ /read.only|reconnect|disconnect|recover|unavailable|failed/iu.test(props.note ?? "")
114
+ ? [props.note!]
115
+ : []
116
+ }
117
+ >
110
118
  {(note) => (
111
119
  <Badge
112
120
  theme={props.theme}
@@ -117,24 +125,28 @@ export function ShellTabBar(props: ShellTabBarProps) {
117
125
  1,
118
126
  Math.min(Math.floor(props.width / 3), terminalDisplayWidth(note) + 1),
119
127
  )}
120
- tone="accent"
128
+ tone="warning"
121
129
  />
122
130
  )}
123
131
  </For>
124
132
  <For each={props.rightChips ?? []}>
125
133
  {(chip) => (
126
- <Badge
127
- theme={props.theme}
128
- label={chip.label.trim()}
129
- textColor={chip.textColor}
130
- width={Math.max(1, terminalDisplayWidth(chip.label) + (chip.context ? 1 : 0))}
131
- presentation={`${chip.label}${chip.context ? " " : ""}`}
132
- surface="header"
133
- hovered={chip.hovered}
134
- context={chip.context}
135
- attention={chip.attention}
136
- tone={chip.attention ? "warning" : chip.context ? "accent" : "neutral"}
137
- />
134
+ <text
135
+ width={Math.min(
136
+ Math.max(1, Math.floor(props.width / 3)),
137
+ terminalDisplayWidth(chip.label),
138
+ )}
139
+ flexShrink={0}
140
+ fg={
141
+ chip.attention
142
+ ? props.theme.roles.statusTone.warning
143
+ : (chip.textColor ?? props.theme.roles.text.muted)
144
+ }
145
+ bg={props.theme.roles.surfaces.panel}
146
+ overflow="hidden"
147
+ >
148
+ {clipTerminal(chip.label, Math.max(1, Math.floor(props.width / 3)))}
149
+ </text>
138
150
  )}
139
151
  </For>
140
152
  </Surface>
@@ -152,6 +164,7 @@ export interface ShellStatusStripProps {
152
164
  tool?: string | null;
153
165
  dockMode?: string | null;
154
166
  focus?: string | null;
167
+ scrollback?: boolean;
155
168
  notification: string | null;
156
169
  transient?: string | null;
157
170
  connectionState?: "connected" | "reconnecting" | "disconnected" | "recovering";
@@ -178,16 +191,19 @@ export function ContextStatusBar(props: ShellStatusStripProps) {
178
191
  const activity = () => presentation().activity;
179
192
  const showMessage = () => !/^(?:Live|\d+ sessions? live)$/iu.test(activity().label);
180
193
  const hints = () => {
181
- const candidates = /^home$/iu.test(props.mode)
182
- ? [
183
- { keys: "↑↓", label: "Select" },
184
- { keys: "Enter", label: "Open" },
185
- { keys: "/", label: "Search" },
186
- ]
187
- : [
188
- { keys: "F6", label: "Sessions" },
189
- { keys: "F7", label: "Attention" },
190
- ];
194
+ const candidates = props.scrollback
195
+ ? [{ keys: "Esc", label: "Back to live" }]
196
+ : /^home$/iu.test(props.mode)
197
+ ? [
198
+ { keys: "↑↓", label: "Select" },
199
+ { keys: "Enter", label: "Open" },
200
+ { keys: "/", label: "Search" },
201
+ ]
202
+ : [
203
+ { keys: "F6", label: "Sessions" },
204
+ { keys: "F7", label: "Attention" },
205
+ { keys: "F10", label: "Sidebar" },
206
+ ];
191
207
  let remaining = available();
192
208
  return candidates.filter((hint) => {
193
209
  const width = terminalDisplayWidth(`${hint.keys} ${hint.label}`) + 2;
@@ -385,9 +385,13 @@ export interface ContextStatusPresentation {
385
385
  readonly hints: readonly ContextStatusHint[];
386
386
  }
387
387
 
388
- function meaningfulStatusMessage(message: string | null | undefined): string | null {
388
+ export function meaningfulStatusMessage(message: string | null | undefined): string | null {
389
389
  const value = message?.trim();
390
- if (!value || /^(?:ready|live|connected)$/iu.test(value)) return null;
390
+ if (
391
+ !value ||
392
+ /^(?:ready|live|connected|live tmux sessions? discovered|\d+ sessions? live)$/iu.test(value)
393
+ )
394
+ return null;
391
395
  return value;
392
396
  }
393
397
 
@@ -445,7 +449,14 @@ export function contextStatusPresentation(input: {
445
449
  : input.connectionState === "recovering"
446
450
  ? "Recovering"
447
451
  : "Disconnected";
448
- const activityLabel = transient ?? notification ?? connectionLabel;
452
+ const persistentProblem =
453
+ notification && contextStatusTone(notification, "connected").tone === "blocked";
454
+ const activityLabel =
455
+ input.connectionState !== "connected"
456
+ ? (notification ?? connectionLabel)
457
+ : persistentProblem
458
+ ? notification
459
+ : (transient ?? notification ?? connectionLabel);
449
460
  const activityTone = contextStatusTone(activityLabel, input.connectionState);
450
461
  const location: ContextStatusLocationSegment[] =
451
462
  input.variant === "wide"
@@ -1,7 +1,7 @@
1
1
  // Bun Snapshot v1, https://bun.sh/docs/test/snapshots
2
2
 
3
3
  exports[`production ApplicationShell → WorkbenchShell OpenTUI renderer records the %sx%s collapsed dock acceptance baseline 1`] = `
4
- " ⌂ ●❯ production application ro…
4
+ " ⌂ ●❯
5
5
  tmux ▌› Native terminal canvas terminals · canvas
6
6
  ● main
7
7
  ○ website
@@ -28,7 +28,7 @@ exports[`production ApplicationShell → WorkbenchShell OpenTUI renderer records
28
28
  `;
29
29
 
30
30
  exports[`production ApplicationShell → WorkbenchShell OpenTUI renderer records the %sx%s open dock acceptance baseline 1`] = `
31
- " tmux-ide ⌂ Home ●❯ Terminals production application root
31
+ " tmux-ide ⌂ Home ●❯ Terminals
32
32
  tmux-ide ▌› Native terminal canvas terminals · canvas
33
33
  ● main
34
34
  ○ website
@@ -71,7 +71,7 @@ exports[`production ApplicationShell → WorkbenchShell OpenTUI renderer records
71
71
  `;
72
72
 
73
73
  exports[`production ApplicationShell → WorkbenchShell OpenTUI renderer records the %sx%s maximized dock acceptance baseline 1`] = `
74
- " tmux-ide F1 ⌂ Home F2 ●❯ Terminals production application root
74
+ " tmux-ide F1 ⌂ Home F2 ●❯ Terminals
75
75
  tmux-ide ▸ tools F3 ▤ Files F4 ± Changes F6 ●◆ Missions F9 ◷ Activity ▾ close ▣ restore
76
76
  ● main ▌○ Native Missions surface maximized
77
77
  ○ website
@@ -11,6 +11,7 @@ import type { SemanticThemeSnapshot } from "../theme.ts";
11
11
  import type { ApplicationShellProjection } from "./application-shell.ts";
12
12
 
13
13
  export interface ApplicationShellProps {
14
+ scrollback?: boolean;
14
15
  footerContext?: "home" | "terminals";
15
16
  theme: SemanticThemeSnapshot;
16
17
  projection: ApplicationShellProjection;
@@ -144,6 +145,7 @@ export function ApplicationShell(props: ApplicationShellProps) {
144
145
  props.showToolStatus === false ? null : props.projection.semantic.bottomDock.mode
145
146
  }
146
147
  focus={props.focusLabel ?? applicationShellFocusLabel(props.projection)}
148
+ scrollback={props.scrollback}
147
149
  notification={props.projection.semantic.statusStrip.message}
148
150
  transient={props.note}
149
151
  connectionState={props.projection.semantic.statusStrip.state}