@tryinget/pi-activity-strip 0.7.0 → 0.9.0
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 +3 -1
- package/native/bin/linux-x64-gnu/artifact.json +3 -2
- package/native/bin/linux-x64-gnu/pi-activity-strip-panel +0 -0
- package/native/panel/src/app.rs +920 -41
- package/native/panel/src/card_view.rs +56 -24
- package/native/panel/src/directory_view.rs +409 -0
- package/native/panel/src/main.rs +1 -0
- package/native/panel/src/protocol.rs +71 -0
- package/native/panel/src/runtime.rs +43 -11
- package/native/panel/src/style.css +94 -5
- package/package.json +2 -3
- package/src/common/contracts.d.ts +1 -0
- package/src/common/contracts.ts +1 -0
- package/src/common/workspace-view.mjs +64 -0
- package/src/native/main.mjs +44 -1
- package/src/native/panel-projection.mjs +24 -2
|
@@ -45,20 +45,36 @@ pub fn apply_theme(definitions: &str) {
|
|
|
45
45
|
THEME_PROVIDER.with(|provider| provider.load_from_data(&css));
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/// Read controller messages from stdin on their own thread. The thread is detached on purpose: it
|
|
49
|
+
/// lives exactly as long as stdin, and on EOF it reports the parent gone, which closes the panel.
|
|
48
50
|
pub fn start_input_reader(sender: relm4::Sender<AppMsg>) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
51
|
+
let reader_sender = sender.clone();
|
|
52
|
+
let spawned = std::thread::Builder::new()
|
|
53
|
+
.name("panel-input".into())
|
|
54
|
+
.spawn(move || read_input(reader_sender));
|
|
55
|
+
match spawned {
|
|
56
|
+
Ok(handle) => drop(handle),
|
|
57
|
+
Err(error) => {
|
|
58
|
+
let _ = sender.send(AppMsg::InputError(format!(
|
|
59
|
+
"could not start the panel input reader: {error}"
|
|
60
|
+
)));
|
|
61
|
+
let _ = sender.send(AppMsg::ParentGone);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fn read_input(sender: relm4::Sender<AppMsg>) {
|
|
67
|
+
for line in BufReader::new(std::io::stdin()).lines() {
|
|
68
|
+
match line {
|
|
69
|
+
Ok(line) if !line.trim().is_empty() => consume_line(&sender, &line),
|
|
70
|
+
Ok(_) => {}
|
|
71
|
+
Err(error) => {
|
|
72
|
+
let _ = sender.send(AppMsg::InputError(format!("panel input failed: {error}")));
|
|
73
|
+
break;
|
|
58
74
|
}
|
|
59
75
|
}
|
|
60
|
-
|
|
61
|
-
|
|
76
|
+
}
|
|
77
|
+
let _ = sender.send(AppMsg::ParentGone);
|
|
62
78
|
}
|
|
63
79
|
|
|
64
80
|
fn consume_line(sender: &relm4::Sender<AppMsg>, line: &str) {
|
|
@@ -121,6 +137,22 @@ fn consume_line(sender: &relm4::Sender<AppMsg>, line: &str) {
|
|
|
121
137
|
.to_owned();
|
|
122
138
|
let _ = sender.send(AppMsg::ActivationResult(card_id, ok, message));
|
|
123
139
|
}
|
|
140
|
+
Some("jump-result") => {
|
|
141
|
+
let ok = value
|
|
142
|
+
.get("ok")
|
|
143
|
+
.and_then(|item| item.as_bool())
|
|
144
|
+
.unwrap_or(false);
|
|
145
|
+
let message = value
|
|
146
|
+
.get("message")
|
|
147
|
+
.and_then(|item| item.as_str())
|
|
148
|
+
.unwrap_or(if ok { "Jumped." } else { "Nothing moved." })
|
|
149
|
+
.to_owned();
|
|
150
|
+
let window_id = value
|
|
151
|
+
.get("windowId")
|
|
152
|
+
.and_then(|item| item.as_i64())
|
|
153
|
+
.unwrap_or(-1);
|
|
154
|
+
let _ = sender.send(AppMsg::JumpResult(window_id, ok, message));
|
|
155
|
+
}
|
|
124
156
|
Some(kind) => {
|
|
125
157
|
let _ = sender.send(AppMsg::InputError(format!(
|
|
126
158
|
"unsupported panel message: {kind}"
|
|
@@ -89,11 +89,14 @@ window {
|
|
|
89
89
|
border-color: alpha(@pi_accent, 0.5);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
/*
|
|
92
|
+
/*
|
|
93
|
+
* The tab the compositor is actually showing: the card the brand block names. A solid accent ring
|
|
94
|
+
* rather than a tint, so it never reads as the hover state and survives soft light themes.
|
|
95
|
+
*/
|
|
93
96
|
.card.current {
|
|
94
|
-
background: mix(@pi_background, @
|
|
95
|
-
border-color:
|
|
96
|
-
box-shadow: inset 0 0 0 1px
|
|
97
|
+
background: mix(@pi_background, @pi_accent, 0.14);
|
|
98
|
+
border-color: @pi_accent;
|
|
99
|
+
box-shadow: inset 0 0 0 1px @pi_accent;
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
.card.hidden-tab {
|
|
@@ -224,6 +227,7 @@ window {
|
|
|
224
227
|
* itself: it reuses the card's activation, so it carries the accent colour and a pointer cursor.
|
|
225
228
|
* Badge states (a claim that outlives its session, a deferred task) are deliberately inert pills.
|
|
226
229
|
*/
|
|
230
|
+
/* Footer-sized: a bare task number with no vertical padding, like the window chip beside it. */
|
|
227
231
|
.ak-task {
|
|
228
232
|
background: alpha(@pi_foreground, 0.07);
|
|
229
233
|
border: 1px solid alpha(@pi_foreground, 0.16);
|
|
@@ -231,7 +235,7 @@ window {
|
|
|
231
235
|
color: alpha(@pi_foreground, 0.68);
|
|
232
236
|
font-family: "IBM Plex Mono", "JetBrains Mono", monospace;
|
|
233
237
|
font-size: 8px;
|
|
234
|
-
padding:
|
|
238
|
+
padding: 0 5px;
|
|
235
239
|
}
|
|
236
240
|
|
|
237
241
|
.ak-task.ak-active {
|
|
@@ -261,3 +265,88 @@ window {
|
|
|
261
265
|
font-family: "IBM Plex Mono", "JetBrains Mono", monospace;
|
|
262
266
|
font-size: 8px;
|
|
263
267
|
}
|
|
268
|
+
|
|
269
|
+
/*
|
|
270
|
+
* The brand block's window list: every agent window on every workspace, as its card's first line
|
|
271
|
+
* and its Niri window number. The number is the thing you type, so it carries the accent.
|
|
272
|
+
*/
|
|
273
|
+
.directory {
|
|
274
|
+
border-top: 1px solid alpha(@pi_foreground, 0.12);
|
|
275
|
+
padding-top: 7px;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.jump-line {
|
|
279
|
+
color: @pi_accent;
|
|
280
|
+
font-family: "IBM Plex Mono", "JetBrains Mono", monospace;
|
|
281
|
+
font-size: 10px;
|
|
282
|
+
font-weight: 600;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* biome-ignore lint/correctness/noUnknownTypeSelector: GTK CSS uses widget node names. */
|
|
286
|
+
.directory-scroller,
|
|
287
|
+
.directory-scroller > viewport {
|
|
288
|
+
background: transparent;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* Same as the card row: scrolls by wheel or touchpad without a visible bar. */
|
|
292
|
+
/* biome-ignore lint/correctness/noUnknownTypeSelector: GTK CSS uses widget node names. */
|
|
293
|
+
.directory-scroller scrollbar {
|
|
294
|
+
opacity: 0;
|
|
295
|
+
min-width: 0;
|
|
296
|
+
min-height: 0;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.directory-header {
|
|
300
|
+
color: alpha(@pi_foreground, 0.5);
|
|
301
|
+
font-family: "IBM Plex Mono", "JetBrains Mono", monospace;
|
|
302
|
+
font-size: 8px;
|
|
303
|
+
letter-spacing: 1px;
|
|
304
|
+
margin-top: 5px;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.directory-row {
|
|
308
|
+
background: transparent;
|
|
309
|
+
border: 1px solid transparent;
|
|
310
|
+
border-radius: 6px;
|
|
311
|
+
color: @pi_foreground;
|
|
312
|
+
min-height: 0;
|
|
313
|
+
padding: 2px 6px;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.directory-row:hover,
|
|
317
|
+
.directory-row:focus-visible {
|
|
318
|
+
background: mix(@pi_background, @pi_foreground, 0.1);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/* The window you are in: the same accent ring as its card. */
|
|
322
|
+
.directory-row.current {
|
|
323
|
+
border-color: @pi_accent;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.directory-label {
|
|
327
|
+
font-size: 11px;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.directory-id {
|
|
331
|
+
color: @pi_accent;
|
|
332
|
+
font-family: "IBM Plex Mono", "JetBrains Mono", monospace;
|
|
333
|
+
font-size: 10px;
|
|
334
|
+
font-weight: 600;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.directory-empty {
|
|
338
|
+
color: alpha(@pi_foreground, 0.5);
|
|
339
|
+
font-size: 10px;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/* Which workspace a row's window is on; quieter than the number you type. */
|
|
343
|
+
.directory-workspace {
|
|
344
|
+
color: alpha(@pi_foreground, 0.5);
|
|
345
|
+
font-family: "IBM Plex Mono", "JetBrains Mono", monospace;
|
|
346
|
+
font-size: 9px;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/* The row Enter would open: a typed name always shows where it leads. */
|
|
350
|
+
.directory-row.chosen {
|
|
351
|
+
background: alpha(@pi_accent, 0.16);
|
|
352
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tryinget/pi-activity-strip",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "pi extension package for activity-strip workflows in monorepo runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -92,9 +92,8 @@
|
|
|
92
92
|
"@earendil-works/pi-ai": "0.84.4",
|
|
93
93
|
"@earendil-works/pi-coding-agent": "0.84.4",
|
|
94
94
|
"@types/node": "22.14.0",
|
|
95
|
-
"@typescript/native-preview": "7.0.0-dev.20260417.1",
|
|
96
95
|
"typebox": "1.3.7",
|
|
97
|
-
"typescript": "
|
|
96
|
+
"typescript": "7.0.2"
|
|
98
97
|
},
|
|
99
98
|
"overrides": {
|
|
100
99
|
"fast-xml-parser": "5.7.0"
|
|
@@ -99,6 +99,7 @@ export interface ActivityStripRuntimeStatus {
|
|
|
99
99
|
clickThrough?: boolean;
|
|
100
100
|
panelMoveCount?: number;
|
|
101
101
|
panelActivationCount?: number;
|
|
102
|
+
panelJumpCount?: number;
|
|
102
103
|
panelRestartCount?: number;
|
|
103
104
|
lastMovedCardId?: string | null;
|
|
104
105
|
warnings?: string[];
|
package/src/common/contracts.ts
CHANGED
|
@@ -123,6 +123,7 @@ export interface ActivityStripRuntimeStatus {
|
|
|
123
123
|
clickThrough?: boolean;
|
|
124
124
|
panelMoveCount?: number;
|
|
125
125
|
panelActivationCount?: number;
|
|
126
|
+
panelJumpCount?: number;
|
|
126
127
|
panelRestartCount?: number;
|
|
127
128
|
lastMovedCardId?: string | null;
|
|
128
129
|
warnings?: string[];
|
|
@@ -149,6 +149,70 @@ export function resolveFocusedWorkspaceView(windows, workspaces, sessions, optio
|
|
|
149
149
|
return workspace ? resolveWorkspaceView(windows, workspace, sessions, options) : null;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
/**
|
|
153
|
+
* @typedef {{
|
|
154
|
+
* cardId: string;
|
|
155
|
+
* label: string;
|
|
156
|
+
* windowId: number;
|
|
157
|
+
* workspaceId: number;
|
|
158
|
+
* workspaceIdx: number | null;
|
|
159
|
+
* workspaceName: string | null;
|
|
160
|
+
* hiddenTab: boolean;
|
|
161
|
+
* current: boolean;
|
|
162
|
+
* }} WindowDirectoryEntry
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Every agent window on every workspace, for the ribbon's window list. It reuses the per-workspace
|
|
167
|
+
* card projection, so an entry exists exactly when that workspace would show the card. Ordered
|
|
168
|
+
* alphabetically by project name, then by window number; each entry carries its workspace.
|
|
169
|
+
* @param {Array<Record<string, unknown>>} windows
|
|
170
|
+
* @param {Array<Record<string, unknown>>} workspaces
|
|
171
|
+
* @param {Array<Record<string, unknown>>} sessions
|
|
172
|
+
* @param {ResolveOptions} [options]
|
|
173
|
+
* @returns {{entries: WindowDirectoryEntry[]; cards: Map<string, Record<string, unknown>>}}
|
|
174
|
+
*/
|
|
175
|
+
export function resolveWindowDirectory(windows, workspaces, sessions, options = {}) {
|
|
176
|
+
const ordered = workspaces
|
|
177
|
+
.filter((workspace) => Number.isInteger(workspace?.id))
|
|
178
|
+
.sort(
|
|
179
|
+
(a, b) =>
|
|
180
|
+
String(a.output ?? "").localeCompare(String(b.output ?? "")) ||
|
|
181
|
+
Number(a.idx ?? 0) - Number(b.idx ?? 0),
|
|
182
|
+
);
|
|
183
|
+
/** @type {WindowDirectoryEntry[]} */
|
|
184
|
+
const entries = [];
|
|
185
|
+
/** @type {Map<string, Record<string, unknown>>} */
|
|
186
|
+
const cards = new Map();
|
|
187
|
+
for (const workspace of ordered) {
|
|
188
|
+
const view = resolveWorkspaceView(windows, workspace, sessions, options);
|
|
189
|
+
if (!view) continue;
|
|
190
|
+
const placed = view.sessions
|
|
191
|
+
.filter((card) => Number.isInteger(card.windowId))
|
|
192
|
+
.sort((a, b) => Number(a.windowId) - Number(b.windowId));
|
|
193
|
+
for (const card of placed) {
|
|
194
|
+
const cardId = String(card.cardId ?? "");
|
|
195
|
+
if (!cardId || cards.has(cardId)) continue;
|
|
196
|
+
cards.set(cardId, card);
|
|
197
|
+
entries.push({
|
|
198
|
+
cardId,
|
|
199
|
+
label: String(card.repoLabel || "session"),
|
|
200
|
+
windowId: Number(card.windowId),
|
|
201
|
+
workspaceId: Number(workspace.id),
|
|
202
|
+
workspaceIdx: Number.isInteger(card.workspaceIdx) ? Number(card.workspaceIdx) : null,
|
|
203
|
+
workspaceName: typeof workspace.name === "string" ? workspace.name : null,
|
|
204
|
+
hiddenTab: card.surfaceVisible === false,
|
|
205
|
+
current: cardId === view.focusedCardId,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
entries.sort(
|
|
210
|
+
(a, b) =>
|
|
211
|
+
a.label.localeCompare(b.label, undefined, { sensitivity: "base" }) || a.windowId - b.windowId,
|
|
212
|
+
);
|
|
213
|
+
return { entries, cards };
|
|
214
|
+
}
|
|
215
|
+
|
|
152
216
|
/** @param {Array<Record<string, unknown>>} workspaces */
|
|
153
217
|
export function resolveFocusedNiriWorkspace(workspaces) {
|
|
154
218
|
const matches = workspaces.filter(
|
package/src/native/main.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import { pruneClaudeEventRecords } from "../common/claude-events.mjs";
|
|
|
13
13
|
import { ACTIVITY_STRIP_WORKSPACE_SYNC_MS } from "../common/constants.mjs";
|
|
14
14
|
import { focusNiriSession, readNiriWindows, readNiriWorkspaces } from "../common/niri-focus.mjs";
|
|
15
15
|
import { haveSameRecordMembership } from "../common/session-cards.mjs";
|
|
16
|
-
import { resolveFocusedWorkspaceView } from "../common/workspace-view.mjs";
|
|
16
|
+
import { resolveFocusedWorkspaceView, resolveWindowDirectory } from "../common/workspace-view.mjs";
|
|
17
17
|
import { discoverAgentTabs } from "./agent-discovery.mjs";
|
|
18
18
|
import { startAkTaskProjection } from "./ak-runtime.mjs";
|
|
19
19
|
import { createHeightRepair } from "./height-repair.mjs";
|
|
@@ -158,6 +158,37 @@ async function focusSession(targetId) {
|
|
|
158
158
|
return result;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Jump to a window by the number Niri gives it, as typed into the window list. Only a window that
|
|
163
|
+
* exists right now is focused; any other number does nothing.
|
|
164
|
+
* @param {number} windowId
|
|
165
|
+
* @returns {Promise<{ok: boolean; error?: string}>}
|
|
166
|
+
*/
|
|
167
|
+
async function jumpToWindow(windowId) {
|
|
168
|
+
if (!isNiriSession()) return { ok: false, error: "Niri is not available; nothing moved." };
|
|
169
|
+
if (!Number.isInteger(windowId) || windowId < 0) {
|
|
170
|
+
return { ok: false, error: "Not a window number; nothing moved." };
|
|
171
|
+
}
|
|
172
|
+
let windows;
|
|
173
|
+
try {
|
|
174
|
+
windows = await getNiriWindows();
|
|
175
|
+
} catch {
|
|
176
|
+
return { ok: false, error: "Could not read Niri windows; nothing moved." };
|
|
177
|
+
}
|
|
178
|
+
if (!windows.some((window) => window?.id === windowId)) {
|
|
179
|
+
return { ok: false, error: `No window #${windowId}; nothing moved.` };
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
await execFileAsync("niri", ["msg", "action", "focus-window", "--id", String(windowId)], {
|
|
183
|
+
env: process.env,
|
|
184
|
+
});
|
|
185
|
+
} catch {
|
|
186
|
+
return { ok: false, error: `Niri refused to focus #${windowId}; nothing moved.` };
|
|
187
|
+
}
|
|
188
|
+
reconcileRunner.request();
|
|
189
|
+
return { ok: true };
|
|
190
|
+
}
|
|
191
|
+
|
|
161
192
|
async function focusStrip() {
|
|
162
193
|
if (runtimeStatus.clickThrough) {
|
|
163
194
|
return { ok: false, error: "Keyboard entry is disabled in click-through mode." };
|
|
@@ -231,6 +262,17 @@ function consumePanelEvents(child, chunk) {
|
|
|
231
262
|
: result.error || "Focus failed; nothing moved.",
|
|
232
263
|
}),
|
|
233
264
|
);
|
|
265
|
+
} else if (event.type === "jump") {
|
|
266
|
+
runtimeStatus.panelJumpCount = (runtimeStatus.panelJumpCount ?? 0) + 1;
|
|
267
|
+
const windowId = Number(event.windowId);
|
|
268
|
+
void jumpToWindow(windowId).then((result) =>
|
|
269
|
+
writePanel({
|
|
270
|
+
type: "jump-result",
|
|
271
|
+
windowId,
|
|
272
|
+
ok: result.ok === true,
|
|
273
|
+
message: result.ok ? `Jumped to #${windowId}.` : result.error || "Nothing moved.",
|
|
274
|
+
}),
|
|
275
|
+
);
|
|
234
276
|
} else if (event.type === "moved") {
|
|
235
277
|
runtimeStatus.panelMoveCount = (runtimeStatus.panelMoveCount ?? 0) + 1;
|
|
236
278
|
runtimeStatus.lastMovedCardId = String(event.cardId ?? "") || null;
|
|
@@ -362,6 +404,7 @@ const reconcileRunner = createLatestOnlyRunner(async ({ isCurrent }) => {
|
|
|
362
404
|
runtimeStatus.unplacedSurfaceCount = placement.countUnplaced(windows, sessions);
|
|
363
405
|
const view = resolveFocusedWorkspaceView(windows, workspaces, sessions, placement.options);
|
|
364
406
|
if (view) placement.learnFromPlacements(view.sessions, windows);
|
|
407
|
+
projection.setDirectory(resolveWindowDirectory(windows, workspaces, sessions, placement.options));
|
|
365
408
|
projection.publishWorkspaceView(
|
|
366
409
|
view ?? { workspace: null, sessions: [], focusedSessionId: null, focusedCardId: null },
|
|
367
410
|
);
|
|
@@ -36,6 +36,11 @@ export function createNativePanelProjection({ isNiriSession, publish }) {
|
|
|
36
36
|
/** Read-only AK task claims joined onto cards; absent until the first successful AK read. */
|
|
37
37
|
/** @type {{claims: import("../common/contracts.ts").AkTaskClaim[]; deferred: import("../common/contracts.ts").AkTaskDeferred[]} | null} */
|
|
38
38
|
let akTasks = null;
|
|
39
|
+
/** Every agent window on every workspace, for the brand block's window list. */
|
|
40
|
+
/** @type {import("../common/workspace-view.mjs").WindowDirectoryEntry[]} */
|
|
41
|
+
let directory = [];
|
|
42
|
+
/** @type {Map<string, SessionRecord>} */
|
|
43
|
+
let directoryCards = new Map();
|
|
39
44
|
let revision = 0;
|
|
40
45
|
|
|
41
46
|
function allSessions() {
|
|
@@ -83,6 +88,7 @@ export function createNativePanelProjection({ isNiriSession, publish }) {
|
|
|
83
88
|
focusedCardId,
|
|
84
89
|
generatedAt: snapshot.generatedAt,
|
|
85
90
|
sessions,
|
|
91
|
+
windows: directory,
|
|
86
92
|
};
|
|
87
93
|
}
|
|
88
94
|
|
|
@@ -117,8 +123,24 @@ export function createNativePanelProjection({ isNiriSession, publish }) {
|
|
|
117
123
|
akTasks = state;
|
|
118
124
|
send();
|
|
119
125
|
},
|
|
120
|
-
/**
|
|
121
|
-
|
|
126
|
+
/**
|
|
127
|
+
* A card on this workspace, or else an entry of the window list, which spans every workspace.
|
|
128
|
+
* @param {string} targetId
|
|
129
|
+
*/
|
|
130
|
+
resolveTarget: (targetId) =>
|
|
131
|
+
resolveSnapshotSession(getDisplaySessions(), targetId) ??
|
|
132
|
+
directoryCards.get(targetId) ??
|
|
133
|
+
null,
|
|
134
|
+
/** @param {number} windowId */
|
|
135
|
+
hasDirectoryWindow: (windowId) => directory.some((entry) => entry.windowId === windowId),
|
|
136
|
+
/**
|
|
137
|
+
* Replace the window list; it is published with the next view.
|
|
138
|
+
* @param {{entries: import("../common/workspace-view.mjs").WindowDirectoryEntry[]; cards: Map<string, SessionRecord>}} next
|
|
139
|
+
*/
|
|
140
|
+
setDirectory(next) {
|
|
141
|
+
directory = next.entries;
|
|
142
|
+
directoryCards = next.cards;
|
|
143
|
+
},
|
|
122
144
|
/** @param {WorkspaceView} view */
|
|
123
145
|
publishWorkspaceView(view) {
|
|
124
146
|
workspace = view.workspace ?? null;
|