tmux-ide 2.9.0-beta.39 → 2.9.0-beta.41

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 (39) hide show
  1. package/README.md +5 -0
  2. package/bin/cli.js +6 -3
  3. package/package.json +6 -3
  4. package/packages/daemon/dist/tui/mirror/runtime/application-fleet-tabs.js +29 -2
  5. package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-roster.jsx +10 -13
  6. package/packages/daemon/dist/tui/mirror/runtime/application-home-agents-owner.js +1 -0
  7. package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +45 -7
  8. package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +44 -44
  9. package/packages/daemon/dist/tui/mirror/runtime/application-palette-command-owner.js +2 -2
  10. package/packages/daemon/dist/tui/mirror/runtime/application-palette-input.js +1 -0
  11. package/packages/daemon/dist/tui/mirror/ui/agent-status-marker.js +6 -5
  12. package/packages/daemon/dist/tui/mirror/ui/icons.js +9 -0
  13. package/packages/daemon/dist/tui/mirror/ui/index.js +3 -0
  14. package/packages/daemon/dist/tui/mirror/ui/session-row.jsx +71 -0
  15. package/packages/daemon/dist/tui/mirror/ui/working-sessions.jsx +61 -0
  16. package/packages/daemon/dist/tui/mirror/workspace/application-action-descriptions.js +51 -0
  17. package/packages/daemon/dist/tui/mirror/workspace/application-command-description.js +10 -3
  18. package/packages/daemon/dist/tui/mirror/workspace/application-shortcuts.js +15 -7
  19. package/packages/daemon/dist/tui/mirror/workspace/palette-list-layout.js +1 -1
  20. package/packages/daemon/src/tui/mirror/runtime/application-fleet-tabs.ts +34 -2
  21. package/packages/daemon/src/tui/mirror/runtime/application-home-agent-roster.tsx +14 -18
  22. package/packages/daemon/src/tui/mirror/runtime/application-home-agents-owner.ts +1 -0
  23. package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +44 -7
  24. package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +70 -80
  25. package/packages/daemon/src/tui/mirror/runtime/application-palette-command-owner.ts +5 -3
  26. package/packages/daemon/src/tui/mirror/runtime/application-palette-input.ts +1 -0
  27. package/packages/daemon/src/tui/mirror/runtime/application-reference-sheet.tsx +80 -5
  28. package/packages/daemon/src/tui/mirror/runtime/application-shell-catalog.tsx +6 -2
  29. package/packages/daemon/src/tui/mirror/runtime/application-shell-overlays.tsx +6 -5
  30. package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +6 -2
  31. package/packages/daemon/src/tui/mirror/ui/agent-status-marker.ts +7 -5
  32. package/packages/daemon/src/tui/mirror/ui/icons.ts +9 -0
  33. package/packages/daemon/src/tui/mirror/ui/index.ts +4 -0
  34. package/packages/daemon/src/tui/mirror/ui/session-row.tsx +122 -0
  35. package/packages/daemon/src/tui/mirror/ui/working-sessions.tsx +99 -0
  36. package/packages/daemon/src/tui/mirror/workspace/application-action-descriptions.ts +52 -0
  37. package/packages/daemon/src/tui/mirror/workspace/application-command-description.ts +12 -3
  38. package/packages/daemon/src/tui/mirror/workspace/application-shortcuts.ts +19 -7
  39. package/packages/daemon/src/tui/mirror/workspace/palette-list-layout.ts +1 -1
@@ -0,0 +1,122 @@
1
+ /* @jsxImportSource @opentui/solid */
2
+ import type { AgentActivity } from "@tmux-ide/contracts";
3
+ import { Show } from "solid-js";
4
+ import type { SemanticThemeSnapshot } from "../theme.ts";
5
+ import { clipTerminal } from "../terminal-text.ts";
6
+ import { NavigationRow } from "./navigation-row.tsx";
7
+ import { TuiButton } from "./button.tsx";
8
+ import { componentPalette } from "./state.ts";
9
+ import { createAgentStatusMarker } from "./agent-status-marker.ts";
10
+ import { UI_ICONS } from "./icons.ts";
11
+
12
+ export interface SessionRowModel {
13
+ readonly key: string;
14
+ readonly label: string;
15
+ readonly hostLabel: string;
16
+ readonly serverLabel?: string;
17
+ readonly active: boolean;
18
+ readonly available: boolean;
19
+ readonly activity?: AgentActivity;
20
+ readonly attention?: boolean;
21
+ readonly unread?: boolean;
22
+ }
23
+ export function sessionRowStatus(row: SessionRowModel) {
24
+ if (!row.available) return "offline";
25
+ if (row.attention || row.activity === "waiting") return "needs input";
26
+ if (row.activity === "failed") return "failed";
27
+ if (row.activity === "running") return "working";
28
+ if (row.unread) return "new result";
29
+ if (!row.activity || row.activity === "disconnected") return "unknown";
30
+ return "idle";
31
+ }
32
+ /** Shared two-line session identity. Pure presentation: opening/closing belongs to its owner. */
33
+ export function SessionRow(props: {
34
+ theme: SemanticThemeSnapshot;
35
+ row: SessionRowModel;
36
+ width: number;
37
+ selected?: boolean;
38
+ focused?: boolean;
39
+ onOpen: () => void;
40
+ onClose?: () => void;
41
+ }) {
42
+ const marker = createAgentStatusMarker({
43
+ theme: () => props.theme,
44
+ status: () => (sessionRowStatus(props.row) === "idle" ? "idle" : props.row.activity),
45
+ attention: () => !!props.row.attention,
46
+ unavailable: () => !props.row.available,
47
+ });
48
+ const tone = () => {
49
+ const status = sessionRowStatus(props.row);
50
+ return status === "needs input" || status === "failed"
51
+ ? ("blocked" as const)
52
+ : status === "working"
53
+ ? ("working" as const)
54
+ : status === "new result"
55
+ ? ("done" as const)
56
+ : ("neutral" as const);
57
+ };
58
+ const glyph = () =>
59
+ !props.row.available
60
+ ? UI_ICONS.offline
61
+ : sessionRowStatus(props.row) === "new result"
62
+ ? UI_ICONS.newResult
63
+ : marker();
64
+ const palette = () =>
65
+ componentPalette(props.theme, {
66
+ selected: props.selected,
67
+ focused: props.focused,
68
+ disabled: !props.row.available,
69
+ });
70
+ const detail = () =>
71
+ `${props.row.hostLabel} · ${props.row.serverLabel ?? "default"}${props.row.active ? " · current" : ""}`;
72
+ return (
73
+ <box
74
+ id={`session-row:${props.row.key}`}
75
+ width={props.width}
76
+ height={2}
77
+ flexDirection="column"
78
+ overflow="hidden"
79
+ >
80
+ <box height={1} flexDirection="row">
81
+ <NavigationRow
82
+ theme={props.theme}
83
+ id={`fleet-tab:${props.row.key}`}
84
+ width={Math.max(1, props.width - (props.onClose ? 3 : 0))}
85
+ label={props.row.label}
86
+ tone={tone()}
87
+ marker={glyph()}
88
+ detail={sessionRowStatus(props.row)}
89
+ selected={props.selected}
90
+ focused={props.focused}
91
+ disabled={!props.row.available}
92
+ onActivate={props.onOpen}
93
+ />
94
+ <Show when={props.onClose}>
95
+ <TuiButton
96
+ theme={props.theme}
97
+ label={UI_ICONS.close}
98
+ size="compact"
99
+ width={3}
100
+ onPress={props.onClose}
101
+ />
102
+ </Show>
103
+ </box>
104
+ <box
105
+ height={1}
106
+ backgroundColor={palette().background}
107
+ onMouseDown={(event) => {
108
+ if (event.button !== 0 || !props.row.available) return;
109
+ event.preventDefault();
110
+ event.stopPropagation();
111
+ props.onOpen();
112
+ }}
113
+ >
114
+ <text
115
+ fg={props.selected || props.focused ? palette().foreground : props.theme.roles.text.muted}
116
+ >
117
+ {clipTerminal(` ${detail()}`, props.width)}
118
+ </text>
119
+ </box>
120
+ </box>
121
+ );
122
+ }
@@ -0,0 +1,99 @@
1
+ /* @jsxImportSource @opentui/solid */
2
+ import { createEffect, createMemo, createSignal, For, Show } from "solid-js";
3
+ import type { SemanticThemeSnapshot } from "../theme.ts";
4
+ import { SessionRow, type SessionRowModel } from "./session-row.tsx";
5
+ import { useKeyboardRoute } from "./keyboard-router.tsx";
6
+
7
+ /** Bounded working-set list, independent of fleet discovery and terminal ownership. */
8
+ export function WorkingSessions(props: {
9
+ theme: SemanticThemeSnapshot;
10
+ rows: readonly SessionRowModel[];
11
+ width: number;
12
+ height: number;
13
+ focused: boolean;
14
+ onFocus?: () => void;
15
+ onOpen: (key: string) => void;
16
+ onClose?: (key: string) => void;
17
+ }) {
18
+ const [selected, setSelected] = createSignal<string | null>(null);
19
+ const index = () =>
20
+ Math.max(
21
+ 0,
22
+ props.rows.findIndex((row) => row.key === selected()),
23
+ );
24
+ const capacity = () => Math.max(0, Math.floor((props.height - 1) / 3));
25
+ const start = () => Math.max(0, index() - capacity() + 1);
26
+ const visible = () => props.rows.slice(start(), start() + capacity()).map((row) => row.key);
27
+ createEffect(() => {
28
+ if (!props.rows.some((row) => row.key === selected()))
29
+ setSelected((props.rows.find((row) => row.active) ?? props.rows[0])?.key ?? null);
30
+ if (!props.focused)
31
+ setSelected((props.rows.find((row) => row.active) ?? props.rows[index()])?.key ?? null);
32
+ });
33
+ const open = (key: string) => {
34
+ setSelected(key);
35
+ props.onFocus?.();
36
+ if (props.rows.find((row) => row.key === key)?.available) props.onOpen(key);
37
+ };
38
+ useKeyboardRoute((event) => {
39
+ if (!props.focused || event.eventType !== "press" || event.ctrl || event.meta) return false;
40
+ const key = event.name.toLowerCase();
41
+ if (!["up", "down", "j", "k", "home", "end", "enter", "return", "space"].includes(key))
42
+ return false;
43
+ event.preventDefault();
44
+ event.stopPropagation();
45
+ if (!props.rows.length || !capacity()) return true;
46
+ if (["enter", "return", "space"].includes(key)) open(props.rows[index()]!.key);
47
+ else {
48
+ const next =
49
+ key === "home"
50
+ ? 0
51
+ : key === "end"
52
+ ? props.rows.length - 1
53
+ : Math.max(
54
+ 0,
55
+ Math.min(props.rows.length - 1, index() + (["up", "k"].includes(key) ? -1 : 1)),
56
+ );
57
+ setSelected(props.rows[next]!.key);
58
+ }
59
+ return true;
60
+ });
61
+ return (
62
+ <box
63
+ width={props.width}
64
+ height={props.height}
65
+ flexShrink={0}
66
+ flexDirection="column"
67
+ overflow="hidden"
68
+ >
69
+ <text height={1} fg={props.theme.roles.text.secondary}>
70
+ {props.focused ? <strong> Working sessions</strong> : " Working sessions"}
71
+ </text>
72
+ <Show
73
+ when={props.rows.length}
74
+ fallback={<text fg={props.theme.roles.text.muted}> No working sessions yet.</text>}
75
+ >
76
+ <For each={visible()}>
77
+ {(key) => {
78
+ const row = createMemo<SessionRowModel>(
79
+ (previous) => props.rows.find((row) => row.key === key) ?? previous,
80
+ props.rows.find((row) => row.key === key)!,
81
+ );
82
+ return (
83
+ <box height={3} flexShrink={0} flexDirection="column">
84
+ <SessionRow
85
+ theme={props.theme}
86
+ row={row()}
87
+ width={props.width}
88
+ selected={props.focused ? selected() === key : row().active}
89
+ onOpen={() => open(key)}
90
+ onClose={props.onClose ? () => props.onClose?.(key) : undefined}
91
+ />
92
+ </box>
93
+ );
94
+ }}
95
+ </For>
96
+ </Show>
97
+ </box>
98
+ );
99
+ }
@@ -0,0 +1,52 @@
1
+ /** Shared presentation/binding metadata. Context owners still decide availability and execution. */
2
+ export const HOME_ACTIONS = {
3
+ search: { key: "/", keys: "/", label: "Search", description: "Find an agent or workspace" },
4
+ machine: { key: "f", keys: "f", label: "Machines", description: "Cycle machine filter" },
5
+ all: { key: "0", keys: "0", label: "All", description: "Show all agent activity", value: "all" },
6
+ working: {
7
+ key: "w",
8
+ keys: "w",
9
+ label: "Working",
10
+ description: "Show working agents",
11
+ value: "working",
12
+ },
13
+ attention: {
14
+ key: "a",
15
+ keys: "a",
16
+ label: "Needs attention",
17
+ description: "Toggle attention filter",
18
+ value: "attention",
19
+ },
20
+ open: { key: "enter", keys: "Enter", label: "open", description: "Open selected agent" },
21
+ } as const;
22
+ export const HOME_ACTIVITY_ACTIONS = [
23
+ HOME_ACTIONS.all,
24
+ HOME_ACTIONS.working,
25
+ HOME_ACTIONS.attention,
26
+ ] as const;
27
+
28
+ export const CHROME_ACTIONS = {
29
+ home: { keys: "F1", label: "Home" },
30
+ terminals: { keys: "F2", label: "Terminals" },
31
+ commands: { keys: "F5", label: "Commands" },
32
+ sessions: { keys: "F6", label: "Sessions" },
33
+ attention: { keys: "F7", label: "Attention" },
34
+ tabs: { keys: "F9", label: "Tabs" },
35
+ sidebar: { keys: "F10", label: "Show / hide sidebar" },
36
+ } as const;
37
+ export const SIDEBAR_ACTIONS = {
38
+ help: { key: "?", keys: "?", label: "Help", description: "Using tmux-ide" },
39
+ retry: {
40
+ key: "r",
41
+ keys: "R",
42
+ label: "Retry connection",
43
+ description: "Retry selected machine connection",
44
+ },
45
+ disconnect: {
46
+ key: "d",
47
+ keys: "D",
48
+ label: "Disconnect",
49
+ description: "Disconnect selected machine",
50
+ },
51
+ add: { key: "a", keys: "A", label: "Add machine", description: "Add an SSH machine" },
52
+ } as const;
@@ -1,3 +1,4 @@
1
+ import { CHROME_ACTIONS } from "./application-action-descriptions.ts";
1
2
  import type { TmuxServerScope } from "@tmux-ide/contracts";
2
3
  import { fuzzyTermsMatch as commandSearchMatch } from "../../team/fuzzy.ts";
3
4
  export { fuzzyTermsMatch as commandSearchMatch } from "../../team/fuzzy.ts";
@@ -40,6 +41,7 @@ export type ApplicationPaletteCommand =
40
41
  | "hide-sidebar"
41
42
  | "show-sidebar"
42
43
  | "switch-session"
44
+ | "help"
43
45
  | "shortcuts"
44
46
  | "whats-new"
45
47
  | "home"
@@ -66,18 +68,20 @@ export function applicationCommandDescription(command: ApplicationPaletteCommand
66
68
  detail: string;
67
69
  shortcut?: string;
68
70
  } {
71
+ if (command === "help")
72
+ return { id: command, label: "Using tmux-ide", detail: "Help and getting started" };
69
73
  if (command === "hide-sidebar" || command === "show-sidebar")
70
74
  return {
71
75
  id: command,
72
76
  label: command === "hide-sidebar" ? "Hide sidebar" : "Show sidebar",
73
77
  detail: "Terminal layout",
74
- shortcut: "F10",
78
+ shortcut: CHROME_ACTIONS.sidebar.keys,
75
79
  };
76
80
  if (command === "switch-session")
77
81
  return {
78
82
  id: command,
79
83
  label: "Switch session",
80
- shortcut: "F6",
84
+ shortcut: CHROME_ACTIONS.sessions.keys,
81
85
  detail: "Sessions across machines",
82
86
  };
83
87
  if (command === "shortcuts" || command === "whats-new")
@@ -133,7 +137,12 @@ export function applicationCommandDescription(command: ApplicationPaletteCommand
133
137
  ? "Zoom / unzoom pane"
134
138
  : undefined) ??
135
139
  (command === "home" ? "Home" : command === "terminals" ? "Terminals" : "New terminal window"),
136
- shortcut: command === "home" ? "F1" : command === "terminals" ? "F2" : undefined,
140
+ shortcut:
141
+ command === "home"
142
+ ? CHROME_ACTIONS.home.keys
143
+ : command === "terminals"
144
+ ? CHROME_ACTIONS.terminals.keys
145
+ : undefined,
137
146
  detail:
138
147
  command === "home"
139
148
  ? "sessions and agent state"
@@ -1,3 +1,8 @@
1
+ import {
2
+ HOME_ACTIONS,
3
+ CHROME_ACTIONS,
4
+ SIDEBAR_ACTIONS,
5
+ } from "./application-action-descriptions.ts";
1
6
  import { applicationCommandDescription } from "./application-command-description.ts";
2
7
 
3
8
  export interface ApplicationShortcut {
@@ -12,16 +17,23 @@ export const APPLICATION_SHORTCUTS: readonly ApplicationShortcut[] = [
12
17
  const description = applicationCommandDescription(command);
13
18
  return { category: "Application", label: description.label, keys: description.shortcut! };
14
19
  }),
15
- { category: "Application", label: "Commands", keys: "F5" },
16
- { category: "Application", label: "Show / hide sidebar", keys: "F10" },
17
- { category: "Application", label: "Agent attention", keys: "F7" },
20
+ { category: "Application", ...CHROME_ACTIONS.commands },
21
+ { category: "Application", ...CHROME_ACTIONS.sidebar },
22
+ { category: "Application", ...CHROME_ACTIONS.attention },
18
23
  { category: "Application", label: "Sidebar / Sessions when hidden", keys: "Ctrl+G" },
19
24
  { category: "Terminals", label: "Open link", keys: "Shift+click" },
20
25
  { category: "Terminals", label: "Select and copy text", keys: "Shift+drag" },
21
- { category: "Home", label: "Find an agent or workspace", keys: "/" },
22
- { category: "Home", label: "Cycle machine filter", keys: "f" },
23
- { category: "Home", label: "Toggle attention filter", keys: "a" },
24
- { category: "Home", label: "Open selected agent", keys: "Enter" },
26
+ ...Object.values(HOME_ACTIONS).map((action) => ({
27
+ category: "Home",
28
+ label: action.description,
29
+ keys: action.keys,
30
+ })),
31
+ { category: "Sidebar (when focused)", label: "Switch working sessions / machines", keys: "Tab" },
32
+ ...Object.values(SIDEBAR_ACTIONS).map((action) => ({
33
+ category: "Sidebar (when focused)",
34
+ label: action.description,
35
+ keys: action.keys,
36
+ })),
25
37
  { category: "Command and session menus", label: "Search actions or sessions", keys: "Type" },
26
38
  { category: "Command and session menus", label: "Choose a result", keys: "↑ / ↓" },
27
39
  { category: "Command and session menus", label: "Activate selected result", keys: "Enter" },
@@ -11,7 +11,7 @@ export function paletteSection(command: ApplicationPaletteCommand): string {
11
11
  if (command === "home" || command === "terminals") return "Navigation";
12
12
  if (command === "hide-sidebar" || command === "show-sidebar") return "Appearance";
13
13
  if (command === "appearance") return "Appearance";
14
- if (command === "shortcuts" || command === "whats-new") return "Help";
14
+ if (command === "help" || command === "shortcuts" || command === "whats-new") return "Help";
15
15
  return "Panes";
16
16
  }
17
17