tmux-ide 2.9.0-beta.29 → 2.9.0-beta.31
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-home-agent-roster.jsx +99 -23
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agents-owner.js +18 -6
- package/packages/daemon/dist/tui/mirror/runtime/application-home-experience.js +9 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +18 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +16 -16
- package/packages/daemon/dist/tui/mirror/runtime/application-palette-command-owner.js +5 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-shell-home.jsx +57 -22
- package/packages/daemon/dist/tui/mirror/runtime/application-shell-viewport.js +2 -2
- package/packages/daemon/dist/tui/mirror/runtime/semantic-shell-viewport-resize.js +2 -2
- package/packages/daemon/dist/tui/mirror/shell-chrome.js +2 -0
- package/packages/daemon/dist/tui/mirror/ui/activity-indicator.jsx +16 -0
- package/packages/daemon/dist/tui/mirror/ui/badge.jsx +1 -1
- package/packages/daemon/dist/tui/mirror/ui/detail-row.jsx +22 -0
- package/packages/daemon/dist/tui/mirror/ui/home-agent-search.jsx +66 -0
- package/packages/daemon/dist/tui/mirror/ui/home-wordmark.js +11 -0
- package/packages/daemon/dist/tui/mirror/ui/key-hint.jsx +15 -3
- package/packages/daemon/dist/tui/mirror/ui/menu.jsx +8 -8
- package/packages/daemon/dist/tui/mirror/ui/overlay-frame.jsx +9 -7
- package/packages/daemon/dist/tui/mirror/ui/overlay-list-row.jsx +2 -2
- package/packages/daemon/dist/tui/mirror/ui/overlay-model.js +10 -0
- package/packages/daemon/dist/tui/mirror/ui/status-bar.jsx +2 -2
- package/packages/daemon/dist/tui/mirror/ui/surface.jsx +1 -1
- package/packages/daemon/dist/tui/mirror/ui/tabs.jsx +1 -3
- package/packages/daemon/dist/tui/mirror/workspace/application-command-description.js +6 -0
- package/packages/daemon/dist/tui/mirror/workspace/application-shortcuts.js +45 -0
- package/packages/daemon/dist/tui/mirror/workspace/palette-list-layout.js +2 -0
- 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 +6 -6
- package/packages/daemon/src/tui/mirror/runtime/application-add-machine-dialog.tsx +8 -1
- package/packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions.tsx +5 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-agent-roster.tsx +163 -36
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents-owner.ts +18 -3
- package/packages/daemon/src/tui/mirror/runtime/application-home-experience.ts +19 -1
- package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +21 -1
- package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +24 -19
- package/packages/daemon/src/tui/mirror/runtime/application-palette-command-owner.ts +6 -0
- package/packages/daemon/src/tui/mirror/runtime/application-reference-sheet.tsx +147 -73
- package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +20 -7
- package/packages/daemon/src/tui/mirror/runtime/application-shell-catalog.tsx +13 -10
- package/packages/daemon/src/tui/mirror/runtime/application-shell-home.tsx +111 -22
- package/packages/daemon/src/tui/mirror/runtime/application-shell-overlays.tsx +12 -2
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +11 -5
- package/packages/daemon/src/tui/mirror/runtime/application-shell-viewport.ts +2 -1
- package/packages/daemon/src/tui/mirror/runtime/semantic-shell-viewport-resize.ts +2 -1
- package/packages/daemon/src/tui/mirror/shell-chrome-view.tsx +50 -80
- package/packages/daemon/src/tui/mirror/shell-chrome.ts +1 -0
- package/packages/daemon/src/tui/mirror/ui/activity-indicator.tsx +19 -0
- package/packages/daemon/src/tui/mirror/ui/badge.tsx +1 -1
- package/packages/daemon/src/tui/mirror/ui/detail-row.tsx +51 -0
- package/packages/daemon/src/tui/mirror/ui/home-agent-search.tsx +81 -0
- package/packages/daemon/src/tui/mirror/ui/home-wordmark.ts +12 -0
- package/packages/daemon/src/tui/mirror/ui/key-hint.tsx +27 -3
- package/packages/daemon/src/tui/mirror/ui/menu.tsx +16 -15
- package/packages/daemon/src/tui/mirror/ui/overlay-frame.tsx +19 -11
- package/packages/daemon/src/tui/mirror/ui/overlay-list-row.tsx +2 -2
- package/packages/daemon/src/tui/mirror/ui/overlay-model.ts +11 -0
- package/packages/daemon/src/tui/mirror/ui/status-bar.tsx +2 -2
- package/packages/daemon/src/tui/mirror/ui/surface.tsx +1 -1
- package/packages/daemon/src/tui/mirror/ui/tabs.tsx +1 -4
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/application-shell-renderer.test.tsx.snap +3 -3
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/terminal-pane-header-polish-renderer.test.tsx.snap +20 -20
- package/packages/daemon/src/tui/mirror/workspace/application-command-description.ts +8 -0
- package/packages/daemon/src/tui/mirror/workspace/application-shell-view.tsx +4 -1
- package/packages/daemon/src/tui/mirror/workspace/application-shortcuts.ts +52 -0
- package/packages/daemon/src/tui/mirror/workspace/palette-list-layout.ts +1 -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.31",
|
|
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
|
@@ -4,6 +4,10 @@ import { clipTerminal, terminalDisplayWidth } from "../terminal-text.js";
|
|
|
4
4
|
import { NavigationRow } from "../ui/navigation-row.jsx";
|
|
5
5
|
import { TuiButton } from "../ui/button.jsx";
|
|
6
6
|
import { useKeyboardRoute } from "../ui/keyboard-router.jsx";
|
|
7
|
+
import { applicationPaneRenameKeyAction, applicationPaneRenamePaste, } from "./application-pane-rename-input.js";
|
|
8
|
+
import { HomeAgentSearch } from "../ui/home-agent-search.jsx";
|
|
9
|
+
import { ActivityIndicator } from "../ui/activity-indicator.jsx";
|
|
10
|
+
import { componentPalette } from "../ui/state.js";
|
|
7
11
|
import { homeAgentStatusLabel, } from "./application-home-agents.js";
|
|
8
12
|
function padCells(text, width) {
|
|
9
13
|
const clipped = clipTerminal(text, Math.max(0, width));
|
|
@@ -21,6 +25,7 @@ function statusTone(row) {
|
|
|
21
25
|
/** A flat projected roster; no subscriptions, navigation effects, or terminal input ingress. */
|
|
22
26
|
export function HomeAgentRoster(props) {
|
|
23
27
|
const [hovered, setHovered] = createSignal(null);
|
|
28
|
+
const [editing, setEditing] = createSignal(false);
|
|
24
29
|
const width = () => Math.max(0, Math.floor(props.width));
|
|
25
30
|
const height = () => Math.max(0, Math.floor(props.height));
|
|
26
31
|
const stale = (row) => row.disabled ||
|
|
@@ -29,7 +34,14 @@ export function HomeAgentRoster(props) {
|
|
|
29
34
|
const showRecovery = () => Boolean(((props.snapshot.phase === "unavailable" || props.snapshot.unavailableSessions > 0) &&
|
|
30
35
|
props.onRetry) ||
|
|
31
36
|
(props.snapshot.truncatedSessions > 0 && props.onLoadMore));
|
|
32
|
-
const
|
|
37
|
+
const rowHeight = () => (width() >= 60 && height() >= 16 ? 2 : 1);
|
|
38
|
+
const searchRows = () => (props.onQueryChange ? 2 : 0);
|
|
39
|
+
const visibleCount = () => Math.max(0, Math.floor((height() - 5 - searchRows() - (showRecovery() ? 1 : 0)) / rowHeight()));
|
|
40
|
+
const openSelected = () => {
|
|
41
|
+
const row = props.snapshot.rows.find((row) => row.key === props.selection.selectedKey);
|
|
42
|
+
if (props.inputActive && visibleCount() > 0 && row?.paneId && !stale(row))
|
|
43
|
+
props.onOpen(row, "keyboard");
|
|
44
|
+
};
|
|
33
45
|
createEffect(() => props.onViewport(visibleCount()));
|
|
34
46
|
const offset = () => Math.max(0, Math.min(props.selection.scrollOffset, Math.max(0, props.snapshot.rows.length - visibleCount())));
|
|
35
47
|
const visible = createMemo(() => props.snapshot.rows.slice(offset(), offset() + visibleCount()));
|
|
@@ -48,6 +60,8 @@ export function HomeAgentRoster(props) {
|
|
|
48
60
|
return "Agent overview unavailable";
|
|
49
61
|
if (props.snapshot.phase === "loading" && props.snapshot.rows.length === 0)
|
|
50
62
|
return "Loading agent overview…";
|
|
63
|
+
if (props.query && props.snapshot.rows.length === 0)
|
|
64
|
+
return "No matching agents";
|
|
51
65
|
if (props.snapshot.phase === "live" && props.snapshot.rows.length === 0)
|
|
52
66
|
return "No agents reported";
|
|
53
67
|
const count = props.snapshot.rows.length;
|
|
@@ -59,6 +73,8 @@ export function HomeAgentRoster(props) {
|
|
|
59
73
|
const footer = () => {
|
|
60
74
|
if (props.snapshot.note)
|
|
61
75
|
return props.snapshot.note;
|
|
76
|
+
if (props.query && props.snapshot.rows.length === 0)
|
|
77
|
+
return "Search covers loaded observations · Esc clears search";
|
|
62
78
|
if (props.snapshot.rows.length === 0) {
|
|
63
79
|
if (props.snapshot.phase === "live")
|
|
64
80
|
return "Observed sessions have no agent entries. F2 opens terminals.";
|
|
@@ -79,17 +95,19 @@ export function HomeAgentRoster(props) {
|
|
|
79
95
|
if (!props.inputActive || event.eventType !== "press" || event.ctrl || event.meta)
|
|
80
96
|
return false;
|
|
81
97
|
const key = event.name.toLowerCase();
|
|
82
|
-
const filter =
|
|
98
|
+
const filter = !editing() &&
|
|
99
|
+
(key === "f" ? props.onCycleMachine : key === "a" ? props.onToggleAttention : undefined);
|
|
83
100
|
if (filter) {
|
|
84
101
|
event.preventDefault();
|
|
85
102
|
event.stopPropagation();
|
|
86
103
|
filter();
|
|
87
104
|
return true;
|
|
88
105
|
}
|
|
89
|
-
const retry =
|
|
106
|
+
const retry = !editing() &&
|
|
107
|
+
key === "r" &&
|
|
90
108
|
(props.snapshot.phase === "unavailable" || props.snapshot.unavailableSessions > 0) &&
|
|
91
109
|
props.onRetry;
|
|
92
|
-
const more = key === "m" && props.snapshot.truncatedSessions > 0 && props.onLoadMore;
|
|
110
|
+
const more = !editing() && key === "m" && props.snapshot.truncatedSessions > 0 && props.onLoadMore;
|
|
93
111
|
const recovery = retry || more;
|
|
94
112
|
if (recovery) {
|
|
95
113
|
event.preventDefault();
|
|
@@ -112,7 +130,7 @@ export function HomeAgentRoster(props) {
|
|
|
112
130
|
props.onMove(delta);
|
|
113
131
|
return true;
|
|
114
132
|
});
|
|
115
|
-
return (<box width={width()} height={height()} flexShrink={0} flexDirection="column" overflow="hidden" onMouseScroll={(event) => {
|
|
133
|
+
return (<box position="relative" width={width()} height={height()} flexShrink={0} flexDirection="column" overflow="hidden" onMouseScroll={(event) => {
|
|
116
134
|
if (!props.inputActive)
|
|
117
135
|
return;
|
|
118
136
|
const direction = event.scroll?.direction;
|
|
@@ -122,38 +140,96 @@ export function HomeAgentRoster(props) {
|
|
|
122
140
|
event.stopPropagation();
|
|
123
141
|
props.onMove(direction === "up" ? -1 : 1);
|
|
124
142
|
}} onMouseOut={() => setHovered(null)}>
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
</
|
|
134
|
-
<
|
|
135
|
-
<text width={width()} height={1}
|
|
136
|
-
|
|
143
|
+
<box height={searchRows()} flexShrink={0}/>
|
|
144
|
+
<box width={width()} height={1} flexShrink={0} flexDirection="row">
|
|
145
|
+
<Show when={props.snapshot.phase === "loading" || props.snapshot.loadingSessions > 0}>
|
|
146
|
+
<ActivityIndicator theme={props.theme} active={props.inputActive}/>
|
|
147
|
+
</Show>
|
|
148
|
+
<text height={1} flexGrow={1} fg={props.theme.roles.text.muted}>
|
|
149
|
+
{clipTerminal(title(), width())}
|
|
150
|
+
</text>
|
|
151
|
+
</box>
|
|
152
|
+
<box width={width()} height={1} flexShrink={0} flexDirection="row">
|
|
153
|
+
<text width={Math.max(0, width() - (props.onToggleAttention ? 16 : 0))} height={1} fg={props.theme.roles.text.secondary} onMouseDown={(event) => {
|
|
154
|
+
if (!props.inputActive || event.button !== 0)
|
|
155
|
+
return;
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
event.stopPropagation();
|
|
158
|
+
props.onCycleMachine?.();
|
|
159
|
+
}}>
|
|
160
|
+
{clipTerminal(props.filterLabel ? `${props.filterLabel} · f machine` : "", Math.max(0, width() - (props.onToggleAttention ? 16 : 0)))}
|
|
137
161
|
</text>
|
|
138
|
-
<
|
|
162
|
+
<Show when={props.onToggleAttention}>
|
|
163
|
+
<TuiButton theme={props.theme} label="Attention" shortcut="a" size="compact" variant="ghost" background={props.theme.roles.surfaces.canvas} width={Math.min(16, width())} onPress={() => {
|
|
164
|
+
if (props.inputActive)
|
|
165
|
+
props.onToggleAttention?.();
|
|
166
|
+
}}/>
|
|
167
|
+
</Show>
|
|
168
|
+
</box>
|
|
169
|
+
<Show when={props.snapshot.rows.length > 0} fallback={<box height={Math.max(0, visibleCount() * rowHeight() + 1)} flexShrink={0} flexDirection="column" overflow="hidden">
|
|
170
|
+
<Show when={props.snapshot.phase === "live" && !props.query}>
|
|
171
|
+
<text height={1} width={width()} fg={props.theme.roles.text.primary}>
|
|
172
|
+
{clipTerminal("Your next session starts here", width())}
|
|
173
|
+
</text>
|
|
174
|
+
<text height={1} width={width()} fg={props.theme.roles.text.muted}>
|
|
175
|
+
{clipTerminal("Open terminals, or use Commands to connect a machine.", width())}
|
|
176
|
+
</text>
|
|
177
|
+
</Show>
|
|
178
|
+
</box>}>
|
|
179
|
+
<box height={1} flexShrink={0}/>
|
|
180
|
+
<box height={visibleCount() * rowHeight()} width={width()} flexShrink={0} flexDirection="column" overflow="hidden">
|
|
139
181
|
<For each={keys()}>
|
|
140
182
|
{(key) => {
|
|
141
183
|
const row = () => byKey().get(key);
|
|
142
|
-
return (<box height={
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
184
|
+
return (<box height={rowHeight()} flexDirection="column" backgroundColor={componentPalette(props.theme, {
|
|
185
|
+
selected: props.selection.selectedKey === key,
|
|
186
|
+
disabled: row().paneId === null || stale(row()),
|
|
187
|
+
}).background} width={width()} flexShrink={0} onMouseDown={(event) => {
|
|
188
|
+
if (!props.inputActive || event.button !== 0 || !row().paneId || stale(row()))
|
|
189
|
+
return;
|
|
190
|
+
event.preventDefault();
|
|
191
|
+
event.stopPropagation();
|
|
192
|
+
props.onSelect(key);
|
|
193
|
+
props.onOpen(row(), "mouse");
|
|
194
|
+
}} onMouseOver={() => setHovered(key)} onMouseMove={() => setHovered(key)}>
|
|
195
|
+
<NavigationRow theme={props.theme} id={`home-agent:${key}`} width={width()} label={rowHeight() > 1
|
|
196
|
+
? row().name
|
|
197
|
+
: padCells(row().name, columns().agent) +
|
|
198
|
+
padCells([row().machineLabel, row().serverLabel, row().sessionName]
|
|
199
|
+
.filter(Boolean)
|
|
200
|
+
.join(" / "), columns().session)} detail={padCells(`${!stale(row()) && row().attention ? "! " : ""}${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) => {
|
|
147
201
|
if (!props.inputActive || row().paneId === null || stale(row()))
|
|
148
202
|
return;
|
|
149
203
|
props.onSelect(key);
|
|
150
204
|
props.onOpen(row(), source);
|
|
151
205
|
}}/>
|
|
206
|
+
<Show when={rowHeight() > 1}>
|
|
207
|
+
<text width={width()} height={1} fg={props.selection.selectedKey === key
|
|
208
|
+
? props.theme.roles.selection.selectionText
|
|
209
|
+
: props.theme.roles.text.muted} bg={componentPalette(props.theme, {
|
|
210
|
+
selected: props.selection.selectedKey === key,
|
|
211
|
+
disabled: row().paneId === null || stale(row()),
|
|
212
|
+
}).background}>
|
|
213
|
+
{clipTerminal(` ${[row().sessionName, row().machineLabel, row().serverLabel, row().harness].filter(Boolean).join(" · ")}`, width())}
|
|
214
|
+
</text>
|
|
215
|
+
</Show>
|
|
152
216
|
</box>);
|
|
153
217
|
}}
|
|
154
218
|
</For>
|
|
155
219
|
</box>
|
|
156
220
|
</Show>
|
|
221
|
+
<Show when={props.onQueryChange}>
|
|
222
|
+
{(onQueryChange) => (<box position="absolute" top={0} left={0} width={width()} height={1}>
|
|
223
|
+
<HomeAgentSearch theme={props.theme} width={width()} query={props.query ?? ""} active={props.inputActive} onChange={onQueryChange()} onEdit={(event) => {
|
|
224
|
+
const action = applicationPaneRenameKeyAction(event, props.query ?? "");
|
|
225
|
+
if (action.kind === "update")
|
|
226
|
+
onQueryChange()(action.value);
|
|
227
|
+
}} onPaste={(bytes) => onQueryChange()(applicationPaneRenamePaste(props.query ?? "", bytes))} onEditing={setEditing} onSubmit={openSelected}/>
|
|
228
|
+
</box>)}
|
|
229
|
+
</Show>
|
|
230
|
+
<text width={width()} height={1} flexShrink={0} fg={props.theme.roles.text.muted}>
|
|
231
|
+
{clipTerminal(coverage(), width())}
|
|
232
|
+
</text>
|
|
157
233
|
<text width={width()} height={1} flexShrink={0} fg={props.theme.roles.text.muted}>
|
|
158
234
|
{clipTerminal(footer(), width())}
|
|
159
235
|
</text>
|
|
@@ -189,12 +189,21 @@ export function createApplicationHomeNavigationOwner(options) {
|
|
|
189
189
|
observer: options.observer,
|
|
190
190
|
});
|
|
191
191
|
const paletteCommands = createApplicationPaletteCommandOwner({
|
|
192
|
-
commands: () =>
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
192
|
+
commands: () => [
|
|
193
|
+
...(options.fleetCommands
|
|
194
|
+
? [
|
|
195
|
+
...applicationPaletteCommands(options.shell().semantic).filter((c) => typeof c === "string"),
|
|
196
|
+
...options.fleetCommands(),
|
|
197
|
+
]
|
|
198
|
+
: applicationPaletteCommands(options.shell().semantic, options.catalog.sessionNames())),
|
|
199
|
+
...(options.toggleSidebar
|
|
200
|
+
? [
|
|
201
|
+
options.sidebarVisible?.() === false
|
|
202
|
+
? "show-sidebar"
|
|
203
|
+
: "hide-sidebar",
|
|
204
|
+
]
|
|
205
|
+
: []),
|
|
206
|
+
],
|
|
198
207
|
isOpen: () => Boolean(options.shell().semantic?.focus.palette.open ?? options.shell().localPaletteOpen),
|
|
199
208
|
targetKey: () => `${applicationGenerationNavigationKey(options.sessionOwner()?.snapshot() ?? null)}:${options.focusedPane()}`,
|
|
200
209
|
disabledReason: (command) => {
|
|
@@ -204,6 +213,8 @@ export function createApplicationHomeNavigationOwner(options) {
|
|
|
204
213
|
command === "home" ||
|
|
205
214
|
command === "terminals" ||
|
|
206
215
|
command === "appearance" ||
|
|
216
|
+
command === "hide-sidebar" ||
|
|
217
|
+
command === "show-sidebar" ||
|
|
207
218
|
command === "shortcuts" ||
|
|
208
219
|
command === "whats-new" ||
|
|
209
220
|
command === "switch-session")
|
|
@@ -218,6 +229,7 @@ export function createApplicationHomeNavigationOwner(options) {
|
|
|
218
229
|
setSurface: options.setSurface,
|
|
219
230
|
setNote: options.setNote,
|
|
220
231
|
openAppearance: options.openAppearance,
|
|
232
|
+
toggleSidebar: options.toggleSidebar,
|
|
221
233
|
openSessions: options.openSessions,
|
|
222
234
|
zoomPane: options.zoomPane,
|
|
223
235
|
newWindow: options.interaction.newWindow,
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
+
import { createEffect } from "solid-js";
|
|
1
2
|
import { createApplicationHomeFleetOwner } from "./application-home-fleet.js";
|
|
2
3
|
import { createApplicationHomeNavigationOwner } from "./application-home-agents-owner.js";
|
|
3
4
|
import { createApplicationGuidedTourIntegration } from "./application-guided-tour-integration.jsx";
|
|
5
|
+
/** A late modal dismissal must not restore focus into Home's hidden sidebar. */
|
|
6
|
+
export function createHomeSidebarFocusGuard(surface, focused, blur, sidebarVisible = () => true) {
|
|
7
|
+
createEffect(() => {
|
|
8
|
+
if ((surface() === "home" || !sidebarVisible()) && focused())
|
|
9
|
+
blur();
|
|
10
|
+
});
|
|
11
|
+
}
|
|
4
12
|
/** Compose Home's fleet, navigation and learning experience without additional transports. */
|
|
5
13
|
export function createApplicationHomeExperience(options) {
|
|
6
14
|
const { machines, appearance } = options;
|
|
15
|
+
createHomeSidebarFocusGuard(options.activeSurface, machines.focused, () => machines.sidebar.onBlur?.(), options.sidebarVisible);
|
|
7
16
|
const paletteOpen = () => options.shell().semantic?.focus.palette.open ?? options.shell().localPaletteOpen;
|
|
8
17
|
const fleetHome = createApplicationHomeFleetOwner({
|
|
9
18
|
catalog: machines.catalog,
|
|
@@ -41,6 +41,17 @@ export function projectHomeFleet(catalog, groups, filter) {
|
|
|
41
41
|
rows.sort((a, b) => a.key.localeCompare(b.key));
|
|
42
42
|
const missing = machines.filter((machine) => machine.state !== "ready" ||
|
|
43
43
|
!groups.find((group) => group.machineId === machine.id)?.available);
|
|
44
|
+
const query = filter.query?.trim().toLocaleLowerCase() ?? "";
|
|
45
|
+
const matching = query
|
|
46
|
+
? rows.filter((row) => [
|
|
47
|
+
row.name,
|
|
48
|
+
row.harness,
|
|
49
|
+
row.projectName,
|
|
50
|
+
row.sessionName,
|
|
51
|
+
row.machineLabel,
|
|
52
|
+
row.serverLabel,
|
|
53
|
+
].some((value) => value?.toLocaleLowerCase().includes(query)))
|
|
54
|
+
: rows;
|
|
44
55
|
return {
|
|
45
56
|
phase: missing.length
|
|
46
57
|
? rows.length || observedSessions
|
|
@@ -49,7 +60,7 @@ export function projectHomeFleet(catalog, groups, filter) {
|
|
|
49
60
|
? "loading"
|
|
50
61
|
: "unavailable"
|
|
51
62
|
: "live",
|
|
52
|
-
rows,
|
|
63
|
+
rows: matching,
|
|
53
64
|
observedSessions,
|
|
54
65
|
totalSessions,
|
|
55
66
|
loadingSessions,
|
|
@@ -92,6 +103,12 @@ export function createApplicationHomeFleetOwner(options) {
|
|
|
92
103
|
selection.dispose();
|
|
93
104
|
});
|
|
94
105
|
const presentation = {
|
|
106
|
+
get agentQuery() {
|
|
107
|
+
return filter().query ?? "";
|
|
108
|
+
},
|
|
109
|
+
onAgentQueryChange(query) {
|
|
110
|
+
setFilter((value) => ({ ...value, query }));
|
|
111
|
+
},
|
|
95
112
|
get agentRoster() {
|
|
96
113
|
return snapshot();
|
|
97
114
|
},
|
|
@@ -165,10 +165,13 @@ export function ApplicationMachineSidebar(props) {
|
|
|
165
165
|
else
|
|
166
166
|
toggle(row.group);
|
|
167
167
|
};
|
|
168
|
+
const sectionGap = (row) => !row.session && !row.agent && !row.server && row.group.id !== props.model.groups()[0]?.id
|
|
169
|
+
? 1
|
|
170
|
+
: 0;
|
|
171
|
+
const rowHeight = (row) => (row.agent ? (row.agentHeading ? 3 : 2) : row.serverHeading ? 2 : 1) + sectionGap(row);
|
|
168
172
|
const revealFocusedRow = () => {
|
|
169
173
|
if (!focused() || !scroll)
|
|
170
174
|
return;
|
|
171
|
-
const rowHeight = (row) => row.agent ? (row.agentHeading ? 3 : 2) : row.serverHeading ? 2 : 1;
|
|
172
175
|
const y = rows()
|
|
173
176
|
.slice(0, index())
|
|
174
177
|
.reduce((sum, row) => sum + rowHeight(row), 0);
|
|
@@ -342,7 +345,8 @@ export function ApplicationMachineSidebar(props) {
|
|
|
342
345
|
return current().agentHeading;
|
|
343
346
|
},
|
|
344
347
|
};
|
|
345
|
-
return (<box width={Math.max(1, props.width - 1)} height={
|
|
348
|
+
return (<box width={Math.max(1, props.width - 1)} height={rowHeight(row)} flexShrink={0} flexDirection="column">
|
|
349
|
+
<box height={sectionGap(row)} flexShrink={0}/>
|
|
346
350
|
<Show when={row.serverHeading}>
|
|
347
351
|
<NavigationRow theme={props.theme} width={Math.max(1, props.width - 1)} id={`server:${row.group.id}:${row.session?.server?.serverId}`} label={row.serverHeading ?? "Server"} marker=" ▾" onActivate={() => {
|
|
348
352
|
if (row.session?.server)
|
|
@@ -364,19 +368,13 @@ export function ApplicationMachineSidebar(props) {
|
|
|
364
368
|
: row.group.label} marker={row.server
|
|
365
369
|
? " "
|
|
366
370
|
: row.agent
|
|
367
|
-
?
|
|
368
|
-
?
|
|
369
|
-
|
|
370
|
-
: "›"
|
|
371
|
-
: row.agent.attention
|
|
372
|
-
? "!"
|
|
373
|
-
: "•"
|
|
371
|
+
? row.agent.attention
|
|
372
|
+
? "!"
|
|
373
|
+
: " "
|
|
374
374
|
: row.session
|
|
375
375
|
? props.model.favorites?.().includes(row.session.id)
|
|
376
376
|
? " ★"
|
|
377
|
-
:
|
|
378
|
-
? " ›"
|
|
379
|
-
: " "
|
|
377
|
+
: " "
|
|
380
378
|
: collapsed().has(preferenceKey(row.group))
|
|
381
379
|
? "▸"
|
|
382
380
|
: "▾"} detail={row.server
|
|
@@ -386,14 +384,16 @@ export function ApplicationMachineSidebar(props) {
|
|
|
386
384
|
: row.agent
|
|
387
385
|
? row.group.state !== "ready" || row.agent.disabled
|
|
388
386
|
? "unavailable"
|
|
389
|
-
:
|
|
387
|
+
: terminalAgentStatusLabel(row.agent.activity).toLowerCase()
|
|
390
388
|
: row.session
|
|
391
389
|
? row.group.state !== "ready" || row.session.disabled
|
|
392
390
|
? "unavailable"
|
|
393
391
|
: `${row.session.paneCount}p${row.group.agents?.length ? ` ${activity(row).label}` : ""}`
|
|
394
392
|
: row.group.agents?.length && row.group.state === "ready"
|
|
395
393
|
? activity(row).label
|
|
396
|
-
: `${connectionDetail(row.group)}${row.group.agents?.length ? " ?" : ""}`} selected={Boolean((row.session || row.agent) && active(row))} focused={Boolean(focused() && row.key === selectedKey())}
|
|
394
|
+
: `${connectionDetail(row.group)}${row.group.agents?.length ? " ?" : ""}`} selected={Boolean((row.session || row.agent) && active(row))} focused={Boolean(focused() && row.key === selectedKey())} status={(row.agent?.attention ?? activity(row).kind === "attention")
|
|
395
|
+
? "blocked"
|
|
396
|
+
: undefined} onActivate={(source) => activate(row, source)}/>
|
|
397
397
|
<Show when={row.agent}>
|
|
398
398
|
{(agent) => (<text height={1} fg={props.theme.roles.text.muted} content={clipTerminal(` ${friendlySessionLabel(agent().sessionName)}`, Math.max(1, props.width - 1))}/>)}
|
|
399
399
|
</Show>
|
|
@@ -412,7 +412,7 @@ export function ApplicationMachineSidebar(props) {
|
|
|
412
412
|
</text>
|
|
413
413
|
</Show>
|
|
414
414
|
<Show when={props.model.onOpenSwitcher}>
|
|
415
|
-
<NavigationRow theme={props.theme} id="machine:switcher" label="
|
|
415
|
+
<NavigationRow theme={props.theme} id="machine:switcher" label="Sessions" detail="F6" marker=" " width={props.width} onActivate={() => props.model.onOpenSwitcher?.()}/>
|
|
416
416
|
</Show>
|
|
417
417
|
<Show when={props.model.onOpenAttention}>
|
|
418
418
|
<NavigationRow theme={props.theme} id="machine:attention" label={`Attention (${props.model.groups().reduce((sum, group) => sum + (group.state === "ready" ? (group.agents ?? []).filter((agent) => agent.attention && !agent.disabled).length : 0), 0)}) · F7`} marker="!" width={props.width} onActivate={() => props.model.onOpenAttention?.()}/>
|
|
@@ -429,7 +429,7 @@ export function ApplicationMachineSidebar(props) {
|
|
|
429
429
|
</>)}
|
|
430
430
|
</Show>
|
|
431
431
|
<For each={props.model.onAddMachine ? [props.model.onAddMachine] : []}>
|
|
432
|
-
{(add) => (<NavigationRow theme={props.theme} id="machine:add" label="Add machine
|
|
432
|
+
{(add) => (<NavigationRow theme={props.theme} id="machine:add" label="Add machine" detail="A" marker=" " width={props.width} onActivate={add}/>)}
|
|
433
433
|
</For>
|
|
434
434
|
</Surface>);
|
|
435
435
|
}
|
|
@@ -41,6 +41,11 @@ export function createApplicationPaletteCommandOwner(options) {
|
|
|
41
41
|
options.openSessions();
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
+
if (command === "hide-sidebar" || command === "show-sidebar") {
|
|
45
|
+
setOpen(false, source);
|
|
46
|
+
options.toggleSidebar?.();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
44
49
|
if (command === "appearance") {
|
|
45
50
|
setOpen(false, source);
|
|
46
51
|
options.openAppearance?.();
|
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
import { interactionReceiptTargetLabel } from "@tmux-ide/core";
|
|
2
2
|
import { For, Show } from "solid-js";
|
|
3
3
|
import { clipTerminal, terminalDisplayWidth } from "../terminal-text.js";
|
|
4
|
+
import { DetailRow } from "../ui/detail-row.jsx";
|
|
4
5
|
import { TuiButton } from "../ui/button.jsx";
|
|
5
6
|
import { HomeAgentRoster } from "./application-home-agent-roster.jsx";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import { APPLICATION_HOME_WORDMARK, APPLICATION_HOME_WORDMARK_WIDTH } from "../ui/home-wordmark.js";
|
|
8
|
+
/** Keep the marketing wordmark intact, falling back when the agent list needs the room. */
|
|
9
|
+
export function applicationHomeBrandVariant(width, height) {
|
|
10
|
+
return width >= APPLICATION_HOME_WORDMARK_WIDTH && height >= 28 ? "ascii" : "wordmark";
|
|
9
11
|
}
|
|
10
12
|
/** Presentation only: session data, commands, and keyboard admission stay with the shell. */
|
|
11
13
|
export function ApplicationHomeSurface(props) {
|
|
12
14
|
const width = () => Math.max(0, Math.floor(props.width));
|
|
13
15
|
const height = () => Math.max(0, Math.floor(props.height));
|
|
14
|
-
const inset = () => (width() >= 40 ? 2 : width() >= 12 ? 1 : 0);
|
|
16
|
+
const inset = () => Math.max(width() >= 40 ? 2 : width() >= 12 ? 1 : 0, props.branded ? Math.floor((width() - 96) / 2) : 0);
|
|
15
17
|
const bodyWidth = () => Math.max(0, width() - inset() * 2);
|
|
18
|
+
const showAscii = () => props.branded &&
|
|
19
|
+
props.agentRoster?.phase === "live" &&
|
|
20
|
+
props.agentRoster.rows.length === 0 &&
|
|
21
|
+
!props.agentQuery &&
|
|
22
|
+
applicationHomeBrandVariant(bodyWidth(), height()) === "ascii";
|
|
23
|
+
const brandRows = () => (showAscii() ? APPLICATION_HOME_WORDMARK.length : 1);
|
|
16
24
|
const spacious = () => height() >= 14;
|
|
17
|
-
const context = () => clipTerminal(
|
|
25
|
+
const context = () => clipTerminal(props.branded && props.agentRoster
|
|
26
|
+
? "Your agents, across your machines"
|
|
27
|
+
: `${props.session ?? "No session selected"} · ${props.status}`, bodyWidth());
|
|
18
28
|
const summary = () => {
|
|
19
29
|
if (!props.session || props.agents === undefined)
|
|
20
30
|
return "Agent signals unavailable";
|
|
@@ -24,7 +34,7 @@ export function ApplicationHomeSurface(props) {
|
|
|
24
34
|
};
|
|
25
35
|
const themeLabel = () => `Theme: ${props.theme.setting}`;
|
|
26
36
|
// Use the existing TuiButton cell budget for both its label and hit target.
|
|
27
|
-
const naturalButtonWidth = (label, shortcut) => terminalDisplayWidth(label) + (shortcut ? terminalDisplayWidth(shortcut) + 1 : 0) +
|
|
37
|
+
const naturalButtonWidth = (label, shortcut) => terminalDisplayWidth(label) + (shortcut ? terminalDisplayWidth(shortcut) + 1 : 0) + 2;
|
|
28
38
|
const buttonWidth = (label, shortcut) => Math.min(bodyWidth(), naturalButtonWidth(label, shortcut));
|
|
29
39
|
const actionsInRow = () => bodyWidth() >=
|
|
30
40
|
naturalButtonWidth("Open terminals", "F2") +
|
|
@@ -32,14 +42,31 @@ export function ApplicationHomeSurface(props) {
|
|
|
32
42
|
(props.onCycleTheme ? naturalButtonWidth(themeLabel()) + 2 : 0) +
|
|
33
43
|
(props.onOpenTutorial ? naturalButtonWidth(props.tutorialLabel ?? "Learn tmux-ide") + 2 : 0) +
|
|
34
44
|
2;
|
|
35
|
-
const reservedRows = () => (
|
|
45
|
+
const reservedRows = () => brandRows() -
|
|
46
|
+
1 +
|
|
47
|
+
(spacious() ? 4 : 2) +
|
|
36
48
|
(actionsInRow() ? 1 : 2 + (props.onCycleTheme ? 1 : 0) + (props.onOpenTutorial ? 1 : 0)) +
|
|
37
49
|
(spacious() ? 1 : 0) +
|
|
38
50
|
(props.note ? (spacious() ? 2 : 1) : 0);
|
|
39
|
-
const
|
|
40
|
-
|
|
51
|
+
const activityRows = () => (height() >= 24 && bodyWidth() >= 48 ? 2 : 1);
|
|
52
|
+
const selectedAgent = () => props.agentRoster?.rows.find((row) => row.key === props.agentSelection?.selectedKey);
|
|
53
|
+
// Receipts lack a fleet-wide machine identity. Never attribute a same-named
|
|
54
|
+
// workspace/pane collision to the selected agent.
|
|
55
|
+
const selectedActivity = () => {
|
|
56
|
+
const selected = selectedAgent();
|
|
57
|
+
if (!selected?.paneId || selected.daemonInstanceId !== props.activityDaemonId)
|
|
58
|
+
return [];
|
|
59
|
+
const matches = props.agentRoster?.rows.filter((row) => row.sessionName === selected.sessionName && row.paneId === selected.paneId);
|
|
60
|
+
if (matches?.length !== 1)
|
|
61
|
+
return [];
|
|
62
|
+
return (props.recentPaneActivity ?? []).filter((receipt) => receipt.workspaceName === selected.sessionName &&
|
|
63
|
+
receipt.target.kind === "pane" &&
|
|
64
|
+
receipt.target.semanticPaneId === selected.paneId);
|
|
65
|
+
};
|
|
66
|
+
const recentActivity = () => selectedActivity().slice(0, spacious()
|
|
67
|
+
? Math.max(0, Math.min(1, Math.floor((height() - reservedRows() - (props.agentRoster ? 12 : 2) - 2) / activityRows())))
|
|
41
68
|
: 0);
|
|
42
|
-
const activityHeight = () =>
|
|
69
|
+
const activityHeight = () => recentActivity().length > 0 ? recentActivity().length * activityRows() + 2 : 0;
|
|
43
70
|
const paneLabel = (paneId) => {
|
|
44
71
|
const matches = props.agentRoster?.rows.filter((row) => row.paneId === paneId) ?? [];
|
|
45
72
|
return matches.length === 1 ? matches[0].name : paneId;
|
|
@@ -63,9 +90,19 @@ export function ApplicationHomeSurface(props) {
|
|
|
63
90
|
: "timed out";
|
|
64
91
|
const rosterHeight = () => Math.max(0, height() - reservedRows() - activityHeight());
|
|
65
92
|
return (<box id="application-home" width={width()} height={height()} paddingLeft={inset()} paddingRight={inset()} paddingTop={spacious() ? 1 : 0} flexDirection="column" alignItems="flex-start" backgroundColor={props.theme.roles.surfaces.canvas} overflow="hidden">
|
|
66
|
-
<
|
|
67
|
-
<
|
|
68
|
-
|
|
93
|
+
<box height={brandRows()} width={bodyWidth()} flexShrink={0} flexDirection="column">
|
|
94
|
+
<Show when={showAscii()} fallback={<text width={bodyWidth()} height={1} fg={props.theme.roles.text.primary}>
|
|
95
|
+
<strong>
|
|
96
|
+
{clipTerminal(props.branded ? "tmux-ide" : props.project, bodyWidth())}
|
|
97
|
+
</strong>
|
|
98
|
+
</text>}>
|
|
99
|
+
<For each={APPLICATION_HOME_WORDMARK}>
|
|
100
|
+
{(line) => (<text width={bodyWidth()} height={1} flexShrink={0} fg={props.theme.roles.text.primary}>
|
|
101
|
+
{" ".repeat(Math.max(0, Math.floor((bodyWidth() - APPLICATION_HOME_WORDMARK_WIDTH) / 2))) + line}
|
|
102
|
+
</text>)}
|
|
103
|
+
</For>
|
|
104
|
+
</Show>
|
|
105
|
+
</box>
|
|
69
106
|
<box height={spacious() ? 1 : 0} flexShrink={0}/>
|
|
70
107
|
<text width={bodyWidth()} height={1} flexShrink={0} fg={props.theme.roles.text.secondary}>
|
|
71
108
|
{context()}
|
|
@@ -79,17 +116,15 @@ export function ApplicationHomeSurface(props) {
|
|
|
79
116
|
{clipTerminal(summary(), bodyWidth())}
|
|
80
117
|
</text>
|
|
81
118
|
</>}>
|
|
82
|
-
{(snapshot) => (<HomeAgentRoster filterLabel={props.agentFilterLabel} onCycleMachine={props.onCycleAgentMachine} onToggleAttention={props.onToggleAgentAttention} theme={props.theme} width={bodyWidth()} height={rosterHeight()} snapshot={snapshot()} selection={props.agentSelection ?? { selectedKey: null, scrollOffset: 0 }} inputActive={props.agentInputActive ?? false} onSelect={(key) => props.onSelectAgent?.(key)} onMove={(delta) => props.onMoveAgent?.(delta)} onViewport={(rows) => props.onAgentViewport?.(rows)} onOpen={(row, source) => props.onOpenAgent?.(row, source)} onRetry={props.onRetryAgents} onLoadMore={props.onLoadMoreAgents}/>)}
|
|
119
|
+
{(snapshot) => (<HomeAgentRoster query={props.agentQuery} onQueryChange={props.onAgentQueryChange} filterLabel={props.agentFilterLabel} onCycleMachine={props.onCycleAgentMachine} onToggleAttention={props.onToggleAgentAttention} theme={props.theme} width={bodyWidth()} height={rosterHeight()} snapshot={snapshot()} selection={props.agentSelection ?? { selectedKey: null, scrollOffset: 0 }} inputActive={props.agentInputActive ?? false} onSelect={(key) => props.onSelectAgent?.(key)} onMove={(delta) => props.onMoveAgent?.(delta)} onViewport={(rows) => props.onAgentViewport?.(rows)} onOpen={(row, source) => props.onOpenAgent?.(row, source)} onRetry={props.onRetryAgents} onLoadMore={props.onLoadMoreAgents}/>)}
|
|
83
120
|
</Show>
|
|
84
121
|
<Show when={activityHeight() > 0}>
|
|
85
122
|
<box width={bodyWidth()} height={activityHeight()} flexShrink={0} flexDirection="column" overflow="hidden">
|
|
86
123
|
<text height={1} width={bodyWidth()} fg={props.theme.roles.text.secondary}>
|
|
87
|
-
{clipTerminal("
|
|
124
|
+
{clipTerminal(`${selectedAgent()?.name ?? "Agent"} · latest activity`, bodyWidth())}
|
|
88
125
|
</text>
|
|
89
126
|
<For each={recentActivity()}>
|
|
90
|
-
{(receipt) => (<
|
|
91
|
-
{clipTerminal(`${activityTime(receipt)} · ${interactionReceiptTargetLabel(receipt, paneLabel)} · ${activityPhase(receipt)}`, bodyWidth())}
|
|
92
|
-
</text>)}
|
|
127
|
+
{(receipt) => (<DetailRow theme={props.theme} width={bodyWidth()} label={interactionReceiptTargetLabel(receipt, paneLabel)} detail={activityPhase(receipt)} description={activityRows() === 2 ? activityTime(receipt) : undefined} attention={receipt.phase === "rejected" || receipt.phase === "timed-out"}/>)}
|
|
93
128
|
</For>
|
|
94
129
|
<text height={1} width={bodyWidth()} fg={props.theme.roles.text.muted}>
|
|
95
130
|
{clipTerminal("Activity reported through tmux-ide", bodyWidth())}
|
|
@@ -98,13 +133,13 @@ export function ApplicationHomeSurface(props) {
|
|
|
98
133
|
</Show>
|
|
99
134
|
<box height={spacious() ? 1 : 0} flexShrink={0}/>
|
|
100
135
|
<box width={bodyWidth()} flexShrink={0} flexDirection={actionsInRow() ? "row" : "column"} alignItems="flex-start" gap={actionsInRow() ? 2 : 0}>
|
|
101
|
-
<TuiButton theme={props.theme} label="Open terminals" shortcut="F2" width={buttonWidth("Open terminals", "F2")} variant="
|
|
102
|
-
<TuiButton theme={props.theme} label="Commands" shortcut="F5" width={buttonWidth("Commands", "F5")} onPress={props.onOpenCommands}/>
|
|
136
|
+
<TuiButton theme={props.theme} label="Open terminals" shortcut="F2" width={buttonWidth("Open terminals", "F2")} size="compact" variant="ghost" background={props.theme.roles.surfaces.canvas} onPress={props.onOpenTerminals}/>
|
|
137
|
+
<TuiButton theme={props.theme} label="Commands" size="compact" variant="ghost" background={props.theme.roles.surfaces.canvas} shortcut="F5" width={buttonWidth("Commands", "F5")} onPress={props.onOpenCommands}/>
|
|
103
138
|
<Show when={props.onOpenTutorial}>
|
|
104
|
-
{(open) => (<TuiButton theme={props.theme} label={props.tutorialLabel ?? "Learn tmux-ide"} width={buttonWidth(props.tutorialLabel ?? "Learn tmux-ide")} onPress={open()}/>)}
|
|
139
|
+
{(open) => (<TuiButton theme={props.theme} label={props.tutorialLabel ?? "Learn tmux-ide"} size="compact" variant="ghost" background={props.theme.roles.surfaces.canvas} width={buttonWidth(props.tutorialLabel ?? "Learn tmux-ide")} onPress={open()}/>)}
|
|
105
140
|
</Show>
|
|
106
141
|
<Show when={props.onCycleTheme}>
|
|
107
|
-
{(onCycleTheme) => (<TuiButton theme={props.theme} label={themeLabel()} width={buttonWidth(themeLabel())} variant="ghost" background={props.theme.roles.surfaces.canvas} onPress={onCycleTheme()}/>)}
|
|
142
|
+
{(onCycleTheme) => (<TuiButton theme={props.theme} label={themeLabel()} width={buttonWidth(themeLabel())} size="compact" variant="ghost" background={props.theme.roles.surfaces.canvas} onPress={onCycleTheme()}/>)}
|
|
108
143
|
</Show>
|
|
109
144
|
</box>
|
|
110
145
|
</Show>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { shellChromeLayout } from "../shell-chrome.js";
|
|
2
2
|
/** The one physical terminal viewport after production shell chrome. */
|
|
3
|
-
export function applicationShellViewport(dimensions, hasSemanticShell) {
|
|
3
|
+
export function applicationShellViewport(dimensions, hasSemanticShell, sidebarVisible = true) {
|
|
4
4
|
if (!hasSemanticShell)
|
|
5
5
|
return {
|
|
6
6
|
width: Math.max(1, dimensions.width),
|
|
7
7
|
height: Math.max(2, dimensions.height - 2),
|
|
8
8
|
};
|
|
9
|
-
const shell = shellChromeLayout(dimensions.width, dimensions.height, 28);
|
|
9
|
+
const shell = shellChromeLayout(dimensions.width, dimensions.height, sidebarVisible ? 28 : 0);
|
|
10
10
|
return {
|
|
11
11
|
width: Math.max(1, shell.main.width),
|
|
12
12
|
height: Math.max(2, shell.main.height - shell.status.height - 1),
|
|
@@ -3,7 +3,7 @@ import { applicationShellViewport } from "./application-shell-viewport.js";
|
|
|
3
3
|
* Owns the one semantic-shell viewport resize for the current generation.
|
|
4
4
|
* Provisional local chrome is intentionally not terminal geometry authority.
|
|
5
5
|
*/
|
|
6
|
-
export function createSemanticShellViewportResizeOwner(getLayout = () => ({ current: { paneBorderStatus: "top" } })) {
|
|
6
|
+
export function createSemanticShellViewportResizeOwner(getLayout = () => ({ current: { paneBorderStatus: "top" } }), sidebarVisible = () => true) {
|
|
7
7
|
let disposed = false;
|
|
8
8
|
let applied = null;
|
|
9
9
|
let pending = null;
|
|
@@ -118,7 +118,7 @@ export function createSemanticShellViewportResizeOwner(getLayout = () => ({ curr
|
|
|
118
118
|
retry?.();
|
|
119
119
|
}) ?? null;
|
|
120
120
|
}
|
|
121
|
-
const viewport = applicationShellViewport(dimensions, true);
|
|
121
|
+
const viewport = applicationShellViewport(dimensions, true, sidebarVisible());
|
|
122
122
|
const lane = generation.fastLane;
|
|
123
123
|
const target = Object.freeze({
|
|
124
124
|
lane,
|
|
@@ -15,6 +15,8 @@ export function shellOverlayWidth(terminalWidth, variant, kind) {
|
|
|
15
15
|
return Math.min(safe, Math.max(1, Math.min(preferred, safe - margin)));
|
|
16
16
|
}
|
|
17
17
|
export function shellSidebarWidth(terminalWidth, preferredWidth, variant = shellChromeVariant(terminalWidth, 24)) {
|
|
18
|
+
if (preferredWidth === 0)
|
|
19
|
+
return 0;
|
|
18
20
|
const safe = Math.max(0, Math.floor(terminalWidth));
|
|
19
21
|
const preferred = Math.max(16, Math.min(48, Math.floor(preferredWidth)));
|
|
20
22
|
if (variant === "compact")
|