@burdenoff/microfe-vibecontrols 2026.709.2 → 2026.710.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/dist/components/agent-manager/AgentManagerBookmarksDrawer.js +87 -85
- package/dist/components/agent-manager/AgentManagerBookmarksDrawer.js.map +1 -1
- package/dist/components/ai/InstructionFilesManager.js +138 -136
- package/dist/components/ai/InstructionFilesManager.js.map +1 -1
- package/dist/components/ai/PluginHarnessPicker.js +70 -64
- package/dist/components/ai/PluginHarnessPicker.js.map +1 -1
- package/dist/components/docs/DocsEditPanel.js +1 -1
- package/dist/components/docs/DocsEditPanel.js.map +1 -1
- package/dist/components/docs/DocsPageTree.js +1 -1
- package/dist/components/docs/DocsPageTree.js.map +1 -1
- package/dist/components/docs/DocsTabPanel.js +108 -106
- package/dist/components/docs/DocsTabPanel.js.map +1 -1
- package/dist/components/notes/NotesTabPanel.js +99 -99
- package/dist/components/notes/NotesTabPanel.js.map +1 -1
- package/dist/components/scorecard/ScorecardPanel.js +309 -308
- package/dist/components/scorecard/ScorecardPanel.js.map +1 -1
- package/dist/components/sessions/SessionPublicLinkPanel.js +104 -102
- package/dist/components/sessions/SessionPublicLinkPanel.js.map +1 -1
- package/dist/components/settings/AdvancedSection.js +92 -90
- package/dist/components/settings/AdvancedSection.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js +104 -102
- package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js.map +1 -1
- package/dist/constants/permissions.js +7 -0
- package/dist/constants/permissions.js.map +1 -1
- package/dist/pages/ActionsPage.js +303 -303
- package/dist/pages/ActionsPage.js.map +1 -1
- package/dist/pages/AgentDetailsPage.js +520 -516
- package/dist/pages/AgentDetailsPage.js.map +1 -1
- package/dist/pages/AgentsPage.js +287 -283
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +554 -549
- package/dist/pages/TargetsPage.js.map +1 -1
- package/dist/pages/VibeAuditPage.js +2 -2
- package/dist/pages/VibeAuditPage.js.map +1 -1
- package/dist/pages/VibeDeckPage.js +257 -257
- package/dist/pages/VibeDeckPage.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +688 -693
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/VibesPage.js +49 -41
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/pages/WebhooksPage.js +162 -138
- package/dist/pages/WebhooksPage.js.map +1 -1
- package/dist/pages/tunnels/DomainsTab.js +54 -47
- package/dist/pages/tunnels/DomainsTab.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,117 +2,119 @@ import { useRemoveAgentPluginMutation as e } from "../../generated/wspace-operat
|
|
|
2
2
|
import { Card as t } from "../shared/Card.js";
|
|
3
3
|
import { usePluginAvailability as n } from "../../hooks/usePluginAvailability.js";
|
|
4
4
|
import { getPluginMeta as r } from "../../constants/pluginCatalog.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { VIBE_PERMISSIONS as i } from "../../constants/permissions.js";
|
|
6
|
+
import { useCallback as a, useEffect as o, useRef as s, useState as c } from "react";
|
|
7
|
+
import { Check as l, CheckCircle2 as u, Download as d, Loader2 as f, Puzzle as p, Trash2 as m } from "lucide-react";
|
|
8
|
+
import { usePermissions as h } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
9
|
+
import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
8
10
|
//#region src/components/ai/PluginHarnessPicker.tsx
|
|
9
|
-
function
|
|
10
|
-
let { installed:
|
|
11
|
-
|
|
12
|
-
let e =
|
|
11
|
+
function y({ packages: y, agentId: b, title: x, description: S, onChange: C, renderInstalledAction: w }) {
|
|
12
|
+
let { installed: T, installing: E, install: D, refetch: O, loading: k, error: A } = n(y, b), { hasPermission: j } = h(), M = j(i.AGENT_UPDATE), [N] = e(), [P, F] = c(null), [I, L] = c({}), [R, z] = c({}), B = s(/* @__PURE__ */ new Map());
|
|
13
|
+
o(() => {
|
|
14
|
+
let e = B.current;
|
|
13
15
|
return () => {
|
|
14
16
|
for (let t of e.values()) clearTimeout(t);
|
|
15
17
|
e.clear();
|
|
16
18
|
};
|
|
17
19
|
}, []);
|
|
18
|
-
let
|
|
19
|
-
if (await
|
|
20
|
-
|
|
20
|
+
let V = a(async (e) => {
|
|
21
|
+
if (await D(e)) {
|
|
22
|
+
z((t) => ({
|
|
21
23
|
...t,
|
|
22
24
|
[e]: !0
|
|
23
25
|
}));
|
|
24
|
-
let t =
|
|
26
|
+
let t = B.current.get(e);
|
|
25
27
|
t && clearTimeout(t);
|
|
26
28
|
let n = setTimeout(() => {
|
|
27
|
-
|
|
29
|
+
z((t) => {
|
|
28
30
|
if (!t[e]) return t;
|
|
29
31
|
let n = { ...t };
|
|
30
32
|
return delete n[e], n;
|
|
31
|
-
}),
|
|
33
|
+
}), B.current.delete(e);
|
|
32
34
|
}, 1500);
|
|
33
|
-
|
|
35
|
+
B.current.set(e, n), C?.();
|
|
34
36
|
}
|
|
35
|
-
}, [
|
|
36
|
-
if (
|
|
37
|
-
|
|
37
|
+
}, [D, C]), H = a(async (e) => {
|
|
38
|
+
if (b) {
|
|
39
|
+
F(e), L((t) => ({
|
|
38
40
|
...t,
|
|
39
41
|
[e]: ""
|
|
40
42
|
}));
|
|
41
43
|
try {
|
|
42
|
-
let { data: t } = await
|
|
43
|
-
agentId:
|
|
44
|
+
let { data: t } = await N({ variables: {
|
|
45
|
+
agentId: b,
|
|
44
46
|
packageName: e
|
|
45
47
|
} });
|
|
46
48
|
if (!t?.removeAgentPlugin.success) {
|
|
47
|
-
|
|
49
|
+
L((n) => ({
|
|
48
50
|
...n,
|
|
49
51
|
[e]: t?.removeAgentPlugin.error || "Remove failed"
|
|
50
52
|
}));
|
|
51
53
|
return;
|
|
52
54
|
}
|
|
53
|
-
await
|
|
55
|
+
await O(), C?.();
|
|
54
56
|
} catch (t) {
|
|
55
|
-
|
|
57
|
+
L((n) => ({
|
|
56
58
|
...n,
|
|
57
59
|
[e]: t instanceof Error ? t.message : String(t)
|
|
58
60
|
}));
|
|
59
61
|
} finally {
|
|
60
|
-
|
|
62
|
+
F(null);
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
65
|
}, [
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
b,
|
|
67
|
+
N,
|
|
68
|
+
O,
|
|
69
|
+
C
|
|
68
70
|
]);
|
|
69
|
-
return
|
|
71
|
+
return y.length === 0 ? null : /* @__PURE__ */ v("section", {
|
|
70
72
|
className: "flex flex-col gap-3",
|
|
71
73
|
"data-testid": "plugin-harness-picker",
|
|
72
74
|
children: [
|
|
73
|
-
(
|
|
75
|
+
(x || S) && /* @__PURE__ */ v("div", { children: [x && /* @__PURE__ */ _("h3", {
|
|
74
76
|
className: "text-base font-semibold text-text-primary",
|
|
75
|
-
children:
|
|
76
|
-
}),
|
|
77
|
+
children: x
|
|
78
|
+
}), S && /* @__PURE__ */ _("p", {
|
|
77
79
|
className: "text-sm text-text-secondary mt-1",
|
|
78
|
-
children:
|
|
80
|
+
children: S
|
|
79
81
|
})] }),
|
|
80
|
-
|
|
82
|
+
A && /* @__PURE__ */ _("div", {
|
|
81
83
|
role: "alert",
|
|
82
84
|
className: "rounded-md border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text",
|
|
83
|
-
children:
|
|
85
|
+
children: A
|
|
84
86
|
}),
|
|
85
|
-
/* @__PURE__ */
|
|
87
|
+
/* @__PURE__ */ _("div", {
|
|
86
88
|
className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3",
|
|
87
|
-
children:
|
|
88
|
-
let n = r(e), i = !!
|
|
89
|
-
return /* @__PURE__ */
|
|
89
|
+
children: y.map((e) => {
|
|
90
|
+
let n = r(e), i = !!T[e], a = E === e, o = P === e, s = a || o || k, c = !!R[e], h = I[e];
|
|
91
|
+
return /* @__PURE__ */ v(t, {
|
|
90
92
|
padding: "md",
|
|
91
93
|
className: "flex flex-col gap-3",
|
|
92
94
|
children: [
|
|
93
|
-
/* @__PURE__ */
|
|
95
|
+
/* @__PURE__ */ v("div", {
|
|
94
96
|
className: "flex items-start gap-3",
|
|
95
|
-
children: [/* @__PURE__ */
|
|
97
|
+
children: [/* @__PURE__ */ _("div", {
|
|
96
98
|
className: "flex-shrink-0 size-9 rounded-md bg-bg-sunken flex items-center justify-center",
|
|
97
|
-
children: /* @__PURE__ */
|
|
98
|
-
}), /* @__PURE__ */
|
|
99
|
+
children: /* @__PURE__ */ _(p, { className: "size-4 text-text-secondary" })
|
|
100
|
+
}), /* @__PURE__ */ v("div", {
|
|
99
101
|
className: "min-w-0 flex-1",
|
|
100
102
|
children: [
|
|
101
|
-
/* @__PURE__ */
|
|
103
|
+
/* @__PURE__ */ v("div", {
|
|
102
104
|
className: "flex items-start gap-2 flex-wrap",
|
|
103
|
-
children: [/* @__PURE__ */
|
|
105
|
+
children: [/* @__PURE__ */ _("h4", {
|
|
104
106
|
className: "font-medium text-text-primary break-words max-w-[100ch]",
|
|
105
107
|
children: n.displayName
|
|
106
|
-
}), i && /* @__PURE__ */
|
|
108
|
+
}), i && /* @__PURE__ */ v("span", {
|
|
107
109
|
className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs rounded bg-status-success-bg-subtle text-status-success-text shrink-0",
|
|
108
|
-
children: [/* @__PURE__ */
|
|
110
|
+
children: [/* @__PURE__ */ _(l, { className: "size-3" }), " installed"]
|
|
109
111
|
})]
|
|
110
112
|
}),
|
|
111
|
-
/* @__PURE__ */
|
|
113
|
+
/* @__PURE__ */ _("p", {
|
|
112
114
|
className: "text-sm text-text-secondary mt-0.5",
|
|
113
115
|
children: n.description
|
|
114
116
|
}),
|
|
115
|
-
/* @__PURE__ */
|
|
117
|
+
/* @__PURE__ */ _("p", {
|
|
116
118
|
className: "text-[10px] font-mono text-text-placeholder mt-1 break-all",
|
|
117
119
|
title: e,
|
|
118
120
|
children: e
|
|
@@ -120,45 +122,49 @@ function _({ packages: _, agentId: v, title: y, description: b, onChange: x, ren
|
|
|
120
122
|
]
|
|
121
123
|
})]
|
|
122
124
|
}),
|
|
123
|
-
/* @__PURE__ */
|
|
125
|
+
/* @__PURE__ */ v("div", {
|
|
124
126
|
className: "flex items-center gap-2",
|
|
125
127
|
children: [
|
|
126
|
-
i &&
|
|
128
|
+
i && c ? /* @__PURE__ */ v("button", {
|
|
127
129
|
type: "button",
|
|
128
130
|
disabled: !0,
|
|
129
131
|
className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-status-success-bg-subtle text-status-success-text border border-status-success-border cursor-default",
|
|
130
132
|
"data-testid": `plugin-installed-${e}`,
|
|
131
133
|
"aria-live": "polite",
|
|
132
|
-
children: [/* @__PURE__ */
|
|
133
|
-
}) : i ? /* @__PURE__ */
|
|
134
|
+
children: [/* @__PURE__ */ _(u, { className: "size-3.5" }), "Installed"]
|
|
135
|
+
}) : i && M ? /* @__PURE__ */ v("button", {
|
|
134
136
|
type: "button",
|
|
135
|
-
disabled: s || !
|
|
136
|
-
onClick: () =>
|
|
137
|
+
disabled: s || !b,
|
|
138
|
+
onClick: () => H(e),
|
|
137
139
|
className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md border border-border-default text-text-secondary hover:border-status-error-border hover:text-status-error-text transition disabled:opacity-60 disabled:cursor-not-allowed",
|
|
138
140
|
"data-testid": `plugin-remove-${e}`,
|
|
139
|
-
children: [o ? /* @__PURE__ */
|
|
141
|
+
children: [o ? /* @__PURE__ */ _(f, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ _(m, { className: "size-3.5" }), "Remove"]
|
|
142
|
+
}) : i ? /* @__PURE__ */ v("span", {
|
|
143
|
+
className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-status-success-bg-subtle text-status-success-text border border-status-success-border",
|
|
144
|
+
"data-testid": `plugin-installed-static-${e}`,
|
|
145
|
+
children: [/* @__PURE__ */ _(u, { className: "size-3.5" }), "Installed"]
|
|
140
146
|
}) : null,
|
|
141
|
-
i && !
|
|
142
|
-
!i && /* @__PURE__ */
|
|
147
|
+
i && !c && w?.(e),
|
|
148
|
+
!i && /* @__PURE__ */ _("button", {
|
|
143
149
|
type: "button",
|
|
144
|
-
disabled: s || !
|
|
145
|
-
onClick: () =>
|
|
150
|
+
disabled: s || !b,
|
|
151
|
+
onClick: () => V(e),
|
|
146
152
|
className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition disabled:opacity-60 disabled:cursor-not-allowed",
|
|
147
153
|
"data-testid": `plugin-install-${e}`,
|
|
148
154
|
"aria-busy": a,
|
|
149
|
-
children: a ? /* @__PURE__ */ g
|
|
155
|
+
children: a ? /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _(f, { className: "size-3.5 animate-spin" }), "Installing…"] }) : /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _(d, { className: "size-3.5" }), "Install"] })
|
|
150
156
|
})
|
|
151
157
|
]
|
|
152
158
|
}),
|
|
153
|
-
|
|
159
|
+
h && /* @__PURE__ */ _("p", {
|
|
154
160
|
className: "text-xs text-status-error-text",
|
|
155
|
-
children:
|
|
161
|
+
children: h
|
|
156
162
|
})
|
|
157
163
|
]
|
|
158
164
|
}, e);
|
|
159
165
|
})
|
|
160
166
|
}),
|
|
161
|
-
!
|
|
167
|
+
!b && /* @__PURE__ */ _("p", {
|
|
162
168
|
className: "text-xs text-text-muted",
|
|
163
169
|
children: "Select an agent to manage plugins."
|
|
164
170
|
})
|
|
@@ -166,6 +172,6 @@ function _({ packages: _, agentId: v, title: y, description: b, onChange: x, ren
|
|
|
166
172
|
});
|
|
167
173
|
}
|
|
168
174
|
//#endregion
|
|
169
|
-
export {
|
|
175
|
+
export { y as PluginHarnessPicker };
|
|
170
176
|
|
|
171
177
|
//# sourceMappingURL=PluginHarnessPicker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginHarnessPicker.js","names":[],"sources":["../../../src/components/ai/PluginHarnessPicker.tsx"],"sourcesContent":["/**\n * <PluginHarnessPicker />\n *\n * Grid of installable VibeControls agent plugins with per-card\n * Install / Installed / Remove state. Driven by the existing\n * `usePluginAvailability` hook (same code path as `<PluginGate>`), so\n * the Plugins tab on the Agent details page and this picker stay in\n * sync automatically.\n *\n * The default use-case is the AI workbench: once the base\n * `@vibecontrols/vibe-plugin-ai` plugin is installed, this picker lists\n * every AI provider harness (claude, codex, copilot, gemini, …) so the\n * user can add / remove harnesses without leaving the AI page.\n *\n * The component is feature-neutral — pass any `packages` array (e.g.\n * session providers, tunnel providers) to reuse the same UI.\n */\n\nimport { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';\nimport { Check, CheckCircle2, Download, Loader2, Puzzle, Trash2 } from 'lucide-react';\nimport { useRemoveAgentPluginMutation } from '@/generated/wspace-operations';\nimport { usePluginAvailability } from '@/hooks/usePluginAvailability';\nimport { getPluginMeta } from '@/constants/pluginCatalog';\nimport { Card } from '../shared/Card';\n\nexport interface PluginHarnessPickerProps {\n /** Package names to offer. Metadata is resolved via `getPluginMeta`. */\n packages: readonly string[];\n /** Active agent id. When null, the picker renders in a disabled state. */\n agentId: string | null;\n /** Optional section heading (rendered above the grid). */\n title?: string;\n /** Optional section description. */\n description?: string;\n /** Fired after a successful install or remove so parents can refetch. */\n onChange?: () => void;\n /**\n * Optional per-card action slot, rendered alongside the Remove button when\n * a package is installed. Lets a feature (e.g. the Security tab) attach a\n * contextual action — like \"Run scan\" — without coupling this generic\n * picker to that feature. Receives the installed package name.\n */\n renderInstalledAction?: (pkg: string) => ReactNode;\n}\n\nexport function PluginHarnessPicker({\n packages,\n agentId,\n title,\n description,\n onChange,\n renderInstalledAction,\n}: PluginHarnessPickerProps) {\n // Plugins are agent-wide (BOFF-2612); no profile threading needed.\n const { installed, installing, install, refetch, loading, error } = usePluginAvailability(\n packages,\n agentId\n );\n const [removePlugin] = useRemoveAgentPluginMutation();\n const [removingPkg, setRemovingPkg] = useState<string | null>(null);\n const [removeError, setRemoveError] = useState<Record<string, string>>({});\n // Per-package \"just installed\" pulse — flips on for ~1.5s after a\n // successful install so the button shows a clear success affordance\n // (checkmark + \"Installed\") before the card transitions to its\n // installed/Remove state. Without this the button silently flips from\n // spinner to Remove and users miss the success signal.\n const [justInstalled, setJustInstalled] = useState<Record<string, boolean>>({});\n const justInstalledTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());\n\n useEffect(() => {\n const timers = justInstalledTimers.current;\n return () => {\n for (const t of timers.values()) clearTimeout(t);\n timers.clear();\n };\n }, []);\n\n const handleInstall = useCallback(\n async (pkg: string) => {\n const ok = await install(pkg);\n if (ok) {\n setJustInstalled((prev) => ({ ...prev, [pkg]: true }));\n const existing = justInstalledTimers.current.get(pkg);\n if (existing) clearTimeout(existing);\n const timer = setTimeout(() => {\n setJustInstalled((prev) => {\n if (!prev[pkg]) return prev;\n const next = { ...prev };\n delete next[pkg];\n return next;\n });\n justInstalledTimers.current.delete(pkg);\n }, 1500);\n justInstalledTimers.current.set(pkg, timer);\n onChange?.();\n }\n },\n [install, onChange]\n );\n\n const handleRemove = useCallback(\n async (pkg: string) => {\n if (!agentId) return;\n setRemovingPkg(pkg);\n setRemoveError((e) => ({ ...e, [pkg]: '' }));\n try {\n const { data } = await removePlugin({\n variables: { agentId, packageName: pkg },\n });\n if (!data?.removeAgentPlugin.success) {\n setRemoveError((e) => ({\n ...e,\n [pkg]: data?.removeAgentPlugin.error || 'Remove failed',\n }));\n return;\n }\n await refetch();\n onChange?.();\n } catch (e) {\n setRemoveError((err) => ({\n ...err,\n [pkg]: e instanceof Error ? e.message : String(e),\n }));\n } finally {\n setRemovingPkg(null);\n }\n },\n [agentId, removePlugin, refetch, onChange]\n );\n\n if (packages.length === 0) return null;\n\n return (\n <section className=\"flex flex-col gap-3\" data-testid=\"plugin-harness-picker\">\n {(title || description) && (\n <div>\n {title && <h3 className=\"text-base font-semibold text-text-primary\">{title}</h3>}\n {description && <p className=\"text-sm text-text-secondary mt-1\">{description}</p>}\n </div>\n )}\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text\"\n >\n {error}\n </div>\n )}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {packages.map((pkg) => {\n const meta = getPluginMeta(pkg);\n const isInstalled = !!installed[pkg];\n const isInstalling = installing === pkg;\n const isRemoving = removingPkg === pkg;\n const isBusy = isInstalling || isRemoving || loading;\n const showJustInstalled = !!justInstalled[pkg];\n const err = removeError[pkg];\n return (\n <Card key={pkg} padding=\"md\" className=\"flex flex-col gap-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-shrink-0 size-9 rounded-md bg-bg-sunken flex items-center justify-center\">\n <Puzzle className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-start gap-2 flex-wrap\">\n {/*\n Plugin display name must wrap freely up to a\n reasonable hard cap so the column never blows up\n the modal layout. break-words allows wrapping on\n arbitrary characters when a name has no spaces;\n max-w bound corresponds to ~100 char-cells (a\n generous upper bound on real-world display names).\n */}\n <h4 className=\"font-medium text-text-primary break-words max-w-[100ch]\">\n {meta.displayName}\n </h4>\n {isInstalled && (\n <span className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs rounded bg-status-success-bg-subtle text-status-success-text shrink-0\">\n <Check className=\"size-3\" /> installed\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary mt-0.5\">{meta.description}</p>\n <p\n className=\"text-[10px] font-mono text-text-placeholder mt-1 break-all\"\n title={pkg}\n >\n {pkg}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {isInstalled && showJustInstalled ? (\n <button\n type=\"button\"\n disabled\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-status-success-bg-subtle text-status-success-text border border-status-success-border cursor-default\"\n data-testid={`plugin-installed-${pkg}`}\n aria-live=\"polite\"\n >\n <CheckCircle2 className=\"size-3.5\" />\n Installed\n </button>\n ) : isInstalled ? (\n <button\n type=\"button\"\n disabled={isBusy || !agentId}\n onClick={() => handleRemove(pkg)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md border border-border-default text-text-secondary hover:border-status-error-border hover:text-status-error-text transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`plugin-remove-${pkg}`}\n >\n {isRemoving ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Trash2 className=\"size-3.5\" />\n )}\n Remove\n </button>\n ) : null}\n {isInstalled && !showJustInstalled && renderInstalledAction?.(pkg)}\n {!isInstalled && (\n <button\n type=\"button\"\n disabled={isBusy || !agentId}\n onClick={() => handleInstall(pkg)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`plugin-install-${pkg}`}\n aria-busy={isInstalling}\n >\n {isInstalling ? (\n <>\n <Loader2 className=\"size-3.5 animate-spin\" />\n Installing…\n </>\n ) : (\n <>\n <Download className=\"size-3.5\" />\n Install\n </>\n )}\n </button>\n )}\n </div>\n {err && <p className=\"text-xs text-status-error-text\">{err}</p>}\n </Card>\n );\n })}\n </div>\n {!agentId && <p className=\"text-xs text-text-muted\">Select an agent to manage plugins.</p>}\n </section>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PluginHarnessPicker.js","names":[],"sources":["../../../src/components/ai/PluginHarnessPicker.tsx"],"sourcesContent":["/**\n * <PluginHarnessPicker />\n *\n * Grid of installable VibeControls agent plugins with per-card\n * Install / Installed / Remove state. Driven by the existing\n * `usePluginAvailability` hook (same code path as `<PluginGate>`), so\n * the Plugins tab on the Agent details page and this picker stay in\n * sync automatically.\n *\n * The default use-case is the AI workbench: once the base\n * `@vibecontrols/vibe-plugin-ai` plugin is installed, this picker lists\n * every AI provider harness (claude, codex, copilot, gemini, …) so the\n * user can add / remove harnesses without leaving the AI page.\n *\n * The component is feature-neutral — pass any `packages` array (e.g.\n * session providers, tunnel providers) to reuse the same UI.\n */\n\nimport { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';\nimport { Check, CheckCircle2, Download, Loader2, Puzzle, Trash2 } from 'lucide-react';\nimport { useRemoveAgentPluginMutation } from '@/generated/wspace-operations';\nimport { usePluginAvailability } from '@/hooks/usePluginAvailability';\nimport { getPluginMeta } from '@/constants/pluginCatalog';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { Card } from '../shared/Card';\n\nexport interface PluginHarnessPickerProps {\n /** Package names to offer. Metadata is resolved via `getPluginMeta`. */\n packages: readonly string[];\n /** Active agent id. When null, the picker renders in a disabled state. */\n agentId: string | null;\n /** Optional section heading (rendered above the grid). */\n title?: string;\n /** Optional section description. */\n description?: string;\n /** Fired after a successful install or remove so parents can refetch. */\n onChange?: () => void;\n /**\n * Optional per-card action slot, rendered alongside the Remove button when\n * a package is installed. Lets a feature (e.g. the Security tab) attach a\n * contextual action — like \"Run scan\" — without coupling this generic\n * picker to that feature. Receives the installed package name.\n */\n renderInstalledAction?: (pkg: string) => ReactNode;\n}\n\nexport function PluginHarnessPicker({\n packages,\n agentId,\n title,\n description,\n onChange,\n renderInstalledAction,\n}: PluginHarnessPickerProps) {\n // Plugins are agent-wide (BOFF-2612); no profile threading needed.\n const { installed, installing, install, refetch, loading, error } = usePluginAvailability(\n packages,\n agentId\n );\n const { hasPermission } = usePermissions();\n // removeAgentPlugin is enforced by @rbac(action:\"update\", resource:\"agent\").\n const canRemovePlugin = hasPermission(VIBE_PERMISSIONS.AGENT_UPDATE);\n const [removePlugin] = useRemoveAgentPluginMutation();\n const [removingPkg, setRemovingPkg] = useState<string | null>(null);\n const [removeError, setRemoveError] = useState<Record<string, string>>({});\n // Per-package \"just installed\" pulse — flips on for ~1.5s after a\n // successful install so the button shows a clear success affordance\n // (checkmark + \"Installed\") before the card transitions to its\n // installed/Remove state. Without this the button silently flips from\n // spinner to Remove and users miss the success signal.\n const [justInstalled, setJustInstalled] = useState<Record<string, boolean>>({});\n const justInstalledTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());\n\n useEffect(() => {\n const timers = justInstalledTimers.current;\n return () => {\n for (const t of timers.values()) clearTimeout(t);\n timers.clear();\n };\n }, []);\n\n const handleInstall = useCallback(\n async (pkg: string) => {\n const ok = await install(pkg);\n if (ok) {\n setJustInstalled((prev) => ({ ...prev, [pkg]: true }));\n const existing = justInstalledTimers.current.get(pkg);\n if (existing) clearTimeout(existing);\n const timer = setTimeout(() => {\n setJustInstalled((prev) => {\n if (!prev[pkg]) return prev;\n const next = { ...prev };\n delete next[pkg];\n return next;\n });\n justInstalledTimers.current.delete(pkg);\n }, 1500);\n justInstalledTimers.current.set(pkg, timer);\n onChange?.();\n }\n },\n [install, onChange]\n );\n\n const handleRemove = useCallback(\n async (pkg: string) => {\n if (!agentId) return;\n setRemovingPkg(pkg);\n setRemoveError((e) => ({ ...e, [pkg]: '' }));\n try {\n const { data } = await removePlugin({\n variables: { agentId, packageName: pkg },\n });\n if (!data?.removeAgentPlugin.success) {\n setRemoveError((e) => ({\n ...e,\n [pkg]: data?.removeAgentPlugin.error || 'Remove failed',\n }));\n return;\n }\n await refetch();\n onChange?.();\n } catch (e) {\n setRemoveError((err) => ({\n ...err,\n [pkg]: e instanceof Error ? e.message : String(e),\n }));\n } finally {\n setRemovingPkg(null);\n }\n },\n [agentId, removePlugin, refetch, onChange]\n );\n\n if (packages.length === 0) return null;\n\n return (\n <section className=\"flex flex-col gap-3\" data-testid=\"plugin-harness-picker\">\n {(title || description) && (\n <div>\n {title && <h3 className=\"text-base font-semibold text-text-primary\">{title}</h3>}\n {description && <p className=\"text-sm text-text-secondary mt-1\">{description}</p>}\n </div>\n )}\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text\"\n >\n {error}\n </div>\n )}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {packages.map((pkg) => {\n const meta = getPluginMeta(pkg);\n const isInstalled = !!installed[pkg];\n const isInstalling = installing === pkg;\n const isRemoving = removingPkg === pkg;\n const isBusy = isInstalling || isRemoving || loading;\n const showJustInstalled = !!justInstalled[pkg];\n const err = removeError[pkg];\n return (\n <Card key={pkg} padding=\"md\" className=\"flex flex-col gap-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-shrink-0 size-9 rounded-md bg-bg-sunken flex items-center justify-center\">\n <Puzzle className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-start gap-2 flex-wrap\">\n {/*\n Plugin display name must wrap freely up to a\n reasonable hard cap so the column never blows up\n the modal layout. break-words allows wrapping on\n arbitrary characters when a name has no spaces;\n max-w bound corresponds to ~100 char-cells (a\n generous upper bound on real-world display names).\n */}\n <h4 className=\"font-medium text-text-primary break-words max-w-[100ch]\">\n {meta.displayName}\n </h4>\n {isInstalled && (\n <span className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs rounded bg-status-success-bg-subtle text-status-success-text shrink-0\">\n <Check className=\"size-3\" /> installed\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary mt-0.5\">{meta.description}</p>\n <p\n className=\"text-[10px] font-mono text-text-placeholder mt-1 break-all\"\n title={pkg}\n >\n {pkg}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {isInstalled && showJustInstalled ? (\n <button\n type=\"button\"\n disabled\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-status-success-bg-subtle text-status-success-text border border-status-success-border cursor-default\"\n data-testid={`plugin-installed-${pkg}`}\n aria-live=\"polite\"\n >\n <CheckCircle2 className=\"size-3.5\" />\n Installed\n </button>\n ) : isInstalled && canRemovePlugin ? (\n <button\n type=\"button\"\n disabled={isBusy || !agentId}\n onClick={() => handleRemove(pkg)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md border border-border-default text-text-secondary hover:border-status-error-border hover:text-status-error-text transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`plugin-remove-${pkg}`}\n >\n {isRemoving ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Trash2 className=\"size-3.5\" />\n )}\n Remove\n </button>\n ) : isInstalled ? (\n // Installed, but the actor lacks agent:update to remove it.\n // Show a non-interactive \"Installed\" indicator so the row\n // still reflects state instead of rendering blank.\n <span\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-status-success-bg-subtle text-status-success-text border border-status-success-border\"\n data-testid={`plugin-installed-static-${pkg}`}\n >\n <CheckCircle2 className=\"size-3.5\" />\n Installed\n </span>\n ) : null}\n {isInstalled && !showJustInstalled && renderInstalledAction?.(pkg)}\n {!isInstalled && (\n <button\n type=\"button\"\n disabled={isBusy || !agentId}\n onClick={() => handleInstall(pkg)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`plugin-install-${pkg}`}\n aria-busy={isInstalling}\n >\n {isInstalling ? (\n <>\n <Loader2 className=\"size-3.5 animate-spin\" />\n Installing…\n </>\n ) : (\n <>\n <Download className=\"size-3.5\" />\n Install\n </>\n )}\n </button>\n )}\n </div>\n {err && <p className=\"text-xs text-status-error-text\">{err}</p>}\n </Card>\n );\n })}\n </div>\n {!agentId && <p className=\"text-xs text-text-muted\">Select an agent to manage plugins.</p>}\n </section>\n );\n}\n"],"mappings":"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"}
|
|
@@ -76,7 +76,7 @@ function h({ site: h, pages: g, onBack: _, onSavePage: v, onAddPage: y, pageAddi
|
|
|
76
76
|
title: "Settings",
|
|
77
77
|
children: /* @__PURE__ */ p(d, { className: "size-4" })
|
|
78
78
|
}),
|
|
79
|
-
/* @__PURE__ */ p("button", {
|
|
79
|
+
T && /* @__PURE__ */ p("button", {
|
|
80
80
|
type: "button",
|
|
81
81
|
onClick: T,
|
|
82
82
|
className: "p-1.5 text-text-muted hover:text-status-error-text transition-colors",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocsEditPanel.js","names":[],"sources":["../../../src/components/docs/DocsEditPanel.tsx"],"sourcesContent":["import { useEffect, useState, type ReactNode } from 'react';\nimport { ArrowLeft, Settings, RefreshCw, History, Trash2 } from 'lucide-react';\nimport { DocsPageTree } from './DocsPageTree';\nimport { DocsPageEditor } from './DocsPageEditor';\nimport { DocsSettingsPanel } from './DocsSettingsPanel';\nimport { DocsCompileStatus } from './DocsCompileStatus';\nimport { DocsRevisionHistory } from './DocsRevisionHistory';\nimport type { DocsSite, DocsPage, DocsRevision } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface DocsEditPanelProps {\n site: DocsSite;\n pages: DocsPage[];\n onBack: () => void;\n onSavePage: (\n id: string,\n data: { title?: string; content?: string; slug?: string }\n ) => Promise<void>;\n onAddPage: (parentId?: string, category?: string) => void;\n pageAdding?: boolean;\n
|
|
1
|
+
{"version":3,"file":"DocsEditPanel.js","names":[],"sources":["../../../src/components/docs/DocsEditPanel.tsx"],"sourcesContent":["import { useEffect, useState, type ReactNode } from 'react';\nimport { ArrowLeft, Settings, RefreshCw, History, Trash2 } from 'lucide-react';\nimport { DocsPageTree } from './DocsPageTree';\nimport { DocsPageEditor } from './DocsPageEditor';\nimport { DocsSettingsPanel } from './DocsSettingsPanel';\nimport { DocsCompileStatus } from './DocsCompileStatus';\nimport { DocsRevisionHistory } from './DocsRevisionHistory';\nimport type { DocsSite, DocsPage, DocsRevision } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface DocsEditPanelProps {\n site: DocsSite;\n pages: DocsPage[];\n onBack: () => void;\n onSavePage: (\n id: string,\n data: { title?: string; content?: string; slug?: string }\n ) => Promise<void>;\n onAddPage: (parentId?: string, category?: string) => void;\n pageAdding?: boolean;\n /** Omitted when the caller lacks docs_page:delete — hides the page delete control. */\n onDeletePage?: (pageId: string) => void;\n onCompile: () => void;\n onSaveSettings: (data: {\n name?: string;\n visibility?: 'PRIVATE' | 'PUBLIC';\n provider?: 'DOCUSAURUS' | 'MKDOCS' | 'VITEPRESS';\n providerConfig?: Record<string, unknown>;\n }) => Promise<void>;\n onRollback: (revisionId: string) => Promise<void>;\n /** Omitted when the caller lacks docs_site:delete — hides the Delete-site control. */\n onDeleteSite?: () => Promise<void>;\n rollingBack: string | null;\n compileRevision: DocsRevision | null;\n onPollRevision?: (id: string) => Promise<DocsRevision | null>;\n compiling: boolean;\n /** Generic grant-based Share affordance (owner-keyed; supplied by the host). */\n shareSlot?: ReactNode;\n}\n\nexport function DocsEditPanel({\n site,\n pages,\n onBack,\n onSavePage,\n onAddPage,\n pageAdding,\n onDeletePage,\n onCompile,\n onSaveSettings,\n onRollback,\n onDeleteSite,\n rollingBack,\n compileRevision,\n onPollRevision,\n compiling,\n shareSlot,\n}: DocsEditPanelProps) {\n const tr = useTr();\n const [selectedPageId, setSelectedPageId] = useState<string | null>(pages[0]?.id || null);\n const [showSettings, setShowSettings] = useState(false);\n const [showHistory, setShowHistory] = useState(false);\n\n const selectedPage = pages.find((p) => p.id === selectedPageId) || null;\n\n useEffect(() => {\n if (pages.length === 0) {\n if (selectedPageId) setSelectedPageId(null);\n return;\n }\n if (!selectedPageId || !pages.some((page) => page.id === selectedPageId)) {\n setSelectedPageId(pages[0].id);\n }\n }, [pages, selectedPageId]);\n\n return (\n <div className=\"flex flex-col h-full\" style={{ minHeight: 'calc(100vh - 380px)' }}>\n {/* Top bar */}\n <div className=\"flex items-center justify-between px-4 py-2 border-b border-border-default bg-bg-surface\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={onBack}\n className=\"flex items-center gap-1.5 text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('vibecontrols.docs.edit.preview', 'Preview')}\n </button>\n <span className=\"text-text-muted\">|</span>\n <span className=\"text-sm font-medium text-text-primary\">{site.name}</span>\n </div>\n <div className=\"flex items-center gap-2\">\n {shareSlot}\n <div className=\"relative\">\n <DocsCompileStatus revision={compileRevision} onPoll={onPollRevision} />\n </div>\n <button\n type=\"button\"\n onClick={onCompile}\n disabled={compiling}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n <RefreshCw className={`size-3.5 ${compiling ? 'animate-spin' : ''}`} />\n {tr('vibecontrols.docs.edit.compile', 'Compile')}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setShowHistory((v) => !v);\n setShowSettings(false);\n }}\n className={`p-1.5 transition-colors ${showHistory ? 'text-action-primary-text-alt' : 'text-text-secondary hover:text-text-primary'}`}\n title=\"Build history\"\n >\n <History className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setShowSettings(true);\n setShowHistory(false);\n }}\n className=\"p-1.5 text-text-secondary hover:text-text-primary transition-colors\"\n title=\"Settings\"\n >\n <Settings className=\"size-4\" />\n </button>\n {onDeleteSite && (\n <button\n type=\"button\"\n onClick={onDeleteSite}\n className=\"p-1.5 text-text-muted hover:text-status-error-text transition-colors\"\n title=\"Delete site\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n )}\n </div>\n </div>\n\n {/* Revision history drawer */}\n {showHistory && (\n <div className=\"border-b border-border-default bg-bg-base max-h-64 overflow-y-auto\">\n <DocsRevisionHistory\n revisions={site.revisions ?? []}\n currentRevisionId={site.currentRevisionId}\n onRollback={onRollback}\n rollingBack={rollingBack}\n />\n </div>\n )}\n\n {/* Main content: sidebar + editor */}\n <div className=\"flex-1 grid grid-cols-[260px_1fr] min-h-0\">\n {/* Page tree sidebar */}\n <div className=\"border-r border-border-default overflow-hidden\">\n <DocsPageTree\n pages={pages}\n selectedPageId={selectedPageId}\n onSelectPage={setSelectedPageId}\n onAddPage={onAddPage}\n pageAdding={pageAdding}\n onDeletePage={onDeletePage}\n />\n </div>\n\n {/* Editor */}\n <div className=\"overflow-hidden\">\n <DocsPageEditor page={selectedPage} onSave={onSavePage} />\n </div>\n </div>\n\n {/* Settings panel */}\n <DocsSettingsPanel\n site={site}\n open={showSettings}\n onClose={() => setShowSettings(false)}\n onSave={onSaveSettings}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;AAwCA,SAAgB,EAAc,EAC5B,SACA,UACA,WACA,eACA,cACA,eACA,iBACA,cACA,mBACA,eACA,iBACA,gBACA,oBACA,mBACA,cACA,gBACqB;CACrB,IAAM,IAAK,GAAO,EACZ,CAAC,GAAgB,KAAqB,EAAwB,EAAM,IAAI,MAAM,KAAK,EACnF,CAAC,GAAc,KAAmB,EAAS,GAAM,EACjD,CAAC,GAAa,KAAkB,EAAS,GAAM,EAE/C,IAAe,EAAM,MAAM,MAAM,EAAE,OAAO,EAAe,IAAI;AAYnE,QAVA,QAAgB;AACd,MAAI,EAAM,WAAW,GAAG;AACtB,GAAI,KAAgB,EAAkB,KAAK;AAC3C;;AAEF,GAAI,CAAC,KAAkB,CAAC,EAAM,MAAM,MAAS,EAAK,OAAO,EAAe,KACtE,EAAkB,EAAM,GAAG,GAAG;IAE/B,CAAC,GAAO,EAAe,CAAC,EAGzB,kBAAC,OAAD;EAAK,WAAU;EAAuB,OAAO,EAAE,WAAW,uBAAuB;YAAjF;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,UAAD;OACE,MAAK;OACL,SAAS;OACT,WAAU;iBAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,EAC/B,EAAG,kCAAkC,UAAU,CACzC;;MACT,kBAAC,QAAD;OAAM,WAAU;iBAAkB;OAAQ,CAAA;MAC1C,kBAAC,QAAD;OAAM,WAAU;iBAAyC,EAAK;OAAY,CAAA;MACtE;QACN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACG;MACD,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD;QAAmB,UAAU;QAAiB,QAAQ;QAAkB,CAAA;OACpE,CAAA;MACN,kBAAC,UAAD;OACE,MAAK;OACL,SAAS;OACT,UAAU;OACV,WAAU;iBAJZ,CAME,kBAAC,GAAD,EAAW,WAAW,YAAY,IAAY,iBAAiB,MAAQ,CAAA,EACtE,EAAG,kCAAkC,UAAU,CACzC;;MACT,kBAAC,UAAD;OACE,MAAK;OACL,eAAe;AAEb,QADA,GAAgB,MAAM,CAAC,EAAE,EACzB,EAAgB,GAAM;;OAExB,WAAW,2BAA2B,IAAc,iCAAiC;OACrF,OAAM;iBAEN,kBAAC,GAAD,EAAS,WAAU,UAAW,CAAA;OACvB,CAAA;MACT,kBAAC,UAAD;OACE,MAAK;OACL,eAAe;AAEb,QADA,EAAgB,GAAK,EACrB,EAAe,GAAM;;OAEvB,WAAU;OACV,OAAM;iBAEN,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA;OACxB,CAAA;MACR,KACC,kBAAC,UAAD;OACE,MAAK;OACL,SAAS;OACT,WAAU;OACV,OAAM;iBAEN,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA;OACtB,CAAA;MAEP;OACF;;GAGL,KACC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,WAAW,EAAK,aAAa,EAAE;KAC/B,mBAAmB,EAAK;KACZ;KACC;KACb,CAAA;IACE,CAAA;GAIR,kBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MACS;MACS;MAChB,cAAc;MACH;MACC;MACE;MACd,CAAA;KACE,CAAA,EAGN,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MAAgB,MAAM;MAAc,QAAQ;MAAc,CAAA;KACtD,CAAA,CACF;;GAGN,kBAAC,GAAD;IACQ;IACN,MAAM;IACN,eAAe,EAAgB,GAAM;IACrC,QAAQ;IACR,CAAA;GACE"}
|
|
@@ -40,7 +40,7 @@ function f({ node: e, depth: t, selectedPageId: o, onSelectPage: s, onDeletePage
|
|
|
40
40
|
className: "truncate flex-1",
|
|
41
41
|
children: e.page.title
|
|
42
42
|
}),
|
|
43
|
-
/* @__PURE__ */ l("button", {
|
|
43
|
+
d && /* @__PURE__ */ l("button", {
|
|
44
44
|
type: "button",
|
|
45
45
|
onClick: (t) => {
|
|
46
46
|
t.stopPropagation(), d(e.page.id);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocsPageTree.js","names":[],"sources":["../../../src/components/docs/DocsPageTree.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n FileText,\n FolderOpen,\n Plus,\n Trash2,\n ChevronRight,\n ChevronDown,\n Loader2,\n} from 'lucide-react';\nimport type { DocsPage } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface DocsPageTreeProps {\n pages: DocsPage[];\n selectedPageId: string | null;\n onSelectPage: (pageId: string) => void;\n onAddPage: (parentId?: string, category?: string) => void;\n pageAdding?: boolean;\n
|
|
1
|
+
{"version":3,"file":"DocsPageTree.js","names":[],"sources":["../../../src/components/docs/DocsPageTree.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n FileText,\n FolderOpen,\n Plus,\n Trash2,\n ChevronRight,\n ChevronDown,\n Loader2,\n} from 'lucide-react';\nimport type { DocsPage } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface DocsPageTreeProps {\n pages: DocsPage[];\n selectedPageId: string | null;\n onSelectPage: (pageId: string) => void;\n onAddPage: (parentId?: string, category?: string) => void;\n pageAdding?: boolean;\n /** Omitted when the caller lacks docs_page:delete — hides the delete control. */\n onDeletePage?: (pageId: string) => void;\n}\n\ninterface TreeNode {\n page: DocsPage;\n children: TreeNode[];\n}\n\nfunction buildTree(pages: DocsPage[]): TreeNode[] {\n const map = new Map<string, TreeNode>();\n const roots: TreeNode[] = [];\n\n for (const page of pages) {\n map.set(page.id, { page, children: [] });\n }\n\n for (const page of pages) {\n const node = map.get(page.id)!;\n if (page.parentId && map.has(page.parentId)) {\n map.get(page.parentId)!.children.push(node);\n } else {\n roots.push(node);\n }\n }\n\n return roots;\n}\n\nfunction TreeItem({\n node,\n depth,\n selectedPageId,\n onSelectPage,\n onDeletePage,\n expandedIds,\n toggleExpand,\n}: {\n node: TreeNode;\n depth: number;\n selectedPageId: string | null;\n onSelectPage: (id: string) => void;\n onDeletePage?: (id: string) => void;\n expandedIds: Set<string>;\n toggleExpand: (id: string) => void;\n}) {\n const isSelected = selectedPageId === node.page.id;\n const hasChildren = node.children.length > 0;\n const isExpanded = expandedIds.has(node.page.id);\n\n return (\n <div>\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => onSelectPage(node.page.id))();\n }\n }}\n className={`flex items-center gap-1 px-2 py-1.5 cursor-pointer rounded text-sm transition-colors group ${\n isSelected\n ? 'bg-[var(--color-accent-soft)] text-text-primary'\n : 'text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n }`}\n style={{ paddingLeft: `${8 + depth * 16}px` }}\n onClick={() => onSelectPage(node.page.id)}\n >\n {hasChildren ? (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n toggleExpand(node.page.id);\n }}\n className=\"p-0.5 -ml-1\"\n >\n {isExpanded ? <ChevronDown className=\"size-3\" /> : <ChevronRight className=\"size-3\" />}\n </button>\n ) : (\n <span className=\"w-4\" />\n )}\n {node.page.category ? (\n <FolderOpen className=\"size-3.5 flex-shrink-0\" />\n ) : (\n <FileText className=\"size-3.5 flex-shrink-0\" />\n )}\n <span className=\"truncate flex-1\">{node.page.title}</span>\n {onDeletePage && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onDeletePage(node.page.id);\n }}\n className=\"opacity-0 group-hover:opacity-100 p-0.5 text-text-muted hover:text-status-error-text transition-all\"\n title=\"Delete\"\n >\n <Trash2 className=\"size-3\" />\n </button>\n )}\n </div>\n {hasChildren && isExpanded && (\n <div>\n {node.children.map((child) => (\n <TreeItem\n key={child.page.id}\n node={child}\n depth={depth + 1}\n selectedPageId={selectedPageId}\n onSelectPage={onSelectPage}\n onDeletePage={onDeletePage}\n expandedIds={expandedIds}\n toggleExpand={toggleExpand}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nexport function DocsPageTree({\n pages,\n selectedPageId,\n onSelectPage,\n onAddPage,\n pageAdding,\n onDeletePage,\n}: DocsPageTreeProps) {\n const tr = useTr();\n const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set(pages.map((p) => p.id)));\n\n const toggleExpand = (id: string) => {\n setExpandedIds((prev) => {\n const next = new Set(prev);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n return next;\n });\n };\n\n const tree = buildTree(pages);\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex items-center justify-between px-3 py-2 border-b border-border-default\">\n <span className=\"text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.docs.pageTree.pages', 'Pages')}\n </span>\n <button\n type=\"button\"\n onClick={() => onAddPage()}\n disabled={pageAdding}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors disabled:opacity-50\"\n title=\"Add page\"\n >\n {pageAdding ? <Loader2 className=\"size-4 animate-spin\" /> : <Plus className=\"size-4\" />}\n </button>\n </div>\n <div className=\"flex-1 overflow-y-auto py-1\">\n {tree.length > 0 ? (\n tree.map((node) => (\n <TreeItem\n key={node.page.id}\n node={node}\n depth={0}\n selectedPageId={selectedPageId}\n onSelectPage={onSelectPage}\n onDeletePage={onDeletePage}\n expandedIds={expandedIds}\n toggleExpand={toggleExpand}\n />\n ))\n ) : (\n <div className=\"p-4 text-center\">\n <p className=\"text-sm text-text-muted mb-2\">\n {tr('vibecontrols.docs.pageTree.noPages', 'No pages yet')}\n </p>\n <button\n type=\"button\"\n onClick={() => onAddPage()}\n className=\"text-sm text-action-primary-bg hover:underline\"\n >\n {tr('vibecontrols.docs.pageTree.addFirstPage', 'Add your first page')}\n </button>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|