paseo-bm 0.2.0-alpha.0 → 0.2.0-alpha.1
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/package.json
CHANGED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "Beads" button on every open workspace's header (delta 20260918e §4.6,
|
|
3
|
+
* REQ-060 o; owner decision Q14 a).
|
|
4
|
+
*
|
|
5
|
+
* Paseo 0.8's mobile app has no "+" new-tab button, and its "…" Workspace
|
|
6
|
+
* actions sheet has a fixed list that plugins cannot extend, so the Beads tab
|
|
7
|
+
* (`bm-beads`) had no way in on a phone. The workspace header does draw plugin
|
|
8
|
+
* header buttons on mobile (the first one inline), and a button can open a
|
|
9
|
+
* panel. On desktop the same button is a shortcut next to "+".
|
|
10
|
+
*
|
|
11
|
+
* A header button belongs to ONE workspace, so one is added per open workspace
|
|
12
|
+
* and removed when the workspace goes, following the list the way
|
|
13
|
+
* `waiting-pills` follows its Workers: read now, every `BEADS_HEADER_POLL_MS`,
|
|
14
|
+
* and whenever Paseo reports a workspace update. A failed read keeps the
|
|
15
|
+
* buttons as they are.
|
|
16
|
+
*
|
|
17
|
+
* No JSX and no React Native: the button is data, and the test drives it with a
|
|
18
|
+
* fake client.
|
|
19
|
+
*/
|
|
20
|
+
import type { PluginButton, PluginButtonRegistration, PluginClientContext } from "@getpaseo/plugin/client";
|
|
21
|
+
import { BEADS_TAB_PANEL_ID } from "./dashboard-view";
|
|
22
|
+
|
|
23
|
+
/** One id for every workspace's button: Paseo keys a header button by id AND workspace. */
|
|
24
|
+
export const BEADS_HEADER_BUTTON_ID = "bm-beads-open";
|
|
25
|
+
|
|
26
|
+
/** How often the list of open workspaces is read again. */
|
|
27
|
+
export const BEADS_HEADER_POLL_MS = 15_000;
|
|
28
|
+
|
|
29
|
+
/** The button itself: an icon only, so it fits a phone's header. */
|
|
30
|
+
export function beadsHeaderButton(openTab: () => void): PluginButton {
|
|
31
|
+
return {
|
|
32
|
+
title: "Open the Beads tab: beads and metrics of this workspace",
|
|
33
|
+
icon: "ListChecks",
|
|
34
|
+
behavior: { kind: "action", onPress: openTab },
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Which buttons to add and remove: one per open workspace. A workspace being
|
|
40
|
+
* archived counts as gone, as it does in the Workspaces list.
|
|
41
|
+
*/
|
|
42
|
+
export function planHeaderButtons(
|
|
43
|
+
shown: ReadonlySet<string>,
|
|
44
|
+
listed: ReadonlyArray<{ id: string; archivingAt?: string | null }>,
|
|
45
|
+
): { add: string[]; remove: string[] } {
|
|
46
|
+
const open = new Set(listed.filter((workspace) => !workspace.archivingAt).map((workspace) => workspace.id));
|
|
47
|
+
return {
|
|
48
|
+
add: [...open].filter((id) => !shown.has(id)),
|
|
49
|
+
remove: [...shown].filter((id) => !open.has(id)),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Starts the buttons; returns the cleanup that stops following and removes them all. */
|
|
54
|
+
export function registerBeadsHeaderButtons(client: PluginClientContext): () => void {
|
|
55
|
+
const shown = new Map<string, PluginButtonRegistration>();
|
|
56
|
+
let pending = false;
|
|
57
|
+
let stopped = false;
|
|
58
|
+
let unsubscribe: (() => void) | undefined;
|
|
59
|
+
|
|
60
|
+
const refresh = async () => {
|
|
61
|
+
if (pending || stopped) return;
|
|
62
|
+
pending = true;
|
|
63
|
+
try {
|
|
64
|
+
const { entries } = await client.paseo.workspaces.list({});
|
|
65
|
+
if (stopped) return;
|
|
66
|
+
const plan = planHeaderButtons(new Set(shown.keys()), entries);
|
|
67
|
+
for (const workspaceId of plan.remove) {
|
|
68
|
+
shown.get(workspaceId)?.remove();
|
|
69
|
+
shown.delete(workspaceId);
|
|
70
|
+
}
|
|
71
|
+
for (const workspaceId of plan.add) {
|
|
72
|
+
const registration = client.addHeaderButton({
|
|
73
|
+
id: BEADS_HEADER_BUTTON_ID,
|
|
74
|
+
workspaceId,
|
|
75
|
+
button: beadsHeaderButton(() => client.openPanel(BEADS_TAB_PANEL_ID, { workspaceId })),
|
|
76
|
+
});
|
|
77
|
+
shown.set(workspaceId, registration);
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// A failed read keeps the buttons as they are: better a button one
|
|
81
|
+
// period late than buttons that flicker whenever the daemon is slow.
|
|
82
|
+
} finally {
|
|
83
|
+
pending = false;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
void refresh();
|
|
88
|
+
try {
|
|
89
|
+
// A newly opened workspace should not wait a whole period for its button.
|
|
90
|
+
unsubscribe = client.paseo.workspaces.subscribe(() => void refresh());
|
|
91
|
+
} catch {
|
|
92
|
+
// No live updates on this host: the timer still follows the list.
|
|
93
|
+
}
|
|
94
|
+
const timer = setInterval(() => void refresh(), BEADS_HEADER_POLL_MS);
|
|
95
|
+
// Node (tests) would otherwise stay alive for the timer; hosts without unref ignore this.
|
|
96
|
+
(timer as { unref?: () => void }).unref?.();
|
|
97
|
+
|
|
98
|
+
return () => {
|
|
99
|
+
stopped = true;
|
|
100
|
+
clearInterval(timer);
|
|
101
|
+
unsubscribe?.();
|
|
102
|
+
for (const registration of shown.values()) registration.remove();
|
|
103
|
+
shown.clear();
|
|
104
|
+
};
|
|
105
|
+
}
|
package/plugin/index.client.tsx
CHANGED
|
@@ -16,6 +16,7 @@ import { CHAT_CARD_KIND, CHAT_CARD_VERSION, chatCardSchema, toChatCard } from ".
|
|
|
16
16
|
import { ChatCardView } from "./client/chat-card";
|
|
17
17
|
import { ChatBeadsPanel } from "./client/bead-chips";
|
|
18
18
|
import { BeadsTabPanel } from "./client/beads-tab";
|
|
19
|
+
import { registerBeadsHeaderButtons } from "./client/beads-header-button";
|
|
19
20
|
import { registerWaitingPills } from "./client/waiting-pills";
|
|
20
21
|
|
|
21
22
|
/** A chat item as a paseo-bm card, or nothing (the item stays Paseo's). */
|
|
@@ -212,6 +213,9 @@ export default function contribute(client: PluginClientContext): () => void {
|
|
|
212
213
|
context: "workspace",
|
|
213
214
|
Component: BeadsTabPanel,
|
|
214
215
|
}),
|
|
216
|
+
// The same tab from a button on every workspace header: Paseo 0.8's mobile
|
|
217
|
+
// app has no "+" menu (delta 20260918e §4.6).
|
|
218
|
+
registerBeadsHeaderButtons(client),
|
|
215
219
|
client.addWorkspacePanel({
|
|
216
220
|
id: AGENT_TREE_PANEL_ID,
|
|
217
221
|
title: "Beads agents",
|
package/plugin/shared/version.ts
CHANGED