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.
- 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 +10 -13
- 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 +44 -44
- 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/ui/agent-status-marker.js +6 -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/session-row.jsx +71 -0
- 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/runtime/application-fleet-tabs.ts +34 -2
- package/packages/daemon/src/tui/mirror/runtime/application-home-agent-roster.tsx +14 -18
- 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 +70 -80
- package/packages/daemon/src/tui/mirror/runtime/application-palette-command-owner.ts +5 -3
- package/packages/daemon/src/tui/mirror/runtime/application-palette-input.ts +1 -0
- package/packages/daemon/src/tui/mirror/runtime/application-reference-sheet.tsx +80 -5
- package/packages/daemon/src/tui/mirror/runtime/application-shell-catalog.tsx +6 -2
- package/packages/daemon/src/tui/mirror/runtime/application-shell-overlays.tsx +6 -5
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +6 -2
- package/packages/daemon/src/tui/mirror/ui/agent-status-marker.ts +7 -5
- 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/session-row.tsx +122 -0
- package/packages/daemon/src/tui/mirror/ui/working-sessions.tsx +99 -0
- 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
|
@@ -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:
|
|
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:
|
|
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:
|
|
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",
|
|
16
|
-
{ category: "Application",
|
|
17
|
-
{ category: "Application",
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|