@burdenoff/microfe-vibecontrols 2026.527.2 → 2026.527.3

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.
@@ -4,41 +4,45 @@ import "../shared/index.js";
4
4
  import { configureAgentHook as r, getPlannotatorStatus as i, installPlannotatorBinary as a, listSupportedAgents as o } from "../../services/planApi.js";
5
5
  import { useCallback as s, useEffect as c, useMemo as l, useRef as u, useState as d } from "react";
6
6
  import { useI18n as f } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
7
- import { AlertCircle as p, CheckCircle2 as m, Loader2 as h, PlayCircle as g, RefreshCcw as _, Settings as v, Wifi as y, WifiOff as b } from "lucide-react";
8
- import { Fragment as x, jsx as S, jsxs as C } from "react/jsx-runtime";
7
+ import { AlertCircle as p, CheckCircle2 as m, Inbox as h, Loader2 as g, PlayCircle as _, RefreshCcw as v, Settings as y, Wifi as b, WifiOff as x } from "lucide-react";
8
+ import { Fragment as S, jsx as C, jsxs as w } from "react/jsx-runtime";
9
9
  //#region src/components/ai/AIPlanPanel.tsx
10
- function w({ agent: r, agentApiKey: i, tunnelUrl: a }) {
10
+ function T({ agent: r, agentApiKey: i, tunnelUrl: a }) {
11
11
  let { t: o } = f(), s = (e, t) => {
12
12
  let n = o(e);
13
13
  return n === e ? t : n;
14
14
  };
15
- return /* @__PURE__ */ S(n, {
15
+ return /* @__PURE__ */ C(n, {
16
16
  agentId: r.id,
17
17
  required: [e, t],
18
18
  mode: "all",
19
19
  eyebrow: s("plan.pluginRequiredEyebrow", "Plan plugin required"),
20
20
  title: s("plan.installPlanProviderTitle", "Install the plan provider"),
21
21
  description: s("plan.installPlanProviderDesc", "Plan mode routes plan sessions through a plan provider plugin on your agent. Install the orchestrator and a provider (Plannotator) below to continue."),
22
- children: /* @__PURE__ */ S(T, {
22
+ children: /* @__PURE__ */ C(E, {
23
23
  agent: r,
24
24
  agentApiKey: i,
25
25
  tunnelUrl: a
26
26
  })
27
27
  });
28
28
  }
29
- function T({ agent: e, agentApiKey: t, tunnelUrl: n }) {
29
+ function E({ agent: e, agentApiKey: t, tunnelUrl: n }) {
30
30
  let { t: u } = f(), m = s((e, t) => {
31
31
  let n = u(e);
32
32
  return n === e ? t : n;
33
- }, [u]), [g, v] = d(null), [y, b] = d(null), [x, w] = d(!0), [T, k] = d([]), [A, j] = d(!1), [M, N] = d(null), [P, F] = d(null), [I, L] = d(null), R = s(async () => {
34
- w(!0), b(null);
33
+ }, [u]), [h, _] = d(null), [y, b] = d(null), [x, S] = d(!0), [T, E] = d([]), [A, j] = d(!1), [M, N] = d(null), [P, F] = d(null), [I, L] = d(null), R = s(async (t = !1) => {
34
+ t || (S(!0), b(null));
35
35
  try {
36
- let [t, n] = await Promise.all([i(e), o(e).catch(() => ({ agents: [] }))]);
37
- v(t), k(n.agents ?? []);
36
+ if (t) {
37
+ _(await i(e));
38
+ return;
39
+ }
40
+ let [n, r] = await Promise.all([i(e), o(e).catch(() => ({ agents: [] }))]);
41
+ _(n), E(r.agents ?? []);
38
42
  } catch (e) {
39
- b(e instanceof Error ? e.message : "Failed to load plan status");
43
+ t || b(e instanceof Error ? e.message : "Failed to load plan status");
40
44
  } finally {
41
- w(!1);
45
+ t || S(!1);
42
46
  }
43
47
  }, [e]);
44
48
  c(() => {
@@ -72,35 +76,41 @@ function T({ agent: e, agentApiKey: t, tunnelUrl: n }) {
72
76
  e,
73
77
  R,
74
78
  m
75
- ]), V = l(() => x ? "loading" : y ? "status-error" : g?.installed ? T.some((e) => e.hookConfigured) ? "ready" : "wizard" : "install-cli", [
76
- g,
79
+ ]), V = l(() => x ? "loading" : y ? "status-error" : h?.installed ? T.some((e) => e.hookConfigured) ? "ready" : "wizard" : "install-cli", [
80
+ h,
77
81
  y,
78
82
  x,
79
83
  T
80
84
  ]);
81
- return V === "loading" ? /* @__PURE__ */ C("div", {
85
+ return c(() => {
86
+ if (V !== "ready") return;
87
+ let e = setInterval(() => {
88
+ document.hidden || R(!0);
89
+ }, 5e3);
90
+ return () => clearInterval(e);
91
+ }, [V, R]), V === "loading" ? /* @__PURE__ */ w("div", {
82
92
  className: "flex items-center gap-2 p-6 text-text-secondary",
83
- children: [/* @__PURE__ */ S(h, { className: "h-4 w-4 animate-spin" }), /* @__PURE__ */ S("span", { children: m("plan.loadingStatus", "Loading plan status…") })]
84
- }) : V === "status-error" ? /* @__PURE__ */ S("div", {
93
+ children: [/* @__PURE__ */ C(g, { className: "h-4 w-4 animate-spin" }), /* @__PURE__ */ C("span", { children: m("plan.loadingStatus", "Loading plan status…") })]
94
+ }) : V === "status-error" ? /* @__PURE__ */ C("div", {
85
95
  className: "rounded-md border border-status-error-border bg-status-error-bg p-4 text-sm text-status-error-text",
86
- children: /* @__PURE__ */ C("div", {
96
+ children: /* @__PURE__ */ w("div", {
87
97
  className: "flex items-start gap-2",
88
- children: [/* @__PURE__ */ S(p, { className: "h-4 w-4 mt-0.5 shrink-0" }), /* @__PURE__ */ C("div", {
98
+ children: [/* @__PURE__ */ C(p, { className: "h-4 w-4 mt-0.5 shrink-0" }), /* @__PURE__ */ w("div", {
89
99
  className: "flex-1",
90
100
  children: [
91
- /* @__PURE__ */ S("p", {
101
+ /* @__PURE__ */ C("p", {
92
102
  className: "font-medium",
93
103
  children: m("plan.statusError", "Could not load plan status from the agent.")
94
104
  }),
95
- /* @__PURE__ */ S("p", {
105
+ /* @__PURE__ */ C("p", {
96
106
  className: "text-xs opacity-80 mt-1",
97
107
  children: y
98
108
  }),
99
- /* @__PURE__ */ C("button", {
109
+ /* @__PURE__ */ w("button", {
100
110
  onClick: () => void R(),
101
111
  className: "mt-3 inline-flex items-center gap-1 text-xs font-medium hover:underline",
102
112
  children: [
103
- /* @__PURE__ */ S(_, { className: "h-3 w-3" }),
113
+ /* @__PURE__ */ C(v, { className: "h-3 w-3" }),
104
114
  " ",
105
115
  m("plan.retry", "Retry")
106
116
  ]
@@ -108,113 +118,114 @@ function T({ agent: e, agentApiKey: t, tunnelUrl: n }) {
108
118
  ]
109
119
  })]
110
120
  })
111
- }) : /* @__PURE__ */ C("div", {
121
+ }) : /* @__PURE__ */ w("div", {
112
122
  className: "space-y-4",
113
123
  children: [
114
- P && /* @__PURE__ */ S("div", {
124
+ P && /* @__PURE__ */ C("div", {
115
125
  role: "status",
116
126
  className: "rounded-md border border-status-success-border bg-status-success-bg px-3 py-2 text-xs text-status-success-text",
117
127
  children: P
118
128
  }),
119
- I && /* @__PURE__ */ S("div", {
129
+ I && /* @__PURE__ */ C("div", {
120
130
  role: "alert",
121
131
  className: "rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text",
122
132
  children: I
123
133
  }),
124
- V === "install-cli" && /* @__PURE__ */ S(E, {
134
+ V === "install-cli" && /* @__PURE__ */ C(D, {
125
135
  installing: A,
126
136
  onInstall: z,
127
137
  tr: m
128
138
  }),
129
- V === "wizard" && /* @__PURE__ */ S(D, {
139
+ V === "wizard" && /* @__PURE__ */ C(O, {
130
140
  agents: T,
131
141
  configuring: M,
132
142
  onConfigure: B,
133
143
  tr: m
134
144
  }),
135
- V === "ready" && /* @__PURE__ */ S(O, {
145
+ V === "ready" && /* @__PURE__ */ C(k, {
136
146
  agent: e,
137
147
  agents: T,
138
148
  agentApiKey: t,
139
149
  tunnelUrl: n,
150
+ runningSessions: h?.runningSessions ?? [],
140
151
  onReconfigure: () => F(null),
141
152
  tr: m
142
153
  })
143
154
  ]
144
155
  });
145
156
  }
146
- function E({ installing: e, onInstall: t, tr: n }) {
147
- return /* @__PURE__ */ S("div", {
157
+ function D({ installing: e, onInstall: t, tr: n }) {
158
+ return /* @__PURE__ */ C("div", {
148
159
  className: "rounded-lg border border-border-default bg-bg-elevated p-6",
149
- children: /* @__PURE__ */ C("div", {
160
+ children: /* @__PURE__ */ w("div", {
150
161
  className: "flex items-start gap-3",
151
- children: [/* @__PURE__ */ S(g, { className: "h-6 w-6 text-action-primary-bg shrink-0" }), /* @__PURE__ */ C("div", {
162
+ children: [/* @__PURE__ */ C(_, { className: "h-6 w-6 text-action-primary-bg shrink-0" }), /* @__PURE__ */ w("div", {
152
163
  className: "flex-1",
153
164
  children: [
154
- /* @__PURE__ */ S("h3", {
165
+ /* @__PURE__ */ C("h3", {
155
166
  className: "text-base font-semibold text-text-primary",
156
167
  children: n("plan.installCliTitle", "Install the plannotator CLI")
157
168
  }),
158
- /* @__PURE__ */ S("p", {
169
+ /* @__PURE__ */ C("p", {
159
170
  className: "text-sm text-text-secondary mt-1",
160
171
  children: n("plan.installCliDesc", "Plan mode runs a local plannotator binary on the agent and proxies its UI back here. The plugin installs the binary into ~/.local/bin/plannotator (sha256-pinned). Roughly 30 seconds.")
161
172
  }),
162
- /* @__PURE__ */ S("button", {
173
+ /* @__PURE__ */ C("button", {
163
174
  onClick: t,
164
175
  disabled: e,
165
176
  className: "mt-4 inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50",
166
- children: e ? /* @__PURE__ */ C(x, { children: [/* @__PURE__ */ S(h, { className: "h-4 w-4 animate-spin" }), n("plan.installing", "Installing…")] }) : n("plan.installCliCta", "Install plannotator CLI")
177
+ children: e ? /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ C(g, { className: "h-4 w-4 animate-spin" }), n("plan.installing", "Installing…")] }) : n("plan.installCliCta", "Install plannotator CLI")
167
178
  })
168
179
  ]
169
180
  })]
170
181
  })
171
182
  });
172
183
  }
173
- function D({ agents: e, configuring: t, onConfigure: n, tr: r }) {
184
+ function O({ agents: e, configuring: t, onConfigure: n, tr: r }) {
174
185
  let i = e.filter((e) => e.cliInstalled);
175
- return /* @__PURE__ */ S("div", {
186
+ return /* @__PURE__ */ C("div", {
176
187
  className: "rounded-lg border border-border-default bg-bg-elevated p-6",
177
- children: /* @__PURE__ */ C("div", {
188
+ children: /* @__PURE__ */ w("div", {
178
189
  className: "flex items-start gap-3",
179
- children: [/* @__PURE__ */ S(v, { className: "h-6 w-6 text-action-primary-bg shrink-0" }), /* @__PURE__ */ C("div", {
190
+ children: [/* @__PURE__ */ C(y, { className: "h-6 w-6 text-action-primary-bg shrink-0" }), /* @__PURE__ */ w("div", {
180
191
  className: "flex-1",
181
192
  children: [
182
- /* @__PURE__ */ S("h3", {
193
+ /* @__PURE__ */ C("h3", {
183
194
  className: "text-base font-semibold text-text-primary",
184
195
  children: r("plan.wizardTitle", "Connect an AI coding agent")
185
196
  }),
186
- /* @__PURE__ */ S("p", {
197
+ /* @__PURE__ */ C("p", {
187
198
  className: "text-sm text-text-secondary mt-1",
188
199
  children: r("plan.wizardDesc", "Configure a plannotator hook for at least one installed AI coding agent. The hook forwards plan content from the agent to plannotator on every plan-finalisation step.")
189
200
  }),
190
- i.length === 0 && /* @__PURE__ */ S("p", {
201
+ i.length === 0 && /* @__PURE__ */ C("p", {
191
202
  className: "mt-4 text-sm text-status-warning-text",
192
203
  children: r("plan.wizardNoAgents", "No AI coding agents detected in this sandbox. Install one (Claude Code, OpenCode, Codex, Pi, or Gemini) and refresh.")
193
204
  }),
194
- /* @__PURE__ */ S("ul", {
205
+ /* @__PURE__ */ C("ul", {
195
206
  className: "mt-4 space-y-2",
196
- children: e.map((e) => /* @__PURE__ */ C("li", {
207
+ children: e.map((e) => /* @__PURE__ */ w("li", {
197
208
  className: "flex items-start gap-3 p-3 border border-border-default rounded-md bg-bg-surface",
198
209
  children: [
199
- e.hookConfigured ? /* @__PURE__ */ S(m, { className: "h-5 w-5 text-status-success-text shrink-0 mt-0.5" }) : /* @__PURE__ */ S("div", { className: "h-5 w-5 rounded-full border-2 border-border-default shrink-0 mt-0.5" }),
200
- /* @__PURE__ */ C("div", {
210
+ e.hookConfigured ? /* @__PURE__ */ C(m, { className: "h-5 w-5 text-status-success-text shrink-0 mt-0.5" }) : /* @__PURE__ */ C("div", { className: "h-5 w-5 rounded-full border-2 border-border-default shrink-0 mt-0.5" }),
211
+ /* @__PURE__ */ w("div", {
201
212
  className: "flex-1",
202
213
  children: [
203
- /* @__PURE__ */ S("p", {
214
+ /* @__PURE__ */ C("p", {
204
215
  className: "text-sm font-medium text-text-primary",
205
216
  children: e.name
206
217
  }),
207
- /* @__PURE__ */ S("p", {
218
+ /* @__PURE__ */ C("p", {
208
219
  className: "text-xs text-text-secondary mt-0.5",
209
220
  children: e.instructions
210
221
  }),
211
- !e.cliInstalled && /* @__PURE__ */ S("p", {
222
+ !e.cliInstalled && /* @__PURE__ */ C("p", {
212
223
  className: "text-xs text-status-warning-text mt-1",
213
224
  children: r("plan.agentCliMissing", "CLI not detected on this sandbox.")
214
225
  })
215
226
  ]
216
227
  }),
217
- /* @__PURE__ */ S("button", {
228
+ /* @__PURE__ */ C("button", {
218
229
  disabled: !e.cliInstalled || e.hookConfigured || t === e.agent,
219
230
  onClick: () => n(e.agent),
220
231
  className: "px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50",
@@ -228,13 +239,21 @@ function D({ agents: e, configuring: t, onConfigure: n, tr: r }) {
228
239
  })
229
240
  });
230
241
  }
231
- function O({ agent: e, agents: t, agentApiKey: n, tunnelUrl: r, onReconfigure: i, tr: a }) {
232
- let o = u(null), [c, f] = d("connecting"), m = l(() => {
233
- if (!r) return null;
234
- let e = r.replace(/\/$/, "");
235
- return new URL(`${e}/plan/`).toString();
236
- }, [r]), g = s(() => {
237
- let e = o.current;
242
+ function k({ agent: e, agents: t, agentApiKey: n, tunnelUrl: r, runningSessions: i, onReconfigure: a, tr: o }) {
243
+ let f = u(null), [m, _] = d("connecting"), T = l(() => i.length > 0 ? [...i].sort((e, t) => (Date.parse(t.startedAt) || 0) - (Date.parse(e.startedAt) || 0))[0] : null, [i]), E = T?.sessionId ?? null, D = l(() => {
244
+ if (!r || !n || !E) return null;
245
+ let e = r.replace(/\/$/, ""), t = new URL(`${e}/plan/${E}/`);
246
+ return t.searchParams.set("apiKey", n), t.toString();
247
+ }, [
248
+ r,
249
+ n,
250
+ E
251
+ ]);
252
+ c(() => {
253
+ _("connecting");
254
+ }, [E]);
255
+ let O = s(() => {
256
+ let e = f.current;
238
257
  if (!(!e?.contentWindow || !n || !r)) try {
239
258
  let t = new URL(r).origin;
240
259
  e.contentWindow.postMessage({
@@ -242,86 +261,100 @@ function O({ agent: e, agents: t, agentApiKey: n, tunnelUrl: r, onReconfigure: i
242
261
  apiKey: n
243
262
  }, t);
244
263
  } catch {}
245
- }, [n, r]), x = s(() => {
246
- f("connected"), g();
247
- }, [g]), w = s(() => {
248
- f("error");
249
- }, []), T = s(() => {
250
- let e = o.current;
264
+ }, [n, r]), k = s(() => {
265
+ _("connected"), O();
266
+ }, [O]), A = s(() => {
267
+ _("error");
268
+ }, []), j = s(() => {
269
+ let e = f.current;
251
270
  if (!e) return;
252
- f("connecting");
271
+ _("connecting");
253
272
  let t = e.src;
254
273
  e.src = "about:blank", requestAnimationFrame(() => {
255
274
  e.src = t;
256
275
  });
257
276
  }, []);
258
- if (!r || !n) return /* @__PURE__ */ S("div", {
277
+ if (!r || !n) return /* @__PURE__ */ C("div", {
259
278
  className: "rounded-lg border border-status-warning-border bg-status-warning-bg p-4 text-sm text-status-warning-text",
260
- children: a("plan.tunnelMissing", "Agent tunnel URL or API key is missing. Bring the agent online and refresh.")
279
+ children: o("plan.tunnelMissing", "Agent tunnel URL or API key is missing. Bring the agent online and refresh.")
261
280
  });
262
- let E = t.filter((e) => e.hookConfigured);
263
- return /* @__PURE__ */ C("div", {
281
+ let M = t.filter((e) => e.hookConfigured);
282
+ return /* @__PURE__ */ w("div", {
264
283
  className: "space-y-3",
265
- children: [/* @__PURE__ */ C("div", {
284
+ children: [/* @__PURE__ */ w("div", {
266
285
  className: "flex items-center gap-2 text-xs text-text-secondary",
267
286
  children: [
268
- /* @__PURE__ */ C("span", {
287
+ T && /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ w("span", {
269
288
  className: "inline-flex items-center gap-1",
270
289
  children: [
271
- c === "connected" && /* @__PURE__ */ S(y, { className: "h-3.5 w-3.5 text-status-success-text" }),
272
- c === "connecting" && /* @__PURE__ */ S(h, { className: "h-3.5 w-3.5 animate-spin text-status-warning-text" }),
273
- c === "error" && /* @__PURE__ */ S(b, { className: "h-3.5 w-3.5 text-status-error-text" }),
274
- a(`plan.connection.${c}`, c)
290
+ m === "connected" && /* @__PURE__ */ C(b, { className: "h-3.5 w-3.5 text-status-success-text" }),
291
+ m === "connecting" && /* @__PURE__ */ C(g, { className: "h-3.5 w-3.5 animate-spin text-status-warning-text" }),
292
+ m === "error" && /* @__PURE__ */ C(x, { className: "h-3.5 w-3.5 text-status-error-text" }),
293
+ o(`plan.connection.${m}`, m)
275
294
  ]
276
- }),
277
- /* @__PURE__ */ S("span", {
295
+ }), /* @__PURE__ */ C("span", {
278
296
  "aria-hidden": "true",
279
297
  children: "·"
280
- }),
281
- /* @__PURE__ */ S("span", { children: a("plan.hookCount", `${E.length} hook(s) configured`) }),
282
- /* @__PURE__ */ C("button", {
283
- onClick: i,
298
+ })] }),
299
+ /* @__PURE__ */ C("span", { children: o("plan.hookCount", `${M.length} hook(s) configured`) }),
300
+ /* @__PURE__ */ w("button", {
301
+ onClick: a,
284
302
  className: "ml-auto inline-flex items-center gap-1 hover:text-text-primary",
285
- children: [/* @__PURE__ */ S(v, { className: "h-3.5 w-3.5" }), a("plan.reopenWizard", "Manage hooks")]
303
+ children: [/* @__PURE__ */ C(y, { className: "h-3.5 w-3.5" }), o("plan.reopenWizard", "Manage hooks")]
286
304
  })
287
305
  ]
288
- }), /* @__PURE__ */ C("div", {
306
+ }), /* @__PURE__ */ w("div", {
289
307
  className: "border border-border-default rounded-lg overflow-hidden bg-bg-sunken relative h-[calc(100vh-320px)] min-h-[500px]",
290
308
  children: [
291
- m && /* @__PURE__ */ S("iframe", {
292
- ref: o,
293
- src: m,
294
- title: a("plan.iframeTitle", "Plannotator plan workspace"),
309
+ !T && /* @__PURE__ */ w("div", {
310
+ className: "absolute inset-0 flex flex-col items-center justify-center gap-2 p-6 text-center",
311
+ children: [
312
+ /* @__PURE__ */ C(h, { className: "h-8 w-8 text-text-muted" }),
313
+ /* @__PURE__ */ C("p", {
314
+ className: "text-sm font-medium text-text-primary",
315
+ children: o("plan.noSessionTitle", "No active plan session")
316
+ }),
317
+ /* @__PURE__ */ C("p", {
318
+ className: "max-w-md text-xs text-text-secondary",
319
+ children: o("plan.noSessionDesc", "Start a plan in a connected coding agent — when it finalises a plan, the configured hook forwards it here for review.")
320
+ })
321
+ ]
322
+ }),
323
+ D && /* @__PURE__ */ C("iframe", {
324
+ ref: f,
325
+ src: D,
326
+ title: o("plan.iframeTitle", "Plannotator plan workspace"),
295
327
  className: "w-full h-full border-0",
296
328
  allow: "clipboard-read; clipboard-write",
297
- sandbox: "allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation-by-user-activation",
298
- onLoad: x,
299
- onError: w,
329
+ referrerPolicy: "no-referrer",
330
+ sandbox: "allow-same-origin allow-scripts allow-forms",
331
+ onLoad: k,
332
+ onError: A,
300
333
  "data-testid": "plan-iframe"
301
334
  }),
302
- c === "connecting" && /* @__PURE__ */ S("div", {
335
+ T && m === "connecting" && /* @__PURE__ */ C("div", {
303
336
  className: "absolute inset-0 flex items-center justify-center bg-bg-sunken/80 pointer-events-none",
304
- children: /* @__PURE__ */ S(h, { className: "h-6 w-6 animate-spin text-action-primary-bg" })
337
+ children: /* @__PURE__ */ C(g, { className: "h-6 w-6 animate-spin text-action-primary-bg" })
305
338
  }),
306
- c === "error" && /* @__PURE__ */ S("div", {
339
+ T && m === "error" && /* @__PURE__ */ C("div", {
307
340
  className: "absolute inset-0 flex items-center justify-center bg-bg-sunken p-6 text-center",
308
- children: /* @__PURE__ */ C("div", { children: [
309
- /* @__PURE__ */ S(p, { className: "h-6 w-6 text-status-error-text mx-auto" }),
310
- /* @__PURE__ */ S("p", {
341
+ children: /* @__PURE__ */ w("div", { children: [
342
+ /* @__PURE__ */ C(p, { className: "h-6 w-6 text-status-error-text mx-auto" }),
343
+ /* @__PURE__ */ C("p", {
311
344
  className: "text-sm font-medium text-text-primary mt-2",
312
- children: a("plan.iframeErrorTitle", "Could not load plannotator.")
345
+ children: o("plan.iframeErrorTitle", "Could not load plannotator.")
313
346
  }),
314
- /* @__PURE__ */ S("p", {
347
+ /* @__PURE__ */ C("p", {
315
348
  className: "text-xs text-text-secondary mt-1",
316
- children: a("plan.iframeErrorDesc", "The agent reverse proxy returned an error. Check the agent status and try again.")
349
+ children: o("plan.iframeErrorDesc", "The agent reverse proxy returned an error. Check the agent status and try again.")
317
350
  }),
318
- /* @__PURE__ */ C("button", {
319
- onClick: T,
351
+ /* @__PURE__ */ w("button", {
352
+ onClick: j,
320
353
  className: "mt-3 inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm text-action-primary-text hover:bg-action-primary-bgHover",
321
354
  children: [
322
- /* @__PURE__ */ S(_, { className: "h-3.5 w-3.5" }),
355
+ /* @__PURE__ */ C(v, { className: "h-3.5 w-3.5" }),
323
356
  " ",
324
- a("plan.retry", "Retry")
357
+ o("plan.retry", "Retry")
325
358
  ]
326
359
  })
327
360
  ] })
@@ -331,6 +364,6 @@ function O({ agent: e, agents: t, agentApiKey: n, tunnelUrl: r, onReconfigure: i
331
364
  });
332
365
  }
333
366
  //#endregion
334
- export { w as AIPlanPanel };
367
+ export { T as AIPlanPanel };
335
368
 
336
369
  //# sourceMappingURL=AIPlanPanel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AIPlanPanel.js","names":[],"sources":["../../../src/components/ai/AIPlanPanel.tsx"],"sourcesContent":["/**\n * AIPlanPanel — the body of /ai-tools/plan.\n *\n * Five UI states gated in order:\n * 1. installing-plugin → vibe-plugin-plan + vibe-plugin-plan-plannotator\n * not installed on the agent. Renders the PluginGate install card.\n * 2. installing-cli → plugins installed but plannotator binary\n * missing on disk. Renders an Install CLI CTA.\n * 3. wizard → CLI installed but no AI-agent hook configured\n * yet. Renders the Claude/OpenCode/Codex/Pi/Gemini wizard.\n * 4. ready → all prereqs satisfied. Renders the tunnel\n * iframe pointing at ${tunnelUrl}/plan/, with postMessage auth\n * handoff on iframe load (mirrors PluginUIPanel.tsx).\n * 5. error → unrecoverable load failure for the status\n * query. Renders an error banner with retry.\n *\n * Auth handoff (matches PluginUIPanel.tsx:194–210):\n * onLoad → iframe.contentWindow.postMessage({type:'vibe-auth', apiKey},\n * new URL(tunnelUrl).origin)\n *\n * Surface contracts:\n * GET /api/plan-plannotator/status → PlannotatorStatus\n * POST /api/plan-plannotator/install → install binary\n * GET /api/plan-plannotator/agents/supported → AgentDetectionResult[]\n * POST /api/plan-plannotator/agents/:agent/configure-hook\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n AlertCircle,\n CheckCircle2,\n Loader2,\n PlayCircle,\n RefreshCcw,\n Settings,\n Wifi,\n WifiOff,\n} from 'lucide-react';\n\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PluginGate } from '@/components/shared';\nimport { PLAN_META_PACKAGE, PLAN_PLANNOTATOR_PACKAGE } from '@/constants/pluginCatalog';\nimport {\n configureAgentHook,\n getPlannotatorStatus,\n installPlannotatorBinary,\n listSupportedAgents,\n type AgentDetectionResult,\n type PlannotatorStatus,\n} from '@/services/planApi';\nimport type { AgentRef } from '@/services/aiApi';\n\ninterface AIPlanPanelProps {\n agent: AgentRef;\n agentApiKey: string | null;\n tunnelUrl: string | null;\n}\n\ntype ConnectionStatus = 'connecting' | 'connected' | 'error';\n\nexport function AIPlanPanel({ agent, agentApiKey, tunnelUrl }: AIPlanPanelProps) {\n const { t } = useI18n();\n // PluginGate renders eyebrow/title/description verbatim (no i18n lookup of\n // its own), so resolve them here with English fallbacks until plan.* keys exist.\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n return (\n <PluginGate\n agentId={agent.id}\n required={[PLAN_META_PACKAGE, PLAN_PLANNOTATOR_PACKAGE]}\n mode=\"all\"\n eyebrow={tr('plan.pluginRequiredEyebrow', 'Plan plugin required')}\n title={tr('plan.installPlanProviderTitle', 'Install the plan provider')}\n description={tr(\n 'plan.installPlanProviderDesc',\n 'Plan mode routes plan sessions through a plan provider plugin on your agent. Install the orchestrator and a provider (Plannotator) below to continue.'\n )}\n >\n <PlanPanelInner agent={agent} agentApiKey={agentApiKey} tunnelUrl={tunnelUrl} />\n </PluginGate>\n );\n}\n\nfunction PlanPanelInner({ agent, agentApiKey, tunnelUrl }: AIPlanPanelProps) {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const [status, setStatus] = useState<PlannotatorStatus | null>(null);\n const [statusError, setStatusError] = useState<string | null>(null);\n const [statusLoading, setStatusLoading] = useState(true);\n const [agents, setAgents] = useState<AgentDetectionResult[]>([]);\n const [installing, setInstalling] = useState(false);\n const [configuring, setConfiguring] = useState<string | null>(null);\n const [actionMessage, setActionMessage] = useState<string | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n\n const refresh = useCallback(async () => {\n setStatusLoading(true);\n setStatusError(null);\n try {\n const [statusResult, agentsResult] = await Promise.all([\n getPlannotatorStatus(agent),\n listSupportedAgents(agent).catch(() => ({ agents: [] })),\n ]);\n setStatus(statusResult);\n setAgents(agentsResult.agents ?? []);\n } catch (err) {\n setStatusError(err instanceof Error ? err.message : 'Failed to load plan status');\n } finally {\n setStatusLoading(false);\n }\n }, [agent]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n const handleInstallCli = useCallback(async () => {\n setInstalling(true);\n setActionError(null);\n setActionMessage(null);\n try {\n const result = await installPlannotatorBinary(agent);\n if (result.ok) {\n setActionMessage(tr('plan.cliInstalled', 'Plannotator CLI installed.'));\n await refresh();\n } else {\n setActionError(result.error ?? 'Install failed');\n }\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Install failed');\n } finally {\n setInstalling(false);\n }\n }, [agent, refresh, tr]);\n\n const handleConfigureHook = useCallback(\n async (agentKind: AgentDetectionResult['agent']) => {\n setConfiguring(agentKind);\n setActionError(null);\n setActionMessage(null);\n try {\n const result = await configureAgentHook(agent, agentKind);\n if (result.ok) {\n setActionMessage(tr('plan.hookConfigured', `Hook configured for ${agentKind}.`));\n await refresh();\n } else {\n setActionError(result.error ?? 'Hook configure failed');\n }\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Hook configure failed');\n } finally {\n setConfiguring(null);\n }\n },\n [agent, refresh, tr]\n );\n\n // Derived render state — guarded ordering means we always render the\n // most prerequisite-bound CTA first.\n const view = useMemo<'loading' | 'status-error' | 'install-cli' | 'wizard' | 'ready'>(() => {\n if (statusLoading) return 'loading';\n if (statusError) return 'status-error';\n if (!status?.installed) return 'install-cli';\n const anyConfigured = agents.some((a) => a.hookConfigured);\n if (!anyConfigured) return 'wizard';\n return 'ready';\n }, [status, statusError, statusLoading, agents]);\n\n if (view === 'loading') {\n return (\n <div className=\"flex items-center gap-2 p-6 text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n <span>{tr('plan.loadingStatus', 'Loading plan status…')}</span>\n </div>\n );\n }\n\n if (view === 'status-error') {\n return (\n <div className=\"rounded-md border border-status-error-border bg-status-error-bg p-4 text-sm text-status-error-text\">\n <div className=\"flex items-start gap-2\">\n <AlertCircle className=\"h-4 w-4 mt-0.5 shrink-0\" />\n <div className=\"flex-1\">\n <p className=\"font-medium\">\n {tr('plan.statusError', 'Could not load plan status from the agent.')}\n </p>\n <p className=\"text-xs opacity-80 mt-1\">{statusError}</p>\n <button\n onClick={() => void refresh()}\n className=\"mt-3 inline-flex items-center gap-1 text-xs font-medium hover:underline\"\n >\n <RefreshCcw className=\"h-3 w-3\" /> {tr('plan.retry', 'Retry')}\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4\">\n {actionMessage && (\n <div\n role=\"status\"\n className=\"rounded-md border border-status-success-border bg-status-success-bg px-3 py-2 text-xs text-status-success-text\"\n >\n {actionMessage}\n </div>\n )}\n {actionError && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text\"\n >\n {actionError}\n </div>\n )}\n\n {view === 'install-cli' && (\n <InstallCliCard installing={installing} onInstall={handleInstallCli} tr={tr} />\n )}\n\n {view === 'wizard' && (\n <WizardCard\n agents={agents}\n configuring={configuring}\n onConfigure={handleConfigureHook}\n tr={tr}\n />\n )}\n\n {view === 'ready' && (\n <PlanReadyPanel\n agent={agent}\n agents={agents}\n agentApiKey={agentApiKey}\n tunnelUrl={tunnelUrl}\n onReconfigure={() => setActionMessage(null)}\n tr={tr}\n />\n )}\n </div>\n );\n}\n\ninterface InstallCliCardProps {\n installing: boolean;\n onInstall: () => void;\n tr: (key: string, fallback: string) => string;\n}\n\nfunction InstallCliCard({ installing, onInstall, tr }: InstallCliCardProps) {\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-elevated p-6\">\n <div className=\"flex items-start gap-3\">\n <PlayCircle className=\"h-6 w-6 text-action-primary-bg shrink-0\" />\n <div className=\"flex-1\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr('plan.installCliTitle', 'Install the plannotator CLI')}\n </h3>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'plan.installCliDesc',\n 'Plan mode runs a local plannotator binary on the agent and proxies its UI back here. The plugin installs the binary into ~/.local/bin/plannotator (sha256-pinned). Roughly 30 seconds.'\n )}\n </p>\n <button\n onClick={onInstall}\n disabled={installing}\n className=\"mt-4 inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {installing ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {tr('plan.installing', 'Installing…')}\n </>\n ) : (\n tr('plan.installCliCta', 'Install plannotator CLI')\n )}\n </button>\n </div>\n </div>\n </div>\n );\n}\n\ninterface WizardCardProps {\n agents: AgentDetectionResult[];\n configuring: string | null;\n onConfigure: (agentKind: AgentDetectionResult['agent']) => void;\n tr: (key: string, fallback: string) => string;\n}\n\nfunction WizardCard({ agents, configuring, onConfigure, tr }: WizardCardProps) {\n const installed = agents.filter((a) => a.cliInstalled);\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-elevated p-6\">\n <div className=\"flex items-start gap-3\">\n <Settings className=\"h-6 w-6 text-action-primary-bg shrink-0\" />\n <div className=\"flex-1\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr('plan.wizardTitle', 'Connect an AI coding agent')}\n </h3>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'plan.wizardDesc',\n 'Configure a plannotator hook for at least one installed AI coding agent. The hook forwards plan content from the agent to plannotator on every plan-finalisation step.'\n )}\n </p>\n {installed.length === 0 && (\n <p className=\"mt-4 text-sm text-status-warning-text\">\n {tr(\n 'plan.wizardNoAgents',\n 'No AI coding agents detected in this sandbox. Install one (Claude Code, OpenCode, Codex, Pi, or Gemini) and refresh.'\n )}\n </p>\n )}\n <ul className=\"mt-4 space-y-2\">\n {agents.map((entry) => (\n <li\n key={entry.agent}\n className=\"flex items-start gap-3 p-3 border border-border-default rounded-md bg-bg-surface\"\n >\n {entry.hookConfigured ? (\n <CheckCircle2 className=\"h-5 w-5 text-status-success-text shrink-0 mt-0.5\" />\n ) : (\n <div className=\"h-5 w-5 rounded-full border-2 border-border-default shrink-0 mt-0.5\" />\n )}\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">{entry.name}</p>\n <p className=\"text-xs text-text-secondary mt-0.5\">{entry.instructions}</p>\n {!entry.cliInstalled && (\n <p className=\"text-xs text-status-warning-text mt-1\">\n {tr('plan.agentCliMissing', 'CLI not detected on this sandbox.')}\n </p>\n )}\n </div>\n <button\n disabled={\n !entry.cliInstalled || entry.hookConfigured || configuring === entry.agent\n }\n onClick={() => onConfigure(entry.agent)}\n className=\"px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {entry.hookConfigured\n ? tr('plan.hookConfiguredLabel', 'Configured')\n : configuring === entry.agent\n ? tr('plan.configuring', 'Configuring…')\n : tr('plan.configureHook', 'Configure hook')}\n </button>\n </li>\n ))}\n </ul>\n </div>\n </div>\n </div>\n );\n}\n\ninterface PlanReadyPanelProps {\n agent: AgentRef;\n agents: AgentDetectionResult[];\n agentApiKey: string | null;\n tunnelUrl: string | null;\n onReconfigure: () => void;\n tr: (key: string, fallback: string) => string;\n}\n\nfunction PlanReadyPanel({\n agent,\n agents,\n agentApiKey,\n tunnelUrl,\n onReconfigure,\n tr,\n}: PlanReadyPanelProps) {\n const iframeRef = useRef<HTMLIFrameElement | null>(null);\n const [connection, setConnection] = useState<ConnectionStatus>('connecting');\n\n const iframeSrc = useMemo(() => {\n if (!tunnelUrl) return null;\n const base = tunnelUrl.replace(/\\/$/, '');\n return new URL(`${base}/plan/`).toString();\n }, [tunnelUrl]);\n\n const sendAuth = useCallback(() => {\n const iframe = iframeRef.current;\n if (!iframe?.contentWindow || !agentApiKey || !tunnelUrl) return;\n try {\n const targetOrigin = new URL(tunnelUrl).origin;\n iframe.contentWindow.postMessage({ type: 'vibe-auth', apiKey: agentApiKey }, targetOrigin);\n } catch {\n // Origin parsing can throw on malformed tunnelUrl — ignored.\n }\n }, [agentApiKey, tunnelUrl]);\n\n const handleLoad = useCallback(() => {\n setConnection('connected');\n sendAuth();\n }, [sendAuth]);\n\n const handleError = useCallback(() => {\n setConnection('error');\n }, []);\n\n const handleRetry = useCallback(() => {\n const iframe = iframeRef.current;\n if (!iframe) return;\n setConnection('connecting');\n const src = iframe.src;\n iframe.src = 'about:blank';\n requestAnimationFrame(() => {\n iframe.src = src;\n });\n }, []);\n\n // Used elsewhere for analytics / breadcrumbs.\n void agent;\n\n if (!tunnelUrl || !agentApiKey) {\n return (\n <div className=\"rounded-lg border border-status-warning-border bg-status-warning-bg p-4 text-sm text-status-warning-text\">\n {tr(\n 'plan.tunnelMissing',\n 'Agent tunnel URL or API key is missing. Bring the agent online and refresh.'\n )}\n </div>\n );\n }\n\n const configuredAgents = agents.filter((a) => a.hookConfigured);\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1\">\n {connection === 'connected' && <Wifi className=\"h-3.5 w-3.5 text-status-success-text\" />}\n {connection === 'connecting' && (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin text-status-warning-text\" />\n )}\n {connection === 'error' && <WifiOff className=\"h-3.5 w-3.5 text-status-error-text\" />}\n {tr(`plan.connection.${connection}`, connection)}\n </span>\n <span aria-hidden=\"true\">·</span>\n <span>{tr('plan.hookCount', `${configuredAgents.length} hook(s) configured`)}</span>\n <button\n onClick={onReconfigure}\n className=\"ml-auto inline-flex items-center gap-1 hover:text-text-primary\"\n >\n <Settings className=\"h-3.5 w-3.5\" />\n {tr('plan.reopenWizard', 'Manage hooks')}\n </button>\n </div>\n\n <div className=\"border border-border-default rounded-lg overflow-hidden bg-bg-sunken relative h-[calc(100vh-320px)] min-h-[500px]\">\n {iframeSrc && (\n <iframe\n ref={iframeRef}\n src={iframeSrc}\n title={tr('plan.iframeTitle', 'Plannotator plan workspace')}\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read; clipboard-write\"\n sandbox=\"allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation-by-user-activation\"\n onLoad={handleLoad}\n onError={handleError}\n data-testid=\"plan-iframe\"\n />\n )}\n {connection === 'connecting' && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-bg-sunken/80 pointer-events-none\">\n <Loader2 className=\"h-6 w-6 animate-spin text-action-primary-bg\" />\n </div>\n )}\n {connection === 'error' && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-bg-sunken p-6 text-center\">\n <div>\n <AlertCircle className=\"h-6 w-6 text-status-error-text mx-auto\" />\n <p className=\"text-sm font-medium text-text-primary mt-2\">\n {tr('plan.iframeErrorTitle', 'Could not load plannotator.')}\n </p>\n <p className=\"text-xs text-text-secondary mt-1\">\n {tr(\n 'plan.iframeErrorDesc',\n 'The agent reverse proxy returned an error. Check the agent status and try again.'\n )}\n </p>\n <button\n onClick={handleRetry}\n className=\"mt-3 inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm text-action-primary-text hover:bg-action-primary-bgHover\"\n >\n <RefreshCcw className=\"h-3.5 w-3.5\" /> {tr('plan.retry', 'Retry')}\n </button>\n </div>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AIPlanPanel.js","names":[],"sources":["../../../src/components/ai/AIPlanPanel.tsx"],"sourcesContent":["/**\n * AIPlanPanel — the body of /ai-tools/plan.\n *\n * Five UI states gated in order:\n * 1. installing-plugin → vibe-plugin-plan + vibe-plugin-plan-plannotator\n * not installed on the agent. Renders the PluginGate install card.\n * 2. installing-cli → plugins installed but plannotator binary\n * missing on disk. Renders an Install CLI CTA.\n * 3. wizard → CLI installed but no AI-agent hook configured\n * yet. Renders the Claude/OpenCode/Codex/Pi/Gemini wizard.\n * 4. ready → all prereqs satisfied. Renders the tunnel\n * iframe pointing at ${tunnelUrl}/plan/, with postMessage auth\n * handoff on iframe load (mirrors PluginUIPanel.tsx).\n * 5. error → unrecoverable load failure for the status\n * query. Renders an error banner with retry.\n *\n * Auth handoff (matches PluginUIPanel.tsx:194–210):\n * onLoad → iframe.contentWindow.postMessage({type:'vibe-auth', apiKey},\n * new URL(tunnelUrl).origin)\n *\n * Surface contracts:\n * GET /api/plan-plannotator/status → PlannotatorStatus\n * POST /api/plan-plannotator/install → install binary\n * GET /api/plan-plannotator/agents/supported → AgentDetectionResult[]\n * POST /api/plan-plannotator/agents/:agent/configure-hook\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n AlertCircle,\n CheckCircle2,\n Inbox,\n Loader2,\n PlayCircle,\n RefreshCcw,\n Settings,\n Wifi,\n WifiOff,\n} from 'lucide-react';\n\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PluginGate } from '@/components/shared';\nimport { PLAN_META_PACKAGE, PLAN_PLANNOTATOR_PACKAGE } from '@/constants/pluginCatalog';\nimport {\n configureAgentHook,\n getPlannotatorStatus,\n installPlannotatorBinary,\n listSupportedAgents,\n type AgentDetectionResult,\n type PlannotatorStatus,\n} from '@/services/planApi';\nimport type { AgentRef } from '@/services/aiApi';\n\ninterface AIPlanPanelProps {\n agent: AgentRef;\n agentApiKey: string | null;\n tunnelUrl: string | null;\n}\n\ntype ConnectionStatus = 'connecting' | 'connected' | 'error';\n\nexport function AIPlanPanel({ agent, agentApiKey, tunnelUrl }: AIPlanPanelProps) {\n const { t } = useI18n();\n // PluginGate renders eyebrow/title/description verbatim (no i18n lookup of\n // its own), so resolve them here with English fallbacks until plan.* keys exist.\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n return (\n <PluginGate\n agentId={agent.id}\n required={[PLAN_META_PACKAGE, PLAN_PLANNOTATOR_PACKAGE]}\n mode=\"all\"\n eyebrow={tr('plan.pluginRequiredEyebrow', 'Plan plugin required')}\n title={tr('plan.installPlanProviderTitle', 'Install the plan provider')}\n description={tr(\n 'plan.installPlanProviderDesc',\n 'Plan mode routes plan sessions through a plan provider plugin on your agent. Install the orchestrator and a provider (Plannotator) below to continue.'\n )}\n >\n <PlanPanelInner agent={agent} agentApiKey={agentApiKey} tunnelUrl={tunnelUrl} />\n </PluginGate>\n );\n}\n\nfunction PlanPanelInner({ agent, agentApiKey, tunnelUrl }: AIPlanPanelProps) {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const [status, setStatus] = useState<PlannotatorStatus | null>(null);\n const [statusError, setStatusError] = useState<string | null>(null);\n const [statusLoading, setStatusLoading] = useState(true);\n const [agents, setAgents] = useState<AgentDetectionResult[]>([]);\n const [installing, setInstalling] = useState(false);\n const [configuring, setConfiguring] = useState<string | null>(null);\n const [actionMessage, setActionMessage] = useState<string | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n\n const refresh = useCallback(\n // `silent` background refreshes (polling) only update session status: they\n // skip the loading/error toggles AND the agent re-detection, so a transient\n // listSupportedAgents failure can't demote the ready view to the wizard.\n async (silent = false) => {\n if (!silent) {\n setStatusLoading(true);\n setStatusError(null);\n }\n try {\n if (silent) {\n setStatus(await getPlannotatorStatus(agent));\n return;\n }\n const [statusResult, agentsResult] = await Promise.all([\n getPlannotatorStatus(agent),\n listSupportedAgents(agent).catch(() => ({ agents: [] })),\n ]);\n setStatus(statusResult);\n setAgents(agentsResult.agents ?? []);\n } catch (err) {\n if (!silent) {\n setStatusError(err instanceof Error ? err.message : 'Failed to load plan status');\n }\n } finally {\n if (!silent) setStatusLoading(false);\n }\n },\n [agent]\n );\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n const handleInstallCli = useCallback(async () => {\n setInstalling(true);\n setActionError(null);\n setActionMessage(null);\n try {\n const result = await installPlannotatorBinary(agent);\n if (result.ok) {\n setActionMessage(tr('plan.cliInstalled', 'Plannotator CLI installed.'));\n await refresh();\n } else {\n setActionError(result.error ?? 'Install failed');\n }\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Install failed');\n } finally {\n setInstalling(false);\n }\n }, [agent, refresh, tr]);\n\n const handleConfigureHook = useCallback(\n async (agentKind: AgentDetectionResult['agent']) => {\n setConfiguring(agentKind);\n setActionError(null);\n setActionMessage(null);\n try {\n const result = await configureAgentHook(agent, agentKind);\n if (result.ok) {\n setActionMessage(tr('plan.hookConfigured', `Hook configured for ${agentKind}.`));\n await refresh();\n } else {\n setActionError(result.error ?? 'Hook configure failed');\n }\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Hook configure failed');\n } finally {\n setConfiguring(null);\n }\n },\n [agent, refresh, tr]\n );\n\n // Derived render state — guarded ordering means we always render the\n // most prerequisite-bound CTA first.\n const view = useMemo<'loading' | 'status-error' | 'install-cli' | 'wizard' | 'ready'>(() => {\n if (statusLoading) return 'loading';\n if (statusError) return 'status-error';\n if (!status?.installed) return 'install-cli';\n const anyConfigured = agents.some((a) => a.hookConfigured);\n if (!anyConfigured) return 'wizard';\n return 'ready';\n }, [status, statusError, statusLoading, agents]);\n\n // While the ready panel is shown, poll status in the background so a plan\n // session created by a coding agent's hook (or one that ends) is reflected\n // without a manual reload — `runningSessions` gates the iframe vs the empty\n // state below.\n useEffect(() => {\n if (view !== 'ready') return;\n // Skip ticks while the tab is hidden — no point polling a panel nobody sees.\n const id = setInterval(() => {\n if (!document.hidden) void refresh(true);\n }, 5000);\n return () => clearInterval(id);\n }, [view, refresh]);\n\n if (view === 'loading') {\n return (\n <div className=\"flex items-center gap-2 p-6 text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n <span>{tr('plan.loadingStatus', 'Loading plan status…')}</span>\n </div>\n );\n }\n\n if (view === 'status-error') {\n return (\n <div className=\"rounded-md border border-status-error-border bg-status-error-bg p-4 text-sm text-status-error-text\">\n <div className=\"flex items-start gap-2\">\n <AlertCircle className=\"h-4 w-4 mt-0.5 shrink-0\" />\n <div className=\"flex-1\">\n <p className=\"font-medium\">\n {tr('plan.statusError', 'Could not load plan status from the agent.')}\n </p>\n <p className=\"text-xs opacity-80 mt-1\">{statusError}</p>\n <button\n onClick={() => void refresh()}\n className=\"mt-3 inline-flex items-center gap-1 text-xs font-medium hover:underline\"\n >\n <RefreshCcw className=\"h-3 w-3\" /> {tr('plan.retry', 'Retry')}\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4\">\n {actionMessage && (\n <div\n role=\"status\"\n className=\"rounded-md border border-status-success-border bg-status-success-bg px-3 py-2 text-xs text-status-success-text\"\n >\n {actionMessage}\n </div>\n )}\n {actionError && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text\"\n >\n {actionError}\n </div>\n )}\n\n {view === 'install-cli' && (\n <InstallCliCard installing={installing} onInstall={handleInstallCli} tr={tr} />\n )}\n\n {view === 'wizard' && (\n <WizardCard\n agents={agents}\n configuring={configuring}\n onConfigure={handleConfigureHook}\n tr={tr}\n />\n )}\n\n {view === 'ready' && (\n <PlanReadyPanel\n agent={agent}\n agents={agents}\n agentApiKey={agentApiKey}\n tunnelUrl={tunnelUrl}\n runningSessions={status?.runningSessions ?? []}\n onReconfigure={() => setActionMessage(null)}\n tr={tr}\n />\n )}\n </div>\n );\n}\n\ninterface InstallCliCardProps {\n installing: boolean;\n onInstall: () => void;\n tr: (key: string, fallback: string) => string;\n}\n\nfunction InstallCliCard({ installing, onInstall, tr }: InstallCliCardProps) {\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-elevated p-6\">\n <div className=\"flex items-start gap-3\">\n <PlayCircle className=\"h-6 w-6 text-action-primary-bg shrink-0\" />\n <div className=\"flex-1\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr('plan.installCliTitle', 'Install the plannotator CLI')}\n </h3>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'plan.installCliDesc',\n 'Plan mode runs a local plannotator binary on the agent and proxies its UI back here. The plugin installs the binary into ~/.local/bin/plannotator (sha256-pinned). Roughly 30 seconds.'\n )}\n </p>\n <button\n onClick={onInstall}\n disabled={installing}\n className=\"mt-4 inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {installing ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {tr('plan.installing', 'Installing…')}\n </>\n ) : (\n tr('plan.installCliCta', 'Install plannotator CLI')\n )}\n </button>\n </div>\n </div>\n </div>\n );\n}\n\ninterface WizardCardProps {\n agents: AgentDetectionResult[];\n configuring: string | null;\n onConfigure: (agentKind: AgentDetectionResult['agent']) => void;\n tr: (key: string, fallback: string) => string;\n}\n\nfunction WizardCard({ agents, configuring, onConfigure, tr }: WizardCardProps) {\n const installed = agents.filter((a) => a.cliInstalled);\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-elevated p-6\">\n <div className=\"flex items-start gap-3\">\n <Settings className=\"h-6 w-6 text-action-primary-bg shrink-0\" />\n <div className=\"flex-1\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr('plan.wizardTitle', 'Connect an AI coding agent')}\n </h3>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'plan.wizardDesc',\n 'Configure a plannotator hook for at least one installed AI coding agent. The hook forwards plan content from the agent to plannotator on every plan-finalisation step.'\n )}\n </p>\n {installed.length === 0 && (\n <p className=\"mt-4 text-sm text-status-warning-text\">\n {tr(\n 'plan.wizardNoAgents',\n 'No AI coding agents detected in this sandbox. Install one (Claude Code, OpenCode, Codex, Pi, or Gemini) and refresh.'\n )}\n </p>\n )}\n <ul className=\"mt-4 space-y-2\">\n {agents.map((entry) => (\n <li\n key={entry.agent}\n className=\"flex items-start gap-3 p-3 border border-border-default rounded-md bg-bg-surface\"\n >\n {entry.hookConfigured ? (\n <CheckCircle2 className=\"h-5 w-5 text-status-success-text shrink-0 mt-0.5\" />\n ) : (\n <div className=\"h-5 w-5 rounded-full border-2 border-border-default shrink-0 mt-0.5\" />\n )}\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">{entry.name}</p>\n <p className=\"text-xs text-text-secondary mt-0.5\">{entry.instructions}</p>\n {!entry.cliInstalled && (\n <p className=\"text-xs text-status-warning-text mt-1\">\n {tr('plan.agentCliMissing', 'CLI not detected on this sandbox.')}\n </p>\n )}\n </div>\n <button\n disabled={\n !entry.cliInstalled || entry.hookConfigured || configuring === entry.agent\n }\n onClick={() => onConfigure(entry.agent)}\n className=\"px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {entry.hookConfigured\n ? tr('plan.hookConfiguredLabel', 'Configured')\n : configuring === entry.agent\n ? tr('plan.configuring', 'Configuring…')\n : tr('plan.configureHook', 'Configure hook')}\n </button>\n </li>\n ))}\n </ul>\n </div>\n </div>\n </div>\n );\n}\n\ninterface PlanReadyPanelProps {\n agent: AgentRef;\n agents: AgentDetectionResult[];\n agentApiKey: string | null;\n tunnelUrl: string | null;\n runningSessions: PlannotatorStatus['runningSessions'];\n onReconfigure: () => void;\n tr: (key: string, fallback: string) => string;\n}\n\nfunction PlanReadyPanel({\n agent,\n agents,\n agentApiKey,\n tunnelUrl,\n runningSessions,\n onReconfigure,\n tr,\n}: PlanReadyPanelProps) {\n const iframeRef = useRef<HTMLIFrameElement | null>(null);\n const [connection, setConnection] = useState<ConnectionStatus>('connecting');\n\n // plannotator is session-scoped — it only serves a plan at /plan/<sessionId>/.\n // A session exists once a connected coding agent finalises a plan (its hook\n // POSTs to the agent). With none running there is nothing to embed (the bare\n // /plan/ root 404s), so we show an empty state instead. The status endpoint\n // doesn't guarantee ordering, so pick the most recently started session.\n const activeSession = useMemo(\n () =>\n runningSessions.length > 0\n ? // `|| 0` keeps the sort deterministic if a startedAt is malformed (Date.parse → NaN).\n [...runningSessions].sort(\n (a, b) => (Date.parse(b.startedAt) || 0) - (Date.parse(a.startedAt) || 0)\n )[0]\n : null,\n [runningSessions]\n );\n const activeSessionId = activeSession?.sessionId ?? null;\n\n const iframeSrc = useMemo(() => {\n if (!tunnelUrl || !agentApiKey || !activeSessionId) return null;\n const base = tunnelUrl.replace(/\\/$/, '');\n // The iframe's initial document GET can't carry an auth header; the proxy\n // accepts ?apiKey= for exactly this case and then sets a scoped session\n // cookie for subsequent requests.\n const url = new URL(`${base}/plan/${activeSessionId}/`);\n url.searchParams.set('apiKey', agentApiKey);\n return url.toString();\n }, [tunnelUrl, agentApiKey, activeSessionId]);\n\n // Reset the connection indicator when the embedded session changes so a\n // prior session's 'error'/'connected' state doesn't bleed into the next one.\n useEffect(() => {\n setConnection('connecting');\n }, [activeSessionId]);\n\n const sendAuth = useCallback(() => {\n const iframe = iframeRef.current;\n if (!iframe?.contentWindow || !agentApiKey || !tunnelUrl) return;\n try {\n const targetOrigin = new URL(tunnelUrl).origin;\n iframe.contentWindow.postMessage({ type: 'vibe-auth', apiKey: agentApiKey }, targetOrigin);\n } catch {\n // Origin parsing can throw on malformed tunnelUrl — ignored.\n }\n }, [agentApiKey, tunnelUrl]);\n\n const handleLoad = useCallback(() => {\n setConnection('connected');\n sendAuth();\n }, [sendAuth]);\n\n const handleError = useCallback(() => {\n setConnection('error');\n }, []);\n\n const handleRetry = useCallback(() => {\n const iframe = iframeRef.current;\n if (!iframe) return;\n setConnection('connecting');\n const src = iframe.src;\n iframe.src = 'about:blank';\n requestAnimationFrame(() => {\n iframe.src = src;\n });\n }, []);\n\n // Used elsewhere for analytics / breadcrumbs.\n void agent;\n\n if (!tunnelUrl || !agentApiKey) {\n return (\n <div className=\"rounded-lg border border-status-warning-border bg-status-warning-bg p-4 text-sm text-status-warning-text\">\n {tr(\n 'plan.tunnelMissing',\n 'Agent tunnel URL or API key is missing. Bring the agent online and refresh.'\n )}\n </div>\n );\n }\n\n const configuredAgents = agents.filter((a) => a.hookConfigured);\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2 text-xs text-text-secondary\">\n {activeSession && (\n <>\n <span className=\"inline-flex items-center gap-1\">\n {connection === 'connected' && (\n <Wifi className=\"h-3.5 w-3.5 text-status-success-text\" />\n )}\n {connection === 'connecting' && (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin text-status-warning-text\" />\n )}\n {connection === 'error' && <WifiOff className=\"h-3.5 w-3.5 text-status-error-text\" />}\n {tr(`plan.connection.${connection}`, connection)}\n </span>\n <span aria-hidden=\"true\">·</span>\n </>\n )}\n <span>{tr('plan.hookCount', `${configuredAgents.length} hook(s) configured`)}</span>\n <button\n onClick={onReconfigure}\n className=\"ml-auto inline-flex items-center gap-1 hover:text-text-primary\"\n >\n <Settings className=\"h-3.5 w-3.5\" />\n {tr('plan.reopenWizard', 'Manage hooks')}\n </button>\n </div>\n\n <div className=\"border border-border-default rounded-lg overflow-hidden bg-bg-sunken relative h-[calc(100vh-320px)] min-h-[500px]\">\n {!activeSession && (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center gap-2 p-6 text-center\">\n <Inbox className=\"h-8 w-8 text-text-muted\" />\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('plan.noSessionTitle', 'No active plan session')}\n </p>\n <p className=\"max-w-md text-xs text-text-secondary\">\n {tr(\n 'plan.noSessionDesc',\n 'Start a plan in a connected coding agent — when it finalises a plan, the configured hook forwards it here for review.'\n )}\n </p>\n </div>\n )}\n {iframeSrc && (\n /* The src carries ?apiKey= for the initial auth (an iframe can't set\n request headers). Drop popups/top-navigation and send no referrer\n so that key can't leak out of the frame via a navigation or popup. */\n <iframe\n ref={iframeRef}\n src={iframeSrc}\n title={tr('plan.iframeTitle', 'Plannotator plan workspace')}\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read; clipboard-write\"\n referrerPolicy=\"no-referrer\"\n sandbox=\"allow-same-origin allow-scripts allow-forms\"\n onLoad={handleLoad}\n onError={handleError}\n data-testid=\"plan-iframe\"\n />\n )}\n {activeSession && connection === 'connecting' && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-bg-sunken/80 pointer-events-none\">\n <Loader2 className=\"h-6 w-6 animate-spin text-action-primary-bg\" />\n </div>\n )}\n {activeSession && connection === 'error' && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-bg-sunken p-6 text-center\">\n <div>\n <AlertCircle className=\"h-6 w-6 text-status-error-text mx-auto\" />\n <p className=\"text-sm font-medium text-text-primary mt-2\">\n {tr('plan.iframeErrorTitle', 'Could not load plannotator.')}\n </p>\n <p className=\"text-xs text-text-secondary mt-1\">\n {tr(\n 'plan.iframeErrorDesc',\n 'The agent reverse proxy returned an error. Check the agent status and try again.'\n )}\n </p>\n <button\n onClick={handleRetry}\n className=\"mt-3 inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm text-action-primary-text hover:bg-action-primary-bgHover\"\n >\n <RefreshCcw className=\"h-3.5 w-3.5\" /> {tr('plan.retry', 'Retry')}\n </button>\n </div>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AA6DA,SAAgB,EAAY,EAAE,UAAO,gBAAa,gBAA+B;CAC/E,IAAM,EAAE,SAAM,GAAS,EAGjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;;AAGpC,QACE,kBAAC,GAAD;EACE,SAAS,EAAM;EACf,UAAU,CAAC,GAAmB,EAAyB;EACvD,MAAK;EACL,SAAS,EAAG,8BAA8B,uBAAuB;EACjE,OAAO,EAAG,iCAAiC,4BAA4B;EACvE,aAAa,EACX,gCACA,wJACD;YAED,kBAAC,GAAD;GAAuB;GAAoB;GAAwB;GAAa,CAAA;EACrE,CAAA;;AAIjB,SAAS,EAAe,EAAE,UAAO,gBAAa,gBAA+B;CAC3E,IAAM,EAAE,SAAM,GAAS,EACjB,IAAK,GACR,GAAa,MAA6B;EACzC,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;IAEpC,CAAC,EAAE,CACJ,EAEK,CAAC,GAAQ,KAAa,EAAmC,KAAK,EAC9D,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAe,KAAoB,EAAS,GAAK,EAClD,CAAC,GAAQ,KAAa,EAAiC,EAAE,CAAC,EAC1D,CAAC,GAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAe,KAAoB,EAAwB,KAAK,EACjE,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAE7D,IAAU,EAId,OAAO,IAAS,OAAU;AACxB,EAAK,MACH,EAAiB,GAAK,EACtB,EAAe,KAAK;AAEtB,MAAI;AACF,OAAI,GAAQ;AACV,MAAU,MAAM,EAAqB,EAAM,CAAC;AAC5C;;GAEF,IAAM,CAAC,GAAc,KAAgB,MAAM,QAAQ,IAAI,CACrD,EAAqB,EAAM,EAC3B,EAAoB,EAAM,CAAC,aAAa,EAAE,QAAQ,EAAE,EAAE,EAAE,CACzD,CAAC;AAEF,GADA,EAAU,EAAa,EACvB,EAAU,EAAa,UAAU,EAAE,CAAC;WAC7B,GAAK;AACZ,GAAK,KACH,EAAe,aAAe,QAAQ,EAAI,UAAU,6BAA6B;YAE3E;AACR,GAAK,KAAQ,EAAiB,GAAM;;IAGxC,CAAC,EAAM,CACR;AAED,SAAgB;AACT,KAAS;IACb,CAAC,EAAQ,CAAC;CAEb,IAAM,IAAmB,EAAY,YAAY;AAG/C,EAFA,EAAc,GAAK,EACnB,EAAe,KAAK,EACpB,EAAiB,KAAK;AACtB,MAAI;GACF,IAAM,IAAS,MAAM,EAAyB,EAAM;AACpD,GAAI,EAAO,MACT,EAAiB,EAAG,qBAAqB,6BAA6B,CAAC,EACvE,MAAM,GAAS,IAEf,EAAe,EAAO,SAAS,iBAAiB;WAE3C,GAAK;AACZ,KAAe,aAAe,QAAQ,EAAI,UAAU,iBAAiB;YAC7D;AACR,KAAc,GAAM;;IAErB;EAAC;EAAO;EAAS;EAAG,CAAC,EAElB,IAAsB,EAC1B,OAAO,MAA6C;AAGlD,EAFA,EAAe,EAAU,EACzB,EAAe,KAAK,EACpB,EAAiB,KAAK;AACtB,MAAI;GACF,IAAM,IAAS,MAAM,EAAmB,GAAO,EAAU;AACzD,GAAI,EAAO,MACT,EAAiB,EAAG,uBAAuB,uBAAuB,EAAU,GAAG,CAAC,EAChF,MAAM,GAAS,IAEf,EAAe,EAAO,SAAS,wBAAwB;WAElD,GAAK;AACZ,KAAe,aAAe,QAAQ,EAAI,UAAU,wBAAwB;YACpE;AACR,KAAe,KAAK;;IAGxB;EAAC;EAAO;EAAS;EAAG,CACrB,EAIK,IAAO,QACP,IAAsB,YACtB,IAAoB,iBACnB,GAAQ,YACS,EAAO,MAAM,MAAM,EAAE,eAAe,GAEnD,UADoB,WAFI,eAI9B;EAAC;EAAQ;EAAa;EAAe;EAAO,CAAC;AA8ChD,QAxCA,QAAgB;AACd,MAAI,MAAS,QAAS;EAEtB,IAAM,IAAK,kBAAkB;AAC3B,GAAK,SAAS,UAAa,EAAQ,GAAK;KACvC,IAAK;AACR,eAAa,cAAc,EAAG;IAC7B,CAAC,GAAM,EAAQ,CAAC,EAEf,MAAS,YAET,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,EAC5C,kBAAC,QAAD,EAAA,UAAO,EAAG,sBAAsB,uBAAuB,EAAQ,CAAA,CAC3D;MAIN,MAAS,iBAET,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAa,WAAU,2BAA4B,CAAA,EACnD,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAG,oBAAoB,6CAA6C;MACnE,CAAA;KACJ,kBAAC,KAAD;MAAG,WAAU;gBAA2B;MAAgB,CAAA;KACxD,kBAAC,UAAD;MACE,eAAe,KAAK,GAAS;MAC7B,WAAU;gBAFZ;OAIE,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;;OAAE,EAAG,cAAc,QAAQ;OACtD;;KACL;MACF;;EACF,CAAA,GAKR,kBAAC,OAAD;EAAK,WAAU;YAAf;GACG,KACC,kBAAC,OAAD;IACE,MAAK;IACL,WAAU;cAET;IACG,CAAA;GAEP,KACC,kBAAC,OAAD;IACE,MAAK;IACL,WAAU;cAET;IACG,CAAA;GAGP,MAAS,iBACR,kBAAC,GAAD;IAA4B;IAAY,WAAW;IAAsB;IAAM,CAAA;GAGhF,MAAS,YACR,kBAAC,GAAD;IACU;IACK;IACb,aAAa;IACT;IACJ,CAAA;GAGH,MAAS,WACR,kBAAC,GAAD;IACS;IACC;IACK;IACF;IACX,iBAAiB,GAAQ,mBAAmB,EAAE;IAC9C,qBAAqB,EAAiB,KAAK;IACvC;IACJ,CAAA;GAEA;;;AAUV,SAAS,EAAe,EAAE,eAAY,cAAW,SAA2B;AAC1E,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAY,WAAU,2CAA4C,CAAA,EAClE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,wBAAwB,8BAA8B;MACvD,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,uBACA,yLACD;MACC,CAAA;KACJ,kBAAC,UAAD;MACE,SAAS;MACT,UAAU;MACV,WAAU;gBAET,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,EAC3C,EAAG,mBAAmB,cAAc,CACpC,EAAA,CAAA,GAEH,EAAG,sBAAsB,0BAA0B;MAE9C,CAAA;KACL;MACF;;EACF,CAAA;;AAWV,SAAS,EAAW,EAAE,WAAQ,gBAAa,gBAAa,SAAuB;CAC7E,IAAM,IAAY,EAAO,QAAQ,MAAM,EAAE,aAAa;AACtD,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAU,WAAU,2CAA4C,CAAA,EAChE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,oBAAoB,6BAA6B;MAClD,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,mBACA,yKACD;MACC,CAAA;KACH,EAAU,WAAW,KACpB,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,uBACA,uHACD;MACC,CAAA;KAEN,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAO,KAAK,MACX,kBAAC,MAAD;OAEE,WAAU;iBAFZ;QAIG,EAAM,iBACL,kBAAC,GAAD,EAAc,WAAU,oDAAqD,CAAA,GAE7E,kBAAC,OAAD,EAAK,WAAU,uEAAwE,CAAA;QAEzF,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,KAAD;WAAG,WAAU;qBAAyC,EAAM;WAAS,CAAA;UACrE,kBAAC,KAAD;WAAG,WAAU;qBAAsC,EAAM;WAAiB,CAAA;UACzE,CAAC,EAAM,gBACN,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAG,wBAAwB,oCAAoC;WAC9D,CAAA;UAEF;;QACN,kBAAC,UAAD;SACE,UACE,CAAC,EAAM,gBAAgB,EAAM,kBAAkB,MAAgB,EAAM;SAEvE,eAAe,EAAY,EAAM,MAAM;SACvC,WAAU;mBAET,EAAM,iBACH,EAAG,4BAA4B,aAAa,GAC5C,MAAgB,EAAM,QACpB,EAAG,oBAAoB,eAAe,GACtC,EAAG,sBAAsB,iBAAiB;SACzC,CAAA;QACN;SA9BE,EAAM,MA8BR,CACL;MACC,CAAA;KACD;MACF;;EACF,CAAA;;AAcV,SAAS,EAAe,EACtB,UACA,WACA,gBACA,cACA,oBACA,kBACA,SACsB;CACtB,IAAM,IAAY,EAAiC,KAAK,EAClD,CAAC,GAAY,KAAiB,EAA2B,aAAa,EAOtE,IAAgB,QAElB,EAAgB,SAAS,IAErB,CAAC,GAAG,EAAgB,CAAC,MAClB,GAAG,OAAO,KAAK,MAAM,EAAE,UAAU,IAAI,MAAM,KAAK,MAAM,EAAE,UAAU,IAAI,GACxE,CAAC,KACF,MACN,CAAC,EAAgB,CAClB,EACK,IAAkB,GAAe,aAAa,MAE9C,IAAY,QAAc;AAC9B,MAAI,CAAC,KAAa,CAAC,KAAe,CAAC,EAAiB,QAAO;EAC3D,IAAM,IAAO,EAAU,QAAQ,OAAO,GAAG,EAInC,IAAM,IAAI,IAAI,GAAG,EAAK,QAAQ,EAAgB,GAAG;AAEvD,SADA,EAAI,aAAa,IAAI,UAAU,EAAY,EACpC,EAAI,UAAU;IACpB;EAAC;EAAW;EAAa;EAAgB,CAAC;AAI7C,SAAgB;AACd,IAAc,aAAa;IAC1B,CAAC,EAAgB,CAAC;CAErB,IAAM,IAAW,QAAkB;EACjC,IAAM,IAAS,EAAU;AACrB,SAAC,GAAQ,iBAAiB,CAAC,KAAe,CAAC,GAC/C,KAAI;GACF,IAAM,IAAe,IAAI,IAAI,EAAU,CAAC;AACxC,KAAO,cAAc,YAAY;IAAE,MAAM;IAAa,QAAQ;IAAa,EAAE,EAAa;UACpF;IAGP,CAAC,GAAa,EAAU,CAAC,EAEtB,IAAa,QAAkB;AAEnC,EADA,EAAc,YAAY,EAC1B,GAAU;IACT,CAAC,EAAS,CAAC,EAER,IAAc,QAAkB;AACpC,IAAc,QAAQ;IACrB,EAAE,CAAC,EAEA,IAAc,QAAkB;EACpC,IAAM,IAAS,EAAU;AACzB,MAAI,CAAC,EAAQ;AACb,IAAc,aAAa;EAC3B,IAAM,IAAM,EAAO;AAEnB,EADA,EAAO,MAAM,eACb,4BAA4B;AAC1B,KAAO,MAAM;IACb;IACD,EAAE,CAAC;AAKN,KAAI,CAAC,KAAa,CAAC,EACjB,QACE,kBAAC,OAAD;EAAK,WAAU;YACZ,EACC,sBACA,8EACD;EACG,CAAA;CAIV,IAAM,IAAmB,EAAO,QAAQ,MAAM,EAAE,eAAe;AAE/D,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,KACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG,MAAe,eACd,kBAAC,GAAD,EAAM,WAAU,wCAAyC,CAAA;MAE1D,MAAe,gBACd,kBAAC,GAAD,EAAS,WAAU,qDAAsD,CAAA;MAE1E,MAAe,WAAW,kBAAC,GAAD,EAAS,WAAU,sCAAuC,CAAA;MACpF,EAAG,mBAAmB,KAAc,EAAW;MAC3C;QACP,kBAAC,QAAD;KAAM,eAAY;eAAO;KAAQ,CAAA,CAChC,EAAA,CAAA;IAEL,kBAAC,QAAD,EAAA,UAAO,EAAG,kBAAkB,GAAG,EAAiB,OAAO,qBAAqB,EAAQ,CAAA;IACpF,kBAAC,UAAD;KACE,SAAS;KACT,WAAU;eAFZ,CAIE,kBAAC,GAAD,EAAU,WAAU,eAAgB,CAAA,EACnC,EAAG,qBAAqB,eAAe,CACjC;;IACL;MAEN,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,CAAC,KACA,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD,EAAO,WAAU,2BAA4B,CAAA;MAC7C,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAG,uBAAuB,yBAAyB;OAClD,CAAA;MACJ,kBAAC,KAAD;OAAG,WAAU;iBACV,EACC,sBACA,wHACD;OACC,CAAA;MACA;;IAEP,KAIC,kBAAC,UAAD;KACE,KAAK;KACL,KAAK;KACL,OAAO,EAAG,oBAAoB,6BAA6B;KAC3D,WAAU;KACV,OAAM;KACN,gBAAe;KACf,SAAQ;KACR,QAAQ;KACR,SAAS;KACT,eAAY;KACZ,CAAA;IAEH,KAAiB,MAAe,gBAC/B,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAAS,WAAU,+CAAgD,CAAA;KAC/D,CAAA;IAEP,KAAiB,MAAe,WAC/B,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD,EAAA,UAAA;MACE,kBAAC,GAAD,EAAa,WAAU,0CAA2C,CAAA;MAClE,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAG,yBAAyB,8BAA8B;OACzD,CAAA;MACJ,kBAAC,KAAD;OAAG,WAAU;iBACV,EACC,wBACA,mFACD;OACC,CAAA;MACJ,kBAAC,UAAD;OACE,SAAS;OACT,WAAU;iBAFZ;QAIE,kBAAC,GAAD,EAAY,WAAU,eAAgB,CAAA;;QAAE,EAAG,cAAc,QAAQ;QAC1D;;MACL,EAAA,CAAA;KACF,CAAA;IAEJ;KACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-vibecontrols",
3
- "version": "2026.527.2",
3
+ "version": "2026.527.3",
4
4
  "description": "VibeControls microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [