tmux-ide 2.9.0-beta.36 → 2.9.0-beta.38
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 +1 -1
- package/package.json +1 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +14 -9
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +5 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-transient-note-owner.js +2 -1
- package/packages/daemon/dist/tui/mirror/shell-chrome.js +9 -3
- package/packages/daemon/src/tui/mirror/__snapshots__/shell-chrome-renderer.test.tsx.snap +6 -6
- package/packages/daemon/src/tui/mirror/runtime/__snapshots__/application-shell-view-renderer.test.tsx.snap +10 -10
- package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +44 -29
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +22 -6
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +7 -0
- package/packages/daemon/src/tui/mirror/runtime/application-transient-note-owner.ts +7 -1
- package/packages/daemon/src/tui/mirror/shell-chrome-view.tsx +61 -25
- package/packages/daemon/src/tui/mirror/shell-chrome.ts +14 -3
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/application-shell-renderer.test.tsx.snap +3 -3
- package/packages/daemon/src/tui/mirror/workspace/application-shell-view.tsx +4 -0
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.
|
|
11282
|
+
version: "2.9.0-beta.38",
|
|
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
|
@@ -6,6 +6,7 @@ import { fleetConnectionMessage, } from "@tmux-ide/daemon-client/fleet-connectio
|
|
|
6
6
|
import { terminalAgentStatusLabel } from "./application-terminal-workspace-policy.js";
|
|
7
7
|
import { For, Show, createEffect, createMemo, createSignal, } from "solid-js";
|
|
8
8
|
import { clipTerminal, friendlySessionLabel } from "../terminal-text.js";
|
|
9
|
+
import { KeyHint } from "../ui/key-hint.jsx";
|
|
9
10
|
import { NavigationRow } from "../ui/navigation-row.jsx";
|
|
10
11
|
import { Surface } from "../ui/surface.jsx";
|
|
11
12
|
import { useKeyboardRoute } from "../ui/keyboard-router.jsx";
|
|
@@ -302,10 +303,14 @@ export function ApplicationMachineSidebar(props) {
|
|
|
302
303
|
return true;
|
|
303
304
|
});
|
|
304
305
|
return (<Surface id="application-machine-sidebar" theme={props.theme} variant="panel" width={props.width} height={props.height} flexShrink={0} flexDirection="column" overflow="hidden">
|
|
305
|
-
<
|
|
306
|
-
{" "}
|
|
307
|
-
{
|
|
308
|
-
|
|
306
|
+
<box height={1} flexShrink={0} flexDirection="row" overflow="hidden">
|
|
307
|
+
<text fg={props.theme.roles.text.secondary}>{" Machines"}</text>
|
|
308
|
+
<box flexGrow={1}/>
|
|
309
|
+
<Show when={props.model.tabs?.().length && props.width >= 28}>
|
|
310
|
+
<KeyHint theme={props.theme} keys="F9" label="Tabs" quiet/>
|
|
311
|
+
</Show>
|
|
312
|
+
<KeyHint theme={props.theme} keys="?" label={props.width >= 20 ? "Help" : undefined} quiet button onPress={() => setShowHelp(!showHelp())}/>
|
|
313
|
+
</box>
|
|
309
314
|
<For each={visibleTabs()}>
|
|
310
315
|
{(tab) => (<box height={1} flexDirection="row">
|
|
311
316
|
<NavigationRow theme={props.theme} width={Math.max(1, props.width - 4)} id={`fleet-tab:${tab.key}`} label={`${tab.hostLabel} / ${tab.label}`} marker={tab.active ? "●" : "○"} detail={tab.available ? "" : "offline"} focused={false} onActivate={() => props.model.onOpenTab?.(tab.key)}/>
|
|
@@ -417,10 +422,10 @@ export function ApplicationMachineSidebar(props) {
|
|
|
417
422
|
</text>
|
|
418
423
|
</Show>
|
|
419
424
|
<Show when={props.model.onOpenSwitcher}>
|
|
420
|
-
<
|
|
425
|
+
<KeyHint theme={props.theme} keys="F6" label="Sessions" width={props.width} quiet button onPress={() => props.model.onOpenSwitcher?.()}/>
|
|
421
426
|
</Show>
|
|
422
427
|
<Show when={props.model.onOpenAttention}>
|
|
423
|
-
<
|
|
428
|
+
<KeyHint theme={props.theme} keys="F7" label={`Attention (${props.model.groups().reduce((sum, group) => sum + (group.state === "ready" ? (group.agents ?? []).filter((agent) => agent.attention && !agent.disabled).length : 0), 0)})`} width={props.width} quiet button onPress={() => props.model.onOpenAttention?.()}/>
|
|
424
429
|
</Show>
|
|
425
430
|
<Show when={controlsHeight() > 0 && controlGroup()}>
|
|
426
431
|
{(group) => (<>
|
|
@@ -429,12 +434,12 @@ export function ApplicationMachineSidebar(props) {
|
|
|
429
434
|
? fleetConnectionMessage(group().diagnostic)
|
|
430
435
|
: connectionDetail(group())}
|
|
431
436
|
</text>
|
|
432
|
-
<
|
|
433
|
-
<
|
|
437
|
+
<KeyHint theme={props.theme} keys="R" label="Retry connection" width={props.width} quiet button onPress={() => props.model.onRetryMachine?.(group().id)}/>
|
|
438
|
+
<KeyHint theme={props.theme} keys="D" label="Disconnect" width={props.width} quiet button onPress={() => props.model.onDisconnectMachine?.(group().id)}/>
|
|
434
439
|
</>)}
|
|
435
440
|
</Show>
|
|
436
441
|
<For each={props.model.onAddMachine ? [props.model.onAddMachine] : []}>
|
|
437
|
-
{(add) => (<
|
|
442
|
+
{(add) => (<KeyHint theme={props.theme} keys="A" label="Add machine" width={props.width} quiet button onPress={add}/>)}
|
|
438
443
|
</For>
|
|
439
444
|
</Surface>);
|
|
440
445
|
}
|
|
@@ -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 ||
|
|
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
|
|
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
|
-
" ⌂ !❯ ▤ ± ◆
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
" ⌂ ●❯
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
" ⌂ ●❯
|
|
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
|
-
" ⌂ ●❯
|
|
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
|
|
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
|
|
86
|
+
F6 Sessions F7 Attention F10 Sidebar F5 Commands"
|
|
87
87
|
`;
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from "solid-js";
|
|
22
22
|
import { clipTerminal, friendlySessionLabel } from "../terminal-text.ts";
|
|
23
23
|
import type { SemanticThemeSnapshot } from "../theme.ts";
|
|
24
|
+
import { KeyHint } from "../ui/key-hint.tsx";
|
|
24
25
|
import { NavigationRow } from "../ui/navigation-row.tsx";
|
|
25
26
|
import { Surface } from "../ui/surface.tsx";
|
|
26
27
|
import { useKeyboardRoute } from "../ui/keyboard-router.tsx";
|
|
@@ -458,10 +459,21 @@ export function ApplicationMachineSidebar(props: {
|
|
|
458
459
|
flexDirection="column"
|
|
459
460
|
overflow="hidden"
|
|
460
461
|
>
|
|
461
|
-
<
|
|
462
|
-
{" "}
|
|
463
|
-
{
|
|
464
|
-
|
|
462
|
+
<box height={1} flexShrink={0} flexDirection="row" overflow="hidden">
|
|
463
|
+
<text fg={props.theme.roles.text.secondary}>{" Machines"}</text>
|
|
464
|
+
<box flexGrow={1} />
|
|
465
|
+
<Show when={props.model.tabs?.().length && props.width >= 28}>
|
|
466
|
+
<KeyHint theme={props.theme} keys="F9" label="Tabs" quiet />
|
|
467
|
+
</Show>
|
|
468
|
+
<KeyHint
|
|
469
|
+
theme={props.theme}
|
|
470
|
+
keys="?"
|
|
471
|
+
label={props.width >= 20 ? "Help" : undefined}
|
|
472
|
+
quiet
|
|
473
|
+
button
|
|
474
|
+
onPress={() => setShowHelp(!showHelp())}
|
|
475
|
+
/>
|
|
476
|
+
</box>
|
|
465
477
|
<For each={visibleTabs()}>
|
|
466
478
|
{(tab) => (
|
|
467
479
|
<box height={1} flexDirection="row">
|
|
@@ -653,24 +665,25 @@ export function ApplicationMachineSidebar(props: {
|
|
|
653
665
|
</text>
|
|
654
666
|
</Show>
|
|
655
667
|
<Show when={props.model.onOpenSwitcher}>
|
|
656
|
-
<
|
|
668
|
+
<KeyHint
|
|
657
669
|
theme={props.theme}
|
|
658
|
-
|
|
670
|
+
keys="F6"
|
|
659
671
|
label="Sessions"
|
|
660
|
-
detail="F6"
|
|
661
|
-
marker=" "
|
|
662
672
|
width={props.width}
|
|
663
|
-
|
|
673
|
+
quiet
|
|
674
|
+
button
|
|
675
|
+
onPress={() => props.model.onOpenSwitcher?.()}
|
|
664
676
|
/>
|
|
665
677
|
</Show>
|
|
666
678
|
<Show when={props.model.onOpenAttention}>
|
|
667
|
-
<
|
|
679
|
+
<KeyHint
|
|
668
680
|
theme={props.theme}
|
|
669
|
-
|
|
670
|
-
label={`Attention (${props.model.groups().reduce((sum, group) => sum + (group.state === "ready" ? (group.agents ?? []).filter((agent) => agent.attention && !agent.disabled).length : 0), 0)})
|
|
671
|
-
marker="!"
|
|
681
|
+
keys="F7"
|
|
682
|
+
label={`Attention (${props.model.groups().reduce((sum, group) => sum + (group.state === "ready" ? (group.agents ?? []).filter((agent) => agent.attention && !agent.disabled).length : 0), 0)})`}
|
|
672
683
|
width={props.width}
|
|
673
|
-
|
|
684
|
+
quiet
|
|
685
|
+
button
|
|
686
|
+
onPress={() => props.model.onOpenAttention?.()}
|
|
674
687
|
/>
|
|
675
688
|
</Show>
|
|
676
689
|
<Show when={controlsHeight() > 0 && controlGroup()}>
|
|
@@ -681,35 +694,37 @@ export function ApplicationMachineSidebar(props: {
|
|
|
681
694
|
? fleetConnectionMessage(group().diagnostic!)
|
|
682
695
|
: connectionDetail(group())}
|
|
683
696
|
</text>
|
|
684
|
-
<
|
|
697
|
+
<KeyHint
|
|
685
698
|
theme={props.theme}
|
|
686
|
-
|
|
687
|
-
label="Retry connection
|
|
688
|
-
marker="↻"
|
|
699
|
+
keys="R"
|
|
700
|
+
label="Retry connection"
|
|
689
701
|
width={props.width}
|
|
690
|
-
|
|
702
|
+
quiet
|
|
703
|
+
button
|
|
704
|
+
onPress={() => props.model.onRetryMachine?.(group().id)}
|
|
691
705
|
/>
|
|
692
|
-
<
|
|
706
|
+
<KeyHint
|
|
693
707
|
theme={props.theme}
|
|
694
|
-
|
|
695
|
-
label="Disconnect
|
|
696
|
-
marker="×"
|
|
708
|
+
keys="D"
|
|
709
|
+
label="Disconnect"
|
|
697
710
|
width={props.width}
|
|
698
|
-
|
|
711
|
+
quiet
|
|
712
|
+
button
|
|
713
|
+
onPress={() => props.model.onDisconnectMachine?.(group().id)}
|
|
699
714
|
/>
|
|
700
715
|
</>
|
|
701
716
|
)}
|
|
702
717
|
</Show>
|
|
703
718
|
<For each={props.model.onAddMachine ? [props.model.onAddMachine] : []}>
|
|
704
719
|
{(add) => (
|
|
705
|
-
<
|
|
720
|
+
<KeyHint
|
|
706
721
|
theme={props.theme}
|
|
707
|
-
|
|
722
|
+
keys="A"
|
|
708
723
|
label="Add machine"
|
|
709
|
-
detail="A"
|
|
710
|
-
marker=" "
|
|
711
724
|
width={props.width}
|
|
712
|
-
|
|
725
|
+
quiet
|
|
726
|
+
button
|
|
727
|
+
onPress={add}
|
|
713
728
|
/>
|
|
714
729
|
)}
|
|
715
730
|
</For>
|
|
@@ -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;
|
|
@@ -394,15 +395,24 @@ export function ApplicationShellView(props: ApplicationShellViewProps): JSX.Elem
|
|
|
394
395
|
}}
|
|
395
396
|
>
|
|
396
397
|
<ApplicationShell
|
|
398
|
+
onFooterAction={(key) => {
|
|
399
|
+
props.machineSidebar?.onBlur?.();
|
|
400
|
+
if (key === "F6") props.machineSidebar?.onOpenSwitcher?.();
|
|
401
|
+
else if (key === "F7") props.machineSidebar?.onOpenAttention?.();
|
|
402
|
+
else
|
|
403
|
+
props.onPaletteActivate?.(
|
|
404
|
+
props.sidebarVisible === false ? "show-sidebar" : "hide-sidebar",
|
|
405
|
+
"mouse",
|
|
406
|
+
);
|
|
407
|
+
}}
|
|
397
408
|
footerContext={props.surface() === "home" ? "home" : "terminals"}
|
|
409
|
+
scrollback={props.surface() === "terminals" && scrollback()}
|
|
398
410
|
rightChips={
|
|
399
|
-
props.
|
|
411
|
+
props.surface() === "terminals" && props.dimensions().width >= 60
|
|
400
412
|
? [
|
|
401
413
|
{
|
|
402
|
-
id: "
|
|
403
|
-
label: `
|
|
404
|
-
context: true,
|
|
405
|
-
textColor: props.machineColor,
|
|
414
|
+
id: "workspace-context",
|
|
415
|
+
label: ` ${props.machineLabel ?? "Local"} · ${friendlySessionLabel(shell.activeSession)} `,
|
|
406
416
|
},
|
|
407
417
|
]
|
|
408
418
|
: undefined
|
|
@@ -413,6 +423,11 @@ export function ApplicationShellView(props: ApplicationShellViewProps): JSX.Elem
|
|
|
413
423
|
onHelp={() => props.onSetPaletteOpen(true, "mouse")}
|
|
414
424
|
note={
|
|
415
425
|
props.bootstrapNote() ??
|
|
426
|
+
(props.copyFeedback
|
|
427
|
+
? props.copyFeedback.copied
|
|
428
|
+
? "Copied"
|
|
429
|
+
: "Copy unavailable"
|
|
430
|
+
: null) ??
|
|
416
431
|
(props.generationStatus() === "read-only"
|
|
417
432
|
? `Read-only input · select a pane to request control · shared tmux ${props.layout().current?.cols ?? "?"}×${props.layout().current?.rows ?? "?"}`
|
|
418
433
|
: props.generationStatus())
|
|
@@ -517,6 +532,7 @@ export function ApplicationShellView(props: ApplicationShellViewProps): JSX.Elem
|
|
|
517
532
|
>
|
|
518
533
|
{(source) => (
|
|
519
534
|
<ApplicationTerminalWorkspace
|
|
535
|
+
onScrollbackChange={setScrollback}
|
|
520
536
|
layout={props.layout}
|
|
521
537
|
adapter={source.adapter}
|
|
522
538
|
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 (
|
|
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
|
|
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="
|
|
128
|
+
tone="warning"
|
|
121
129
|
/>
|
|
122
130
|
)}
|
|
123
131
|
</For>
|
|
124
132
|
<For each={props.rightChips ?? []}>
|
|
125
133
|
{(chip) => (
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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,8 @@ export interface ShellStatusStripProps {
|
|
|
152
164
|
tool?: string | null;
|
|
153
165
|
dockMode?: string | null;
|
|
154
166
|
focus?: string | null;
|
|
167
|
+
onFooterAction?: (key: "F6" | "F7" | "F10") => void;
|
|
168
|
+
scrollback?: boolean;
|
|
155
169
|
notification: string | null;
|
|
156
170
|
transient?: string | null;
|
|
157
171
|
connectionState?: "connected" | "reconnecting" | "disconnected" | "recovering";
|
|
@@ -178,16 +192,19 @@ export function ContextStatusBar(props: ShellStatusStripProps) {
|
|
|
178
192
|
const activity = () => presentation().activity;
|
|
179
193
|
const showMessage = () => !/^(?:Live|\d+ sessions? live)$/iu.test(activity().label);
|
|
180
194
|
const hints = () => {
|
|
181
|
-
const candidates =
|
|
182
|
-
? [
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
195
|
+
const candidates = props.scrollback
|
|
196
|
+
? [{ keys: "Esc", label: "Back to live" }]
|
|
197
|
+
: /^home$/iu.test(props.mode)
|
|
198
|
+
? [
|
|
199
|
+
{ keys: "↑↓", label: "Select" },
|
|
200
|
+
{ keys: "Enter", label: "Open" },
|
|
201
|
+
{ keys: "/", label: "Search" },
|
|
202
|
+
]
|
|
203
|
+
: [
|
|
204
|
+
{ keys: "F6", label: "Sessions" },
|
|
205
|
+
{ keys: "F7", label: "Attention" },
|
|
206
|
+
{ keys: "F10", label: "Sidebar" },
|
|
207
|
+
];
|
|
191
208
|
let remaining = available();
|
|
192
209
|
return candidates.filter((hint) => {
|
|
193
210
|
const width = terminalDisplayWidth(`${hint.keys} ${hint.label}`) + 2;
|
|
@@ -203,7 +220,26 @@ export function ContextStatusBar(props: ShellStatusStripProps) {
|
|
|
203
220
|
when={showMessage()}
|
|
204
221
|
fallback={
|
|
205
222
|
<For each={hints()}>
|
|
206
|
-
{(hint) =>
|
|
223
|
+
{(hint) => {
|
|
224
|
+
const action = () =>
|
|
225
|
+
hint.keys === "F6" || hint.keys === "F7" || hint.keys === "F10"
|
|
226
|
+
? hint.keys
|
|
227
|
+
: null;
|
|
228
|
+
return (
|
|
229
|
+
<KeyHint
|
|
230
|
+
theme={props.theme}
|
|
231
|
+
keys={hint.keys}
|
|
232
|
+
label={hint.label}
|
|
233
|
+
quiet
|
|
234
|
+
button={action() !== null}
|
|
235
|
+
onPress={
|
|
236
|
+
action() && props.onFooterAction
|
|
237
|
+
? () => props.onFooterAction?.(action()!)
|
|
238
|
+
: undefined
|
|
239
|
+
}
|
|
240
|
+
/>
|
|
241
|
+
);
|
|
242
|
+
}}
|
|
207
243
|
</For>
|
|
208
244
|
}
|
|
209
245
|
>
|
|
@@ -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 (
|
|
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
|
|
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
|
-
" ⌂ ●❯
|
|
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
|
|
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
|
|
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,8 @@ import type { SemanticThemeSnapshot } from "../theme.ts";
|
|
|
11
11
|
import type { ApplicationShellProjection } from "./application-shell.ts";
|
|
12
12
|
|
|
13
13
|
export interface ApplicationShellProps {
|
|
14
|
+
onFooterAction?: (key: "F6" | "F7" | "F10") => void;
|
|
15
|
+
scrollback?: boolean;
|
|
14
16
|
footerContext?: "home" | "terminals";
|
|
15
17
|
theme: SemanticThemeSnapshot;
|
|
16
18
|
projection: ApplicationShellProjection;
|
|
@@ -144,6 +146,8 @@ export function ApplicationShell(props: ApplicationShellProps) {
|
|
|
144
146
|
props.showToolStatus === false ? null : props.projection.semantic.bottomDock.mode
|
|
145
147
|
}
|
|
146
148
|
focus={props.focusLabel ?? applicationShellFocusLabel(props.projection)}
|
|
149
|
+
onFooterAction={props.onFooterAction}
|
|
150
|
+
scrollback={props.scrollback}
|
|
147
151
|
notification={props.projection.semantic.statusStrip.message}
|
|
148
152
|
transient={props.note}
|
|
149
153
|
connectionState={props.projection.semantic.statusStrip.state}
|