tmux-ide 2.9.0-beta.40 → 2.9.0-beta.44
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/README.md +5 -0
- package/bin/cli.js +6 -3
- package/package.json +6 -3
- package/packages/daemon/dist/tui/mirror/runtime/application-fleet-tabs.js +29 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-roster.jsx +52 -98
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agents-owner.js +1 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +45 -7
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +99 -114
- package/packages/daemon/dist/tui/mirror/runtime/application-palette-command-owner.js +2 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-palette-input.js +1 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-shell-home.jsx +43 -13
- package/packages/daemon/dist/tui/mirror/shell-chrome.js +5 -7
- package/packages/daemon/dist/tui/mirror/ui/agent-row.jsx +40 -0
- package/packages/daemon/dist/tui/mirror/ui/agent-status-marker.js +6 -5
- package/packages/daemon/dist/tui/mirror/ui/home-agent-search.jsx +8 -5
- package/packages/daemon/dist/tui/mirror/ui/icons.js +9 -0
- package/packages/daemon/dist/tui/mirror/ui/index.js +3 -0
- package/packages/daemon/dist/tui/mirror/ui/input-surface.jsx +11 -0
- package/packages/daemon/dist/tui/mirror/ui/navigation-row.jsx +7 -2
- package/packages/daemon/dist/tui/mirror/ui/section-heading.jsx +6 -0
- package/packages/daemon/dist/tui/mirror/ui/session-row.jsx +71 -0
- package/packages/daemon/dist/tui/mirror/ui/tabs.jsx +22 -5
- package/packages/daemon/dist/tui/mirror/ui/working-sessions.jsx +61 -0
- package/packages/daemon/dist/tui/mirror/workspace/application-action-descriptions.js +51 -0
- package/packages/daemon/dist/tui/mirror/workspace/application-command-description.js +10 -3
- package/packages/daemon/dist/tui/mirror/workspace/application-shortcuts.js +15 -7
- package/packages/daemon/dist/tui/mirror/workspace/palette-list-layout.js +1 -1
- package/packages/daemon/src/tui/mirror/__snapshots__/shell-chrome-renderer.test.tsx.snap +3 -3
- package/packages/daemon/src/tui/mirror/runtime/__snapshots__/application-shell-view-renderer.test.tsx.snap +8 -8
- package/packages/daemon/src/tui/mirror/runtime/application-fleet-tabs.ts +34 -2
- package/packages/daemon/src/tui/mirror/runtime/application-home-agent-roster.tsx +122 -190
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents-owner.ts +1 -0
- package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +44 -7
- package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +133 -169
- package/packages/daemon/src/tui/mirror/runtime/application-palette-command-owner.ts +4 -2
- package/packages/daemon/src/tui/mirror/runtime/application-palette-input.ts +1 -0
- package/packages/daemon/src/tui/mirror/runtime/application-reference-sheet.tsx +41 -8
- package/packages/daemon/src/tui/mirror/runtime/application-shell-home.tsx +67 -17
- package/packages/daemon/src/tui/mirror/runtime/application-shell-overlays.tsx +2 -1
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +2 -0
- package/packages/daemon/src/tui/mirror/shell-chrome.ts +4 -5
- package/packages/daemon/src/tui/mirror/ui/agent-row.tsx +92 -0
- package/packages/daemon/src/tui/mirror/ui/agent-status-marker.ts +7 -5
- package/packages/daemon/src/tui/mirror/ui/home-agent-search.tsx +17 -17
- package/packages/daemon/src/tui/mirror/ui/icons.ts +9 -0
- package/packages/daemon/src/tui/mirror/ui/index.ts +4 -0
- package/packages/daemon/src/tui/mirror/ui/input-surface.tsx +39 -0
- package/packages/daemon/src/tui/mirror/ui/navigation-row.tsx +8 -1
- package/packages/daemon/src/tui/mirror/ui/overlay-search-field.tsx +5 -12
- package/packages/daemon/src/tui/mirror/ui/section-heading.tsx +15 -0
- package/packages/daemon/src/tui/mirror/ui/session-row.tsx +122 -0
- package/packages/daemon/src/tui/mirror/ui/tabs.tsx +30 -7
- package/packages/daemon/src/tui/mirror/ui/working-sessions.tsx +99 -0
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/application-shell-renderer.test.tsx.snap +3 -3
- package/packages/daemon/src/tui/mirror/workspace/application-action-descriptions.ts +52 -0
- package/packages/daemon/src/tui/mirror/workspace/application-command-description.ts +12 -3
- package/packages/daemon/src/tui/mirror/workspace/application-shortcuts.ts +19 -7
- package/packages/daemon/src/tui/mirror/workspace/palette-list-layout.ts +1 -1
package/README.md
CHANGED
|
@@ -146,6 +146,11 @@ environment.
|
|
|
146
146
|
|
|
147
147
|
Regenerate the production-renderer demo with `pnpm demo:tui`.
|
|
148
148
|
|
|
149
|
+
Explore real Home, sidebar, Help, pane-header and footer components with `pnpm gallery:tui`. The
|
|
150
|
+
development gallery uses fixture data and local actions, so it does not connect
|
|
151
|
+
to your daemon or change live tmux sessions. See the
|
|
152
|
+
[gallery controls and checks](scripts/tui-gallery/README.md).
|
|
153
|
+
|
|
149
154
|
- [Documentation](https://github.com/wavyrai/tmux-ide/tree/main/docs)
|
|
150
155
|
- [Contributing](CONTRIBUTING.md)
|
|
151
156
|
- [Release checklist](RELEASE.md)
|
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.44",
|
|
11283
11283
|
description: "A visual, agent-aware IDE for any tmux session, with optional workspace presets",
|
|
11284
11284
|
type: "module",
|
|
11285
11285
|
bin: {
|
|
@@ -11339,7 +11339,7 @@ var require_package = __commonJS({
|
|
|
11339
11339
|
"test:pack-installed": "node scripts/pack-check-run.mjs",
|
|
11340
11340
|
"release:opentui:check": "node scripts/opentui-release-check.mjs",
|
|
11341
11341
|
"check:native-deps": "node packages/daemon/scripts/check-native-deps.mjs",
|
|
11342
|
-
check: "pnpm run lint:workspace && pnpm run check:control-bytes && pnpm run format:check && pnpm run typecheck:workspace && pnpm run test:postinstall && pnpm run test:portable-release-contract && pnpm run test:benchmark-comparative && pnpm run test:contracts-initializer-purity && pnpm run test:unit && pnpm run test:tui-testdrive && pnpm run test:product-test-rig && pnpm run test:daemon-bun && pnpm run test:tui-renderer && pnpm run test:workbench-dock-package && pnpm run test:pane-frame-package && pnpm run docs:build && pnpm run pack:check && pnpm run test:pack-installed && pnpm run check:native-deps && pnpm run smoke:desktop",
|
|
11342
|
+
check: "pnpm run lint:workspace && pnpm run check:control-bytes && pnpm run format:check && pnpm run typecheck:workspace && pnpm run test:postinstall && pnpm run test:portable-release-contract && pnpm run test:benchmark-comparative && pnpm run test:contracts-initializer-purity && pnpm run test:unit && pnpm run test:tui-testdrive && pnpm run test:product-test-rig && pnpm run test:daemon-bun && pnpm run test:tui-renderer && pnpm run typecheck:tui-gallery && pnpm run test:tui-gallery && pnpm run test:workbench-dock-package && pnpm run test:pane-frame-package && pnpm run docs:build && pnpm run pack:check && pnpm run test:pack-installed && pnpm run check:native-deps && pnpm run smoke:desktop",
|
|
11343
11343
|
postinstall: "node scripts/postinstall.js",
|
|
11344
11344
|
docs: "turbo run dev --filter=@tmux-ide/docs",
|
|
11345
11345
|
"demo:tui": "bun --preload @opentui/solid/preload docs/scripts/render-tui-demo.tsx",
|
|
@@ -11374,7 +11374,10 @@ var require_package = __commonJS({
|
|
|
11374
11374
|
"test:postinstall": "node --test scripts/postinstall-daemon-upgrade.test.mjs",
|
|
11375
11375
|
"test:development-isolation": "tsx scripts/lib/development-isolation-qualification.ts",
|
|
11376
11376
|
"test:development-isolation-unit": "node --test scripts/lib/development-isolation-resources.test.mjs",
|
|
11377
|
-
"test:development-ci": "node --test scripts/lib/development-ci.test.mjs"
|
|
11377
|
+
"test:development-ci": "node --test scripts/lib/development-ci.test.mjs",
|
|
11378
|
+
"gallery:tui": "bun --preload @opentui/solid/preload scripts/tui-gallery/index.tsx",
|
|
11379
|
+
"test:tui-gallery": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./scripts/tui-gallery/gallery.test.tsx",
|
|
11380
|
+
"typecheck:tui-gallery": "tsc --noEmit -p scripts/tsconfig.tui-gallery.json"
|
|
11378
11381
|
},
|
|
11379
11382
|
keywords: [
|
|
11380
11383
|
"tmux",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tmux-ide",
|
|
3
|
-
"version": "2.9.0-beta.
|
|
3
|
+
"version": "2.9.0-beta.44",
|
|
4
4
|
"description": "A visual, agent-aware IDE for any tmux session, with optional workspace presets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"test:pack-installed": "node scripts/pack-check-run.mjs",
|
|
61
61
|
"release:opentui:check": "node scripts/opentui-release-check.mjs",
|
|
62
62
|
"check:native-deps": "node packages/daemon/scripts/check-native-deps.mjs",
|
|
63
|
-
"check": "pnpm run lint:workspace && pnpm run check:control-bytes && pnpm run format:check && pnpm run typecheck:workspace && pnpm run test:postinstall && pnpm run test:portable-release-contract && pnpm run test:benchmark-comparative && pnpm run test:contracts-initializer-purity && pnpm run test:unit && pnpm run test:tui-testdrive && pnpm run test:product-test-rig && pnpm run test:daemon-bun && pnpm run test:tui-renderer && pnpm run test:workbench-dock-package && pnpm run test:pane-frame-package && pnpm run docs:build && pnpm run pack:check && pnpm run test:pack-installed && pnpm run check:native-deps && pnpm run smoke:desktop",
|
|
63
|
+
"check": "pnpm run lint:workspace && pnpm run check:control-bytes && pnpm run format:check && pnpm run typecheck:workspace && pnpm run test:postinstall && pnpm run test:portable-release-contract && pnpm run test:benchmark-comparative && pnpm run test:contracts-initializer-purity && pnpm run test:unit && pnpm run test:tui-testdrive && pnpm run test:product-test-rig && pnpm run test:daemon-bun && pnpm run test:tui-renderer && pnpm run typecheck:tui-gallery && pnpm run test:tui-gallery && pnpm run test:workbench-dock-package && pnpm run test:pane-frame-package && pnpm run docs:build && pnpm run pack:check && pnpm run test:pack-installed && pnpm run check:native-deps && pnpm run smoke:desktop",
|
|
64
64
|
"postinstall": "node scripts/postinstall.js",
|
|
65
65
|
"docs": "turbo run dev --filter=@tmux-ide/docs",
|
|
66
66
|
"demo:tui": "bun --preload @opentui/solid/preload docs/scripts/render-tui-demo.tsx",
|
|
@@ -95,7 +95,10 @@
|
|
|
95
95
|
"test:postinstall": "node --test scripts/postinstall-daemon-upgrade.test.mjs",
|
|
96
96
|
"test:development-isolation": "tsx scripts/lib/development-isolation-qualification.ts",
|
|
97
97
|
"test:development-isolation-unit": "node --test scripts/lib/development-isolation-resources.test.mjs",
|
|
98
|
-
"test:development-ci": "node --test scripts/lib/development-ci.test.mjs"
|
|
98
|
+
"test:development-ci": "node --test scripts/lib/development-ci.test.mjs",
|
|
99
|
+
"gallery:tui": "bun --preload @opentui/solid/preload scripts/tui-gallery/index.tsx",
|
|
100
|
+
"test:tui-gallery": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./scripts/tui-gallery/gallery.test.tsx",
|
|
101
|
+
"typecheck:tui-gallery": "tsc --noEmit -p scripts/tsconfig.tui-gallery.json"
|
|
99
102
|
},
|
|
100
103
|
"keywords": [
|
|
101
104
|
"tmux",
|
|
@@ -1,21 +1,43 @@
|
|
|
1
1
|
/** Retained targets, with at most one active terminal owner. Suspended tabs own no streams. */
|
|
2
2
|
export function createFleetTabs(options) {
|
|
3
3
|
const tabs = new Map();
|
|
4
|
+
const unread = new Set();
|
|
5
|
+
const observations = new Map();
|
|
4
6
|
let active = null;
|
|
5
7
|
let pending = null;
|
|
6
8
|
let epoch = 0;
|
|
7
9
|
let disposed = false;
|
|
8
10
|
return {
|
|
9
|
-
snapshot: () => ({ tabs: [...tabs.values()], active }),
|
|
11
|
+
snapshot: () => ({ tabs: [...tabs.values()], active, unread: [...unread] }),
|
|
12
|
+
observe(key, agents) {
|
|
13
|
+
if (disposed || !tabs.has(key))
|
|
14
|
+
return;
|
|
15
|
+
if (!agents) {
|
|
16
|
+
observations.delete(key);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const previous = observations.get(key);
|
|
20
|
+
observations.set(key, new Map(agents.map((agent) => [agent.id, agent.activity])));
|
|
21
|
+
if (active !== key &&
|
|
22
|
+
!unread.has(key) &&
|
|
23
|
+
agents.some((agent) => agent.activity === "complete" && previous?.get(agent.id) === "running")) {
|
|
24
|
+
unread.add(key);
|
|
25
|
+
options.publish();
|
|
26
|
+
}
|
|
27
|
+
},
|
|
10
28
|
remember(target) {
|
|
11
29
|
if (disposed)
|
|
12
30
|
return;
|
|
13
31
|
if (!tabs.has(target.key) && tabs.size >= 8) {
|
|
14
32
|
const removable = [...tabs.keys()].find((key) => key !== active);
|
|
15
|
-
if (removable)
|
|
33
|
+
if (removable) {
|
|
16
34
|
tabs.delete(removable);
|
|
35
|
+
unread.delete(removable);
|
|
36
|
+
observations.delete(removable);
|
|
37
|
+
}
|
|
17
38
|
}
|
|
18
39
|
tabs.set(target.key, Object.freeze({ ...target }));
|
|
40
|
+
unread.delete(target.key);
|
|
19
41
|
active = target.key;
|
|
20
42
|
options.publish();
|
|
21
43
|
},
|
|
@@ -41,6 +63,7 @@ export function createFleetTabs(options) {
|
|
|
41
63
|
options.unavailable();
|
|
42
64
|
return false;
|
|
43
65
|
}
|
|
66
|
+
unread.delete(key);
|
|
44
67
|
active = key;
|
|
45
68
|
options.publish();
|
|
46
69
|
return true;
|
|
@@ -55,6 +78,8 @@ export function createFleetTabs(options) {
|
|
|
55
78
|
pending = null;
|
|
56
79
|
}
|
|
57
80
|
tabs.delete(key);
|
|
81
|
+
unread.delete(key);
|
|
82
|
+
observations.delete(key);
|
|
58
83
|
options.publish();
|
|
59
84
|
},
|
|
60
85
|
suspend() {
|
|
@@ -70,6 +95,8 @@ export function createFleetTabs(options) {
|
|
|
70
95
|
disposed = true;
|
|
71
96
|
epoch++;
|
|
72
97
|
tabs.clear();
|
|
98
|
+
unread.clear();
|
|
99
|
+
observations.clear();
|
|
73
100
|
active = null;
|
|
74
101
|
},
|
|
75
102
|
};
|
|
@@ -1,29 +1,14 @@
|
|
|
1
1
|
/* @jsxImportSource @opentui/solid */
|
|
2
|
+
import { HOME_ACTIONS, HOME_ACTIVITY_ACTIONS, } from "../workspace/application-action-descriptions.js";
|
|
2
3
|
import { createEffect, createMemo, createSignal, For, Show } from "solid-js";
|
|
3
4
|
import { clipTerminal, terminalDisplayWidth } from "../terminal-text.js";
|
|
4
|
-
import {
|
|
5
|
+
import { AgentRow } from "../ui/agent-row.jsx";
|
|
5
6
|
import { KeyHint } from "../ui/key-hint.jsx";
|
|
6
7
|
import { TuiButton } from "../ui/button.jsx";
|
|
7
8
|
import { useKeyboardRoute } from "../ui/keyboard-router.jsx";
|
|
8
9
|
import { applicationPaneRenameKeyAction, applicationPaneRenamePaste, } from "./application-pane-rename-input.js";
|
|
9
10
|
import { HomeAgentSearch } from "../ui/home-agent-search.jsx";
|
|
10
|
-
import { createAgentStatusMarker } from "../ui/agent-status-marker.js";
|
|
11
11
|
import { ActivityIndicator } from "../ui/activity-indicator.jsx";
|
|
12
|
-
import { componentPalette } from "../ui/state.js";
|
|
13
|
-
import { homeAgentStatusLabel, } from "./application-home-agents.js";
|
|
14
|
-
function padCells(text, width) {
|
|
15
|
-
const clipped = clipTerminal(text, Math.max(0, width));
|
|
16
|
-
return clipped + " ".repeat(Math.max(0, width - terminalDisplayWidth(clipped)));
|
|
17
|
-
}
|
|
18
|
-
function statusTone(row) {
|
|
19
|
-
if (row.activity === "waiting" || row.activity === "failed")
|
|
20
|
-
return "blocked";
|
|
21
|
-
if (row.activity === "running")
|
|
22
|
-
return "working";
|
|
23
|
-
if (row.activity === "complete")
|
|
24
|
-
return "done";
|
|
25
|
-
return row.activity === "idle" ? "idle" : "unknown";
|
|
26
|
-
}
|
|
27
12
|
/** A flat projected roster; no subscriptions, navigation effects, or terminal input ingress. */
|
|
28
13
|
export function HomeAgentRoster(props) {
|
|
29
14
|
const [hovered, setHovered] = createSignal(null);
|
|
@@ -36,10 +21,12 @@ export function HomeAgentRoster(props) {
|
|
|
36
21
|
const showRecovery = () => Boolean(((props.snapshot.phase === "unavailable" || props.snapshot.unavailableSessions > 0) &&
|
|
37
22
|
props.onRetry) ||
|
|
38
23
|
(props.snapshot.truncatedSessions > 0 && props.onLoadMore));
|
|
39
|
-
const rowHeight = () => (
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
const
|
|
24
|
+
const rowHeight = () => (height() >= 14 ? 2 : 1);
|
|
25
|
+
const compact = () => height() < 8;
|
|
26
|
+
const filterRows = () => (compact() ? 0 : props.onSetActivityFilter && width() < 70 ? 2 : 1);
|
|
27
|
+
const searchRows = () => (props.onQueryChange ? (compact() ? 1 : 2) : 0);
|
|
28
|
+
const visibleCount = () => Math.max(0, Math.floor((height() - (compact() ? 1 : 4) - filterRows() - searchRows() - (showRecovery() ? 1 : 0)) /
|
|
29
|
+
rowHeight()));
|
|
43
30
|
const openSelected = (source = "keyboard") => {
|
|
44
31
|
const row = props.snapshot.rows.find((row) => row.key === props.selection.selectedKey);
|
|
45
32
|
if (props.inputActive && visibleCount() > 0 && row?.paneId && !stale(row))
|
|
@@ -52,12 +39,6 @@ export function HomeAgentRoster(props) {
|
|
|
52
39
|
const keys = createMemo(() => visible().map((row) => row.key), undefined, {
|
|
53
40
|
equals: (a, b) => a.length === b.length && a.every((key, index) => key === b[index]),
|
|
54
41
|
});
|
|
55
|
-
const columns = () => {
|
|
56
|
-
const status = Math.min(12, Math.max(0, width() - 4));
|
|
57
|
-
const label = Math.max(0, width() - status - 3);
|
|
58
|
-
const session = width() >= 44 ? Math.floor(label * 0.43) : 0;
|
|
59
|
-
return { status, agent: label - session, session };
|
|
60
|
-
};
|
|
61
42
|
const filtered = () => Boolean(props.query || (props.activityFilter && props.activityFilter !== "all"));
|
|
62
43
|
const title = () => {
|
|
63
44
|
if (props.snapshot.phase === "unavailable")
|
|
@@ -100,13 +81,13 @@ export function HomeAgentRoster(props) {
|
|
|
100
81
|
return false;
|
|
101
82
|
const key = event.name.toLowerCase();
|
|
102
83
|
const filter = !editing() &&
|
|
103
|
-
(key ===
|
|
84
|
+
(key === HOME_ACTIONS.machine.key
|
|
104
85
|
? props.onCycleMachine
|
|
105
|
-
: key ===
|
|
86
|
+
: key === HOME_ACTIONS.attention.key
|
|
106
87
|
? props.onToggleAttention
|
|
107
|
-
: key ===
|
|
88
|
+
: key === HOME_ACTIONS.working.key && props.onSetActivityFilter
|
|
108
89
|
? () => props.onSetActivityFilter?.("working")
|
|
109
|
-
: key ===
|
|
90
|
+
: key === HOME_ACTIONS.all.key && props.onSetActivityFilter
|
|
110
91
|
? () => props.onSetActivityFilter?.("all")
|
|
111
92
|
: undefined);
|
|
112
93
|
if (filter) {
|
|
@@ -142,7 +123,9 @@ export function HomeAgentRoster(props) {
|
|
|
142
123
|
props.onMove(delta);
|
|
143
124
|
return true;
|
|
144
125
|
});
|
|
145
|
-
return (<box position="relative" width={width()} height={
|
|
126
|
+
return (<box position="relative" width={width()} height={props.fitContent && props.snapshot.rows.length > 0
|
|
127
|
+
? height() - (visibleCount() - visible().length) * rowHeight()
|
|
128
|
+
: height()} flexShrink={0} flexDirection="column" overflow="hidden" onMouseScroll={(event) => {
|
|
146
129
|
if (!props.inputActive)
|
|
147
130
|
return;
|
|
148
131
|
const direction = event.scroll?.direction;
|
|
@@ -153,16 +136,19 @@ export function HomeAgentRoster(props) {
|
|
|
153
136
|
props.onMove(direction === "up" ? -1 : 1);
|
|
154
137
|
}} onMouseOut={() => setHovered(null)}>
|
|
155
138
|
<box height={searchRows()} flexShrink={0}/>
|
|
156
|
-
<
|
|
157
|
-
<
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
{
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
139
|
+
<Show when={!compact()}>
|
|
140
|
+
<box width={width()} height={1} flexShrink={0} flexDirection="row">
|
|
141
|
+
<Show when={props.snapshot.phase === "loading" || props.snapshot.loadingSessions > 0}>
|
|
142
|
+
<ActivityIndicator theme={props.theme} active={props.inputActive}/>
|
|
143
|
+
</Show>
|
|
144
|
+
<text height={1} flexGrow={1} fg={props.theme.roles.text.muted}>
|
|
145
|
+
{clipTerminal(title(), width())}
|
|
146
|
+
</text>
|
|
147
|
+
</box>
|
|
148
|
+
</Show>
|
|
149
|
+
<Show when={!compact()}>
|
|
150
|
+
<box width={width()} height={filterRows()} flexShrink={0} flexDirection={filterRows() > 1 ? "column" : "row"} overflow="hidden">
|
|
151
|
+
<KeyHint theme={props.theme} keys={HOME_ACTIONS.machine.keys} label={props.filterLabel?.split(" · ")[0] ?? "All machines"} width={props.onSetActivityFilter && filterRows() === 1
|
|
166
152
|
? Math.max(1, width() - 47)
|
|
167
153
|
: props.onSetActivityFilter
|
|
168
154
|
? Math.min(width(), 28)
|
|
@@ -170,24 +156,21 @@ export function HomeAgentRoster(props) {
|
|
|
170
156
|
if (props.inputActive)
|
|
171
157
|
props.onCycleMachine?.();
|
|
172
158
|
}}/>
|
|
173
|
-
|
|
174
|
-
|
|
159
|
+
<box height={1} flexShrink={0} flexDirection="row" overflow="hidden" width={Math.min(width(), 47)}>
|
|
160
|
+
<Show when={props.onSetActivityFilter} fallback={<KeyHint theme={props.theme} keys={HOME_ACTIONS.attention.keys} label="Attention" quiet button onPress={() => {
|
|
175
161
|
if (props.inputActive)
|
|
176
162
|
props.onToggleAttention?.();
|
|
177
163
|
}}/>}>
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
{ key: "w", label: "Working", value: "working" },
|
|
181
|
-
{ key: "a", label: "Needs attention", value: "attention" },
|
|
182
|
-
]}>
|
|
183
|
-
{(filter) => (<KeyHint theme={props.theme} keys={filter.key} label={filter.value === "attention" && width() < 47 ? "Attention" : filter.label} quiet button selected={(props.activityFilter ?? "all") === filter.value} onPress={() => {
|
|
164
|
+
<For each={HOME_ACTIVITY_ACTIONS}>
|
|
165
|
+
{(filter) => (<KeyHint theme={props.theme} keys={filter.keys} label={filter.value === "attention" && width() < 47 ? "Attention" : filter.label} quiet button selected={(props.activityFilter ?? "all") === filter.value} onPress={() => {
|
|
184
166
|
if (props.inputActive)
|
|
185
167
|
props.onSetActivityFilter?.(filter.value);
|
|
186
168
|
}}/>)}
|
|
187
|
-
|
|
188
|
-
|
|
169
|
+
</For>
|
|
170
|
+
</Show>
|
|
171
|
+
</box>
|
|
189
172
|
</box>
|
|
190
|
-
</
|
|
173
|
+
</Show>
|
|
191
174
|
<Show when={props.snapshot.rows.length > 0} fallback={<box height={Math.max(0, visibleCount() * rowHeight() + 1)} flexShrink={0} flexDirection="column" overflow="hidden">
|
|
192
175
|
<Show when={props.snapshot.phase === "live" && !props.query}>
|
|
193
176
|
<text height={1} width={width()} fg={props.theme.roles.text.primary}>
|
|
@@ -200,51 +183,20 @@ export function HomeAgentRoster(props) {
|
|
|
200
183
|
</text>
|
|
201
184
|
</Show>
|
|
202
185
|
</box>}>
|
|
203
|
-
<box height={1} flexShrink={0}/>
|
|
204
|
-
<box height={visibleCount() * rowHeight()} width={width()} flexShrink={0} flexDirection="column" overflow="hidden">
|
|
186
|
+
<box height={compact() ? 0 : 1} flexShrink={0}/>
|
|
187
|
+
<box height={(props.fitContent ? visible().length : visibleCount()) * rowHeight()} width={width()} flexShrink={0} flexDirection="column" overflow="hidden">
|
|
205
188
|
<For each={keys()}>
|
|
206
189
|
{(key) => {
|
|
207
190
|
const row = () => byKey().get(key);
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
unavailable: () => stale(row()),
|
|
213
|
-
});
|
|
214
|
-
return (<box height={rowHeight()} flexDirection="column" backgroundColor={componentPalette(props.theme, {
|
|
215
|
-
selected: props.selection.selectedKey === key,
|
|
216
|
-
hovered: hovered() === key,
|
|
217
|
-
disabled: row().paneId === null || stale(row()),
|
|
218
|
-
}).background} width={width()} flexShrink={0} onMouseDown={(event) => {
|
|
219
|
-
if (!props.inputActive || event.button !== 0 || !row().paneId || stale(row()))
|
|
220
|
-
return;
|
|
221
|
-
event.preventDefault();
|
|
222
|
-
event.stopPropagation();
|
|
223
|
-
props.onSelect(key);
|
|
224
|
-
props.onOpen(row(), "mouse");
|
|
225
|
-
}} onMouseOver={() => setHovered(key)} onMouseMove={() => setHovered(key)}>
|
|
226
|
-
<NavigationRow theme={props.theme} id={`home-agent:${key}`} width={width()} label={rowHeight() > 1
|
|
227
|
-
? row().name
|
|
228
|
-
: padCells(row().name, columns().agent) +
|
|
229
|
-
padCells([row().machineLabel, row().serverLabel, row().sessionName]
|
|
230
|
-
.filter(Boolean)
|
|
231
|
-
.join(" / "), columns().session)} detail={padCells(`${marker()} ${stale(row()) ? "last seen" : homeAgentStatusLabel(row().activity).toLowerCase()}`, columns().status)} detailAlign="end" marker=" " selected={props.selection.selectedKey === key} focused={props.inputActive && !editing() && props.selection.selectedKey === key} hovered={hovered() === key} status={stale(row()) ? "unknown" : statusTone(row())} disabled={row().paneId === null || stale(row())} onActivate={(source) => {
|
|
191
|
+
return (<box height={rowHeight()} width={width()} flexShrink={0} onMouseOver={() => setHovered(key)} onMouseMove={() => setHovered(key)}>
|
|
192
|
+
<AgentRow surface="canvas" theme={props.theme} id={`home-agent:${key}`} name={row().name} context={[row().machineLabel, row().serverLabel, row().sessionName]
|
|
193
|
+
.filter(Boolean)
|
|
194
|
+
.join(" · ")} width={width()} compact={rowHeight() === 1} activity={row().activity} attention={row().attention} unavailable={row().paneId === null || stale(row())} selected={props.selection.selectedKey === key} focused={props.inputActive && !editing() && props.selection.selectedKey === key} hovered={hovered() === key} onOpen={(source) => {
|
|
232
195
|
if (!props.inputActive || row().paneId === null || stale(row()))
|
|
233
196
|
return;
|
|
234
197
|
props.onSelect(key);
|
|
235
198
|
props.onOpen(row(), source);
|
|
236
199
|
}}/>
|
|
237
|
-
<Show when={rowHeight() > 1}>
|
|
238
|
-
<text width={width()} height={1} fg={props.selection.selectedKey === key
|
|
239
|
-
? props.theme.roles.selection.selectionText
|
|
240
|
-
: props.theme.roles.text.muted} bg={componentPalette(props.theme, {
|
|
241
|
-
selected: props.selection.selectedKey === key,
|
|
242
|
-
hovered: hovered() === key,
|
|
243
|
-
disabled: row().paneId === null || stale(row()),
|
|
244
|
-
}).background}>
|
|
245
|
-
{clipTerminal(` ${[row().sessionName, row().machineLabel, row().serverLabel, row().harness].filter(Boolean).join(" · ")}`, width())}
|
|
246
|
-
</text>
|
|
247
|
-
</Show>
|
|
248
200
|
</box>);
|
|
249
201
|
}}
|
|
250
202
|
</For>
|
|
@@ -262,14 +214,16 @@ export function HomeAgentRoster(props) {
|
|
|
262
214
|
<text width={width()} height={1} flexShrink={0} fg={props.theme.roles.text.muted}>
|
|
263
215
|
{clipTerminal(coverage(), width())}
|
|
264
216
|
</text>
|
|
265
|
-
<
|
|
266
|
-
<
|
|
267
|
-
{
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
<
|
|
271
|
-
|
|
272
|
-
|
|
217
|
+
<Show when={!compact()}>
|
|
218
|
+
<box width={width()} height={1} flexShrink={0} flexDirection="row" overflow="hidden">
|
|
219
|
+
<text width={Math.min(Math.max(0, width() - (footer().endsWith("Enter open") && width() >= 20 ? 12 : 0)), terminalDisplayWidth(footer().replace(/ Enter open$/u, "")))} fg={props.theme.roles.text.muted}>
|
|
220
|
+
{clipTerminal(footer().replace(/ Enter open$/u, ""), width())}
|
|
221
|
+
</text>
|
|
222
|
+
<Show when={footer().endsWith("Enter open") && width() >= 20}>
|
|
223
|
+
<KeyHint theme={props.theme} keys={HOME_ACTIONS.open.keys} label={HOME_ACTIONS.open.label} quiet button onPress={() => openSelected("mouse")}/>
|
|
224
|
+
</Show>
|
|
225
|
+
</box>
|
|
226
|
+
</Show>
|
|
273
227
|
<Show when={showRecovery()}>
|
|
274
228
|
<box height={1} width={width()} flexShrink={0} flexDirection="row" gap={1}>
|
|
275
229
|
<Show when={(props.snapshot.phase === "unavailable" || props.snapshot.unavailableSessions > 0) &&
|
|
@@ -215,6 +215,7 @@ export function createApplicationHomeNavigationOwner(options) {
|
|
|
215
215
|
command === "appearance" ||
|
|
216
216
|
command === "hide-sidebar" ||
|
|
217
217
|
command === "show-sidebar" ||
|
|
218
|
+
command === "help" ||
|
|
218
219
|
command === "shortcuts" ||
|
|
219
220
|
command === "whats-new" ||
|
|
220
221
|
command === "switch-session")
|
|
@@ -3,7 +3,7 @@ import { createFleetTabs } from "./application-fleet-tabs.js";
|
|
|
3
3
|
import { saveMachineProfiles } from "../../../lib/local-fleet-request.js";
|
|
4
4
|
import { createApplicationFleetPreferences } from "./application-fleet-preferences.js";
|
|
5
5
|
import { createApplicationMachineAgents, } from "./application-machine-agents.js";
|
|
6
|
-
import { createSignal, onCleanup } from "solid-js";
|
|
6
|
+
import { createEffect, createSignal, onCleanup } from "solid-js";
|
|
7
7
|
import { applicationMachineAuthorityManager as manager } from "./application-machine-authority.js";
|
|
8
8
|
import { createApplicationMachineCatalog } from "./application-machine-catalog.js";
|
|
9
9
|
import { ephemeralMachineProfile } from "./application-machine-startup.js";
|
|
@@ -159,7 +159,8 @@ export function createApplicationMachineNavigation(options) {
|
|
|
159
159
|
machineId: id,
|
|
160
160
|
liveSessionId: session.liveSessionId,
|
|
161
161
|
label: session.name,
|
|
162
|
-
hostLabel:
|
|
162
|
+
hostLabel: manager.getMachine(id)?.label ?? id,
|
|
163
|
+
serverLabel: session.serverLabel ?? "default",
|
|
163
164
|
});
|
|
164
165
|
preferences.change({ type: "visit", key: session.id });
|
|
165
166
|
if (remember && history[historyIndex] !== session.id) {
|
|
@@ -198,6 +199,28 @@ export function createApplicationMachineNavigation(options) {
|
|
|
198
199
|
});
|
|
199
200
|
const isDefaultSession = (machineId, session) => !session.server ||
|
|
200
201
|
session.server.generation === manager.getMachine(machineId)?.read()?.instanceId;
|
|
202
|
+
const tabAgents = (tab) => {
|
|
203
|
+
const group = agentGroups().find((value) => value.machineId === tab.machineId);
|
|
204
|
+
// This observer covers only the default tmux server; never guess for other scopes.
|
|
205
|
+
if (!resolveTab(tab) || !group?.available || !isDefaultSession(tab.machineId, tab))
|
|
206
|
+
return null;
|
|
207
|
+
const observation = group.observation;
|
|
208
|
+
if (observation &&
|
|
209
|
+
(observation.loadingSessions > 0 ||
|
|
210
|
+
observation.unavailableSessions > 0 ||
|
|
211
|
+
observation.truncatedSessions > 0 ||
|
|
212
|
+
observation.refreshingSessionKeys.length > 0))
|
|
213
|
+
return null;
|
|
214
|
+
const rows = group.agents.filter((agent) => agent.liveSessionId === tab.liveSessionId);
|
|
215
|
+
if (rows.some((agent) => agent.disabled))
|
|
216
|
+
return null;
|
|
217
|
+
return rows;
|
|
218
|
+
};
|
|
219
|
+
createEffect(() => {
|
|
220
|
+
tabRevision();
|
|
221
|
+
for (const tab of tabs.snapshot().tabs)
|
|
222
|
+
tabs.observe(tab.key, tabAgents(tab));
|
|
223
|
+
});
|
|
201
224
|
const sidebar = {
|
|
202
225
|
groups: () => snapshot().groups.map((group) => ({
|
|
203
226
|
...group,
|
|
@@ -214,11 +237,26 @@ export function createApplicationMachineNavigation(options) {
|
|
|
214
237
|
tabs: () => {
|
|
215
238
|
tabRevision();
|
|
216
239
|
const value = tabs.snapshot();
|
|
217
|
-
return value.tabs.map((tab) =>
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
240
|
+
return value.tabs.map((tab) => {
|
|
241
|
+
const agents = tabAgents(tab);
|
|
242
|
+
const activity = !agents
|
|
243
|
+
? undefined
|
|
244
|
+
: agents.some((agent) => agent.activity === "failed")
|
|
245
|
+
? "failed"
|
|
246
|
+
: agents.some((agent) => agent.attention || agent.activity === "waiting")
|
|
247
|
+
? "waiting"
|
|
248
|
+
: agents.some((agent) => agent.activity === "running")
|
|
249
|
+
? "running"
|
|
250
|
+
: "idle";
|
|
251
|
+
return {
|
|
252
|
+
...tab,
|
|
253
|
+
active: value.active === tab.key,
|
|
254
|
+
available: !!resolveTab(tab),
|
|
255
|
+
activity,
|
|
256
|
+
attention: agents?.some((agent) => agent.attention || agent.activity === "waiting"),
|
|
257
|
+
unread: value.unread.includes(tab.key),
|
|
258
|
+
};
|
|
259
|
+
});
|
|
222
260
|
},
|
|
223
261
|
onOpenTab: (key) => {
|
|
224
262
|
void tabs.activate(key);
|