@hansenexus/hud 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +253 -9
  2. package/dist/access-CNvJOwKM.d.ts +54 -0
  3. package/dist/capabilities-CjulXFQX.js +189 -0
  4. package/dist/define-hud-BBzsSl8b.d.ts +36 -0
  5. package/dist/env-DfxpPfs9.js +28 -0
  6. package/dist/env-vEjyJfPW.d.ts +17 -0
  7. package/dist/force-state-BaqWsmrf.js +153 -0
  8. package/dist/force-state-CCqoTfVB.d.ts +23 -0
  9. package/dist/handler-WmE4Lu7l.d.ts +39 -0
  10. package/dist/index-BYsdS-7d.d.ts +55 -0
  11. package/dist/index.d.ts +6 -39
  12. package/dist/index.js +980 -264
  13. package/dist/marker-C66LxOhJ.d.ts +9 -0
  14. package/dist/marker-CMrvxPmS.d.ts +12 -0
  15. package/dist/marker-DjeGvL-F.js +9 -0
  16. package/dist/marker-uZYPOJ4M.js +12 -0
  17. package/dist/markers.d.ts +3 -0
  18. package/dist/markers.js +3 -0
  19. package/dist/next.d.ts +33 -0
  20. package/dist/next.js +52 -0
  21. package/dist/payload-CLKDVZWl.d.ts +68 -0
  22. package/dist/plugins/access/server.d.ts +122 -0
  23. package/dist/plugins/access/server.js +339 -0
  24. package/dist/plugins/access.d.ts +41 -0
  25. package/dist/plugins/access.js +250 -0
  26. package/dist/plugins/agent/server.d.ts +129 -0
  27. package/dist/plugins/agent/server.js +429 -0
  28. package/dist/plugins/agent.d.ts +37 -0
  29. package/dist/plugins/agent.js +411 -0
  30. package/dist/plugins/design.d.ts +125 -0
  31. package/dist/plugins/design.js +619 -0
  32. package/dist/plugins/observe.d.ts +64 -0
  33. package/dist/plugins/observe.js +617 -0
  34. package/dist/plugins/ops/server.d.ts +82 -0
  35. package/dist/plugins/ops/server.js +380 -0
  36. package/dist/plugins/ops.d.ts +26 -0
  37. package/dist/plugins/ops.js +362 -0
  38. package/dist/plugins/vitals.d.ts +25 -0
  39. package/dist/plugins/vitals.js +26 -0
  40. package/dist/plugins-CQ_0w1ot.d.ts +114 -0
  41. package/dist/plugins-DVy_vaYr.js +70 -0
  42. package/dist/review.d.ts +181 -0
  43. package/dist/review.js +1017 -0
  44. package/dist/selector-NSYSe7AZ.js +28 -0
  45. package/dist/server.d.ts +3 -0
  46. package/dist/server.js +106 -0
  47. package/dist/shortcut-U6ftVY06.js +222 -0
  48. package/dist/state-boundary.d.ts +26 -0
  49. package/dist/state-boundary.js +61 -0
  50. package/dist/styles--slk86ZQ.js +332 -0
  51. package/dist/types-DADBRiGk.d.ts +50 -0
  52. package/dist/types-QJUajwWq.js +35 -0
  53. package/dist/types-f3jlNxzw.d.ts +92 -0
  54. package/dist/vitals-panel-Dvc5dnR-.js +87 -0
  55. package/package.json +36 -3
@@ -0,0 +1,619 @@
1
+ import { i as definePlugin } from "../plugins-DVy_vaYr.js";
2
+ import { a as getForceStateSnapshot, c as subscribeForceState, i as formatForceStateParam, n as FORCE_STATE_MARKER, o as getForceStateStore, r as FORCE_STATE_PARAM, s as resolveForcedState, t as FORCED_STATES } from "../force-state-BaqWsmrf.js";
3
+ import { useCallback, useEffect, useState, useSyncExternalStore } from "react";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/plugins/design/styles.ts
6
+ /**
7
+ * Design plugin styles. Each panel renders them in a `<style>` inside the
8
+ * HUD's shadow root, so the core stylesheet carries nothing for an app that
9
+ * omits the plugin. Built on the shell's `--hud-*` tokens.
10
+ */
11
+ const DESIGN_CSS = `
12
+ .design { display: flex; flex-direction: column; gap: 12px; }
13
+ .design-section { display: flex; flex-direction: column; gap: 6px; }
14
+ .design-heading {
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: space-between;
18
+ gap: 8px;
19
+ font-size: 10px;
20
+ font-weight: 600;
21
+ letter-spacing: 0.06em;
22
+ text-transform: uppercase;
23
+ color: var(--hud-fg-muted);
24
+ }
25
+ .design-muted { color: var(--hud-fg-muted); }
26
+ .design-hint { color: var(--hud-fg-muted); font-size: 11px; }
27
+ .design-mono { font-family: var(--hud-mono); }
28
+ .hud .design-link {
29
+ color: var(--hud-accent);
30
+ text-decoration: none;
31
+ cursor: pointer;
32
+ }
33
+ .hud .design-link:hover { text-decoration: underline; }
34
+
35
+ .design-swatches {
36
+ display: grid;
37
+ grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
38
+ gap: 6px;
39
+ }
40
+ .hud .design-swatch {
41
+ display: flex;
42
+ flex-direction: column;
43
+ overflow: hidden;
44
+ border: 1px solid var(--hud-border);
45
+ border-radius: 6px;
46
+ text-align: left;
47
+ }
48
+ .hud .design-swatch:hover { border-color: var(--hud-accent); }
49
+ .design-chip {
50
+ display: block;
51
+ height: 28px;
52
+ border-bottom: 1px solid var(--hud-border);
53
+ background-image: repeating-linear-gradient(
54
+ 45deg, var(--hud-bg-subtle) 0 4px, transparent 4px 8px
55
+ );
56
+ }
57
+ .design-swatch-text {
58
+ display: flex;
59
+ flex-direction: column;
60
+ padding: 3px 6px;
61
+ min-width: 0;
62
+ font-family: var(--hud-mono);
63
+ font-size: 10px;
64
+ }
65
+ .design-swatch-text span {
66
+ overflow: hidden;
67
+ text-overflow: ellipsis;
68
+ white-space: nowrap;
69
+ }
70
+
71
+ .design-row {
72
+ display: flex;
73
+ align-items: center;
74
+ gap: 8px;
75
+ min-width: 0;
76
+ }
77
+ .design-grow { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
78
+ .design-badge {
79
+ padding: 0 6px;
80
+ border: 1px solid var(--hud-border);
81
+ border-radius: 999px;
82
+ font-family: var(--hud-mono);
83
+ font-size: 10px;
84
+ white-space: nowrap;
85
+ }
86
+ .design-badge[data-tone="active"] { color: var(--hud-good); border-color: currentColor; }
87
+ .design-badge[data-tone="forced"] {
88
+ color: var(--hud-bg);
89
+ background: var(--hud-warn);
90
+ border-color: var(--hud-warn);
91
+ }
92
+
93
+ .design-boundary { display: flex; flex-direction: column; gap: 4px; padding: 4px 0; }
94
+ .design-segments { display: flex; gap: 2px; margin: 0; padding: 0; border: 0; min-inline-size: 0; }
95
+ .hud .design-segment {
96
+ flex: 1;
97
+ padding: 2px 4px;
98
+ border: 1px solid var(--hud-border);
99
+ border-radius: 4px;
100
+ font-family: var(--hud-mono);
101
+ font-size: 10px;
102
+ text-align: center;
103
+ }
104
+ .hud .design-segment:hover { background: var(--hud-bg-subtle); }
105
+ .hud .design-segment[aria-pressed="true"] {
106
+ background: var(--hud-accent);
107
+ border-color: var(--hud-accent);
108
+ color: var(--hud-bg);
109
+ }
110
+
111
+ .hud .design-button {
112
+ align-self: flex-start;
113
+ padding: 2px 10px;
114
+ border: 1px solid var(--hud-border);
115
+ border-radius: 4px;
116
+ }
117
+ .hud .design-button:hover:not(:disabled) { background: var(--hud-bg-subtle); }
118
+ .hud .design-button:disabled { opacity: 0.5; cursor: default; }
119
+
120
+ .design-field {
121
+ width: 100%;
122
+ padding: 4px 6px;
123
+ border: 1px solid var(--hud-border);
124
+ border-radius: 4px;
125
+ background: var(--hud-bg-subtle);
126
+ color: var(--hud-fg);
127
+ font-family: var(--hud-mono);
128
+ font-size: 11px;
129
+ }
130
+ textarea.design-field { min-height: 44px; resize: vertical; }
131
+ .design-result[data-ok="true"] { color: var(--hud-good); }
132
+ .design-result[data-ok="false"] { color: var(--hud-poor); }
133
+ `;
134
+ //#endregion
135
+ //#region src/plugins/design/force-state-panel.tsx
136
+ const CHOICES = [null, ...FORCED_STATES];
137
+ function useForceState() {
138
+ return useSyncExternalStore(subscribeForceState, getForceStateSnapshot, getForceStateSnapshot);
139
+ }
140
+ /**
141
+ * Pill content: how many boundaries are forced, by toggle or param. Uses the
142
+ * core `.dot`, since the plugin stylesheet only loads with the panel.
143
+ */
144
+ function ForceStatePill() {
145
+ const snapshot = useForceState();
146
+ const forced = snapshot.boundaries.filter((b) => resolveForcedState(snapshot, b)).length;
147
+ if (forced === 0) return /* @__PURE__ */ jsx(Fragment, { children: "States" });
148
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
149
+ /* @__PURE__ */ jsx("span", {
150
+ className: "dot",
151
+ "data-rating": "needs-improvement"
152
+ }),
153
+ "States ",
154
+ forced
155
+ ] });
156
+ }
157
+ /**
158
+ * Toggles every mounted `StateBoundary` between its real state and a forced
159
+ * loading, empty or error. Toggles live in memory: a reload returns every
160
+ * boundary to auto, so a forgotten toggle cannot outlive the page.
161
+ */
162
+ function ForceStatePanel() {
163
+ const store = getForceStateStore();
164
+ const snapshot = useForceState();
165
+ const hasOverrides = Object.keys(snapshot.overrides).length > 0;
166
+ useEffect(() => store.syncSearch(window.location.search), [store]);
167
+ return /* @__PURE__ */ jsxs("div", {
168
+ className: "design",
169
+ "data-hud-force-state": "",
170
+ children: [
171
+ /* @__PURE__ */ jsx("style", { children: DESIGN_CSS }),
172
+ /* @__PURE__ */ jsxs("section", {
173
+ className: "design-section",
174
+ "aria-label": "State boundaries",
175
+ children: [/* @__PURE__ */ jsxs("div", {
176
+ className: "design-heading",
177
+ children: [/* @__PURE__ */ jsx("span", { children: "State boundaries" }), hasOverrides && /* @__PURE__ */ jsx("button", {
178
+ type: "button",
179
+ className: "design-link",
180
+ onClick: () => store.clearOverrides(),
181
+ children: "reset all"
182
+ })]
183
+ }), snapshot.boundaries.length === 0 ? /* @__PURE__ */ jsx("p", {
184
+ className: "design-hint",
185
+ children: "No state boundary on this page. Wrap a query branch in StateBoundary from @hansenexus/hud/state-boundary to list it here."
186
+ }) : snapshot.boundaries.map((boundary) => {
187
+ const override = snapshot.overrides[boundary.id] ?? null;
188
+ const effective = resolveForcedState(snapshot, boundary);
189
+ return /* @__PURE__ */ jsxs("div", {
190
+ className: "design-boundary",
191
+ children: [/* @__PURE__ */ jsxs("div", {
192
+ className: "design-row",
193
+ children: [/* @__PURE__ */ jsxs("span", {
194
+ className: "design-grow design-mono",
195
+ children: [boundary.parentId ? "↳ " : "", boundary.id]
196
+ }), /* @__PURE__ */ jsx("span", {
197
+ className: "design-badge",
198
+ "data-tone": effective ? "forced" : void 0,
199
+ children: effective ?? "real"
200
+ })]
201
+ }), /* @__PURE__ */ jsx("fieldset", {
202
+ className: "design-segments",
203
+ "aria-label": `${boundary.id} state`,
204
+ children: CHOICES.map((choice) => /* @__PURE__ */ jsx("button", {
205
+ type: "button",
206
+ className: "design-segment",
207
+ "aria-pressed": override === choice,
208
+ onClick: () => store.force(boundary.id, choice),
209
+ children: choice ?? "auto"
210
+ }, choice ?? "auto"))
211
+ })]
212
+ }, boundary.id);
213
+ })]
214
+ }),
215
+ /* @__PURE__ */ jsxs("section", {
216
+ className: "design-section",
217
+ "aria-label": "URL param",
218
+ children: [/* @__PURE__ */ jsxs("div", {
219
+ className: "design-row",
220
+ children: [/* @__PURE__ */ jsxs("span", {
221
+ className: "design-grow design-mono",
222
+ children: [
223
+ "?",
224
+ FORCE_STATE_PARAM,
225
+ "="
226
+ ]
227
+ }), /* @__PURE__ */ jsx("span", {
228
+ className: "design-mono",
229
+ children: snapshot.param ? formatForceStateParam(snapshot.param) : "not set"
230
+ })]
231
+ }), /* @__PURE__ */ jsxs("p", {
232
+ className: "design-hint",
233
+ children: [
234
+ "?",
235
+ FORCE_STATE_PARAM,
236
+ "=loading|empty|error forces the top-level boundaries; ?",
237
+ FORCE_STATE_PARAM,
238
+ "=<id>:error targets one. A toggle here wins over the param."
239
+ ]
240
+ })]
241
+ })
242
+ ]
243
+ });
244
+ }
245
+ //#endregion
246
+ //#region src/plugins/design/tokens.ts
247
+ function describe(token) {
248
+ return typeof token === "string" ? { cssVar: token } : token;
249
+ }
250
+ /**
251
+ * Reads every token's computed value from the host document.
252
+ *
253
+ * The HUD renders in a shadow root, and host selectors such as
254
+ * `[data-theme="brand"]` never match inside it, so painting `var(--x)` there
255
+ * shows the wrong value for scoped tokens. Instead each group gets a hidden
256
+ * probe element in the host `<body>`, carrying the group's scope attribute,
257
+ * and the panel paints the probe's resolved values.
258
+ */
259
+ function readTokens(adapter, doc = document) {
260
+ const view = doc.defaultView;
261
+ if (!view) return [];
262
+ return adapter.groups.map((group) => {
263
+ const probe = doc.createElement("div");
264
+ probe.setAttribute("data-hud-token-probe", "");
265
+ probe.setAttribute("aria-hidden", "true");
266
+ probe.style.setProperty("display", "none", "important");
267
+ if (group.scope) probe.setAttribute(group.scope.attribute, group.scope.value ?? "");
268
+ doc.body.appendChild(probe);
269
+ try {
270
+ const style = view.getComputedStyle(probe);
271
+ return {
272
+ label: group.label,
273
+ tokens: group.tokens.map(describe).map(({ cssVar, label }) => ({
274
+ cssVar,
275
+ label: label ?? cssVar.replace(/^--/, ""),
276
+ value: style.getPropertyValue(cssVar).trim()
277
+ }))
278
+ };
279
+ } finally {
280
+ probe.remove();
281
+ }
282
+ });
283
+ }
284
+ /**
285
+ * The value as a paintable color, or null. shadcn-style themes store bare HSL
286
+ * channels (`222 47% 11%`), which paint once wrapped in `hsl()`.
287
+ */
288
+ function tokenColor(value) {
289
+ if (!value || typeof CSS === "undefined" || typeof CSS.supports !== "function") return null;
290
+ if (CSS.supports("color", value)) return value;
291
+ const hsl = `hsl(${value})`;
292
+ return CSS.supports("color", hsl) ? hsl : null;
293
+ }
294
+ /**
295
+ * Calls `onChange` whenever the host's resolved tokens may have changed: a
296
+ * theme class or attribute flips on `<html>` or `<body>`, a stylesheet is
297
+ * added or removed (HMR), or the OS color scheme changes.
298
+ */
299
+ function watchTokens(onChange, doc = document) {
300
+ const observer = new MutationObserver((records) => {
301
+ if (records.some((record) => record.type === "attributes" || [...record.addedNodes, ...record.removedNodes].some((node) => node.nodeName === "STYLE" || node.nodeName === "LINK"))) onChange();
302
+ });
303
+ observer.observe(doc.documentElement, { attributes: true });
304
+ observer.observe(doc.head, { childList: true });
305
+ observer.observe(doc.body, { attributes: true });
306
+ const media = doc.defaultView?.matchMedia?.("(prefers-color-scheme: dark)");
307
+ media?.addEventListener("change", onChange);
308
+ return () => {
309
+ observer.disconnect();
310
+ media?.removeEventListener("change", onChange);
311
+ };
312
+ }
313
+ //#endregion
314
+ //#region src/plugins/design/tokens-panel.tsx
315
+ /**
316
+ * Live design-token swatches. Values are read from the host document (see
317
+ * `readTokens`), re-read when the host's theme changes, and painted as
318
+ * resolved literals. Click copies `var(<token>)`.
319
+ */
320
+ function TokensPanel({ adapter }) {
321
+ const [groups, setGroups] = useState(() => readTokens(adapter));
322
+ const [copied, setCopied] = useState(null);
323
+ const refresh = useCallback(() => setGroups(readTokens(adapter)), [adapter]);
324
+ useEffect(() => {
325
+ refresh();
326
+ return watchTokens(refresh);
327
+ }, [refresh]);
328
+ useEffect(() => {
329
+ if (copied === null) return;
330
+ const timer = window.setTimeout(() => setCopied(null), 1200);
331
+ return () => window.clearTimeout(timer);
332
+ }, [copied]);
333
+ const copy = (cssVar) => {
334
+ navigator.clipboard?.writeText(`var(${cssVar})`).catch(() => {});
335
+ setCopied(cssVar);
336
+ };
337
+ return /* @__PURE__ */ jsxs("div", {
338
+ className: "design",
339
+ children: [
340
+ /* @__PURE__ */ jsx("style", { children: DESIGN_CSS }),
341
+ groups.map((group) => /* @__PURE__ */ jsxs("section", {
342
+ className: "design-section",
343
+ "aria-label": group.label,
344
+ children: [/* @__PURE__ */ jsxs("div", {
345
+ className: "design-heading",
346
+ children: [/* @__PURE__ */ jsx("span", { children: group.label }), /* @__PURE__ */ jsx("button", {
347
+ type: "button",
348
+ className: "design-link",
349
+ onClick: refresh,
350
+ children: "refresh"
351
+ })]
352
+ }), /* @__PURE__ */ jsx("div", {
353
+ className: "design-swatches",
354
+ children: group.tokens.map((token) => {
355
+ const color = tokenColor(token.value);
356
+ return /* @__PURE__ */ jsxs("button", {
357
+ type: "button",
358
+ className: "design-swatch",
359
+ title: `var(${token.cssVar}) = ${token.value || "unset"} (click to copy)`,
360
+ onClick: () => copy(token.cssVar),
361
+ children: [/* @__PURE__ */ jsx("span", {
362
+ className: "design-chip",
363
+ "data-token-chip": token.cssVar,
364
+ style: color ? { background: color } : void 0
365
+ }), /* @__PURE__ */ jsxs("span", {
366
+ className: "design-swatch-text",
367
+ children: [/* @__PURE__ */ jsx("span", { children: copied === token.cssVar ? "copied" : token.label }), /* @__PURE__ */ jsx("span", {
368
+ className: "design-muted",
369
+ children: token.value || "unset"
370
+ })]
371
+ })]
372
+ }, token.cssVar);
373
+ })
374
+ })]
375
+ }, group.label)),
376
+ adapter.sourceHint && /* @__PURE__ */ jsx("p", {
377
+ className: "design-hint design-mono",
378
+ children: adapter.sourceHint
379
+ })
380
+ ]
381
+ });
382
+ }
383
+ //#endregion
384
+ //#region src/plugins/design/variants-panel.tsx
385
+ function errorMessage(error, fallback) {
386
+ return error instanceof Error ? error.message : fallback;
387
+ }
388
+ function useCategories(source) {
389
+ const [loaded, setLoaded] = useState(() => typeof source === "function" ? { state: "loading" } : {
390
+ state: "ready",
391
+ categories: source
392
+ });
393
+ useEffect(() => {
394
+ if (typeof source !== "function") {
395
+ setLoaded({
396
+ state: "ready",
397
+ categories: source
398
+ });
399
+ return;
400
+ }
401
+ let live = true;
402
+ setLoaded({ state: "loading" });
403
+ source().then((categories) => live && setLoaded({
404
+ state: "ready",
405
+ categories
406
+ }), (error) => live && setLoaded({
407
+ state: "failed",
408
+ error: errorMessage(error, "loading failed")
409
+ }));
410
+ return () => {
411
+ live = false;
412
+ };
413
+ }, [source]);
414
+ return loaded;
415
+ }
416
+ function groupByDomain(categories) {
417
+ const grouped = /* @__PURE__ */ new Map();
418
+ for (const category of categories) {
419
+ const key = category.domain ?? "shared";
420
+ grouped.set(key, [...grouped.get(key) ?? [], category]);
421
+ }
422
+ return [...grouped];
423
+ }
424
+ /**
425
+ * Variant overview over an app-provided adapter: registered categories with
426
+ * status, drill-in links (or the in-page preview), and the generate-brief
427
+ * queue. Voting stays on the review dashboard; it needs the showcase.
428
+ */
429
+ function VariantsPanel({ adapter, ctx }) {
430
+ const loaded = useCategories(adapter.categories);
431
+ const { reviewHref, categoryHref, openPreview, generate } = adapter;
432
+ return /* @__PURE__ */ jsxs("div", {
433
+ className: "design",
434
+ children: [
435
+ /* @__PURE__ */ jsx("style", { children: DESIGN_CSS }),
436
+ /* @__PURE__ */ jsxs("section", {
437
+ className: "design-section",
438
+ "aria-label": "Categories",
439
+ children: [
440
+ /* @__PURE__ */ jsxs("div", {
441
+ className: "design-heading",
442
+ children: [/* @__PURE__ */ jsxs("span", { children: ["Categories", loaded.state === "ready" ? ` (${loaded.categories.length})` : ""] }), reviewHref && /* @__PURE__ */ jsx("a", {
443
+ className: "design-link",
444
+ href: reviewHref,
445
+ children: "review ↗"
446
+ })]
447
+ }),
448
+ loaded.state === "loading" && /* @__PURE__ */ jsx("p", {
449
+ className: "design-hint",
450
+ children: "Loading…"
451
+ }),
452
+ loaded.state === "failed" && /* @__PURE__ */ jsx("p", {
453
+ className: "design-result",
454
+ "data-ok": "false",
455
+ children: loaded.error
456
+ }),
457
+ loaded.state === "ready" && loaded.categories.length === 0 && /* @__PURE__ */ jsx("p", {
458
+ className: "design-hint",
459
+ children: "No variant categories registered for this app."
460
+ }),
461
+ loaded.state === "ready" && groupByDomain(loaded.categories).map(([domain, list]) => /* @__PURE__ */ jsxs("div", {
462
+ className: "design-section",
463
+ children: [/* @__PURE__ */ jsx("span", {
464
+ className: "design-heading",
465
+ children: domain
466
+ }), list.map((category) => /* @__PURE__ */ jsxs("div", {
467
+ className: "design-row",
468
+ children: [
469
+ /* @__PURE__ */ jsx("span", {
470
+ className: "design-badge",
471
+ "data-tone": category.status === "active" ? "active" : void 0,
472
+ children: category.status
473
+ }),
474
+ /* @__PURE__ */ jsx("span", {
475
+ className: "design-grow",
476
+ children: category.name
477
+ }),
478
+ /* @__PURE__ */ jsxs("span", {
479
+ className: "design-muted",
480
+ children: [
481
+ category.alternativeCount,
482
+ " alt",
483
+ category.alternativeCount === 1 ? "" : "s"
484
+ ]
485
+ }),
486
+ openPreview ? /* @__PURE__ */ jsx("button", {
487
+ type: "button",
488
+ className: "design-link",
489
+ onClick: () => openPreview(category.id),
490
+ children: "open"
491
+ }) : categoryHref ? /* @__PURE__ */ jsx("a", {
492
+ className: "design-link",
493
+ href: categoryHref(category.id),
494
+ children: "open ↗"
495
+ }) : null
496
+ ]
497
+ }, category.id))]
498
+ }, domain))
499
+ ]
500
+ }),
501
+ generate && ctx.can("variants.generate") && /* @__PURE__ */ jsx(GenerateForm, { generate })
502
+ ]
503
+ });
504
+ }
505
+ function GenerateForm({ generate }) {
506
+ const [path, setPath] = useState("");
507
+ const [brief, setBrief] = useState("");
508
+ const [sending, setSending] = useState(false);
509
+ const [result, setResult] = useState(null);
510
+ const submit = async () => {
511
+ if (!path.trim()) return;
512
+ setSending(true);
513
+ setResult(null);
514
+ try {
515
+ setResult(await generate({
516
+ path: path.trim(),
517
+ brief: brief.trim(),
518
+ count: 3
519
+ }));
520
+ } catch (error) {
521
+ setResult({
522
+ ok: false,
523
+ error: errorMessage(error, "generate failed")
524
+ });
525
+ } finally {
526
+ setSending(false);
527
+ }
528
+ };
529
+ return /* @__PURE__ */ jsxs("section", {
530
+ className: "design-section",
531
+ "aria-label": "Generate variants",
532
+ children: [
533
+ /* @__PURE__ */ jsx("span", {
534
+ className: "design-heading",
535
+ children: "Generate variants"
536
+ }),
537
+ /* @__PURE__ */ jsx("input", {
538
+ className: "design-field",
539
+ "aria-label": "Component path",
540
+ placeholder: "src/components/… (repo-relative)",
541
+ value: path,
542
+ onChange: (e) => setPath(e.target.value)
543
+ }),
544
+ /* @__PURE__ */ jsx("textarea", {
545
+ className: "design-field",
546
+ "aria-label": "Brief",
547
+ placeholder: "Brief, e.g. three denser table treatments",
548
+ value: brief,
549
+ onChange: (e) => setBrief(e.target.value)
550
+ }),
551
+ /* @__PURE__ */ jsx("button", {
552
+ type: "button",
553
+ className: "design-button",
554
+ disabled: sending || !path.trim(),
555
+ onClick: submit,
556
+ children: "Queue 3 variants"
557
+ }),
558
+ result && /* @__PURE__ */ jsx("p", {
559
+ className: "design-result design-mono",
560
+ "data-ok": result.ok,
561
+ children: result.ok ? `queued → ${result.markerPath ?? "ok"}` : result.error ?? "failed"
562
+ })
563
+ ]
564
+ });
565
+ }
566
+ //#endregion
567
+ //#region src/plugins/design/index.tsx
568
+ /**
569
+ * Live swatches of the host app's design tokens. Values are read from the
570
+ * host document, outside the HUD's shadow root, so scoped and themed tokens
571
+ * show what the page actually renders. Client only.
572
+ */
573
+ function tokens(adapter) {
574
+ function Panel() {
575
+ return /* @__PURE__ */ jsx(TokensPanel, { adapter });
576
+ }
577
+ return definePlugin({
578
+ id: "tokens",
579
+ title: "Tokens",
580
+ panel: Panel
581
+ });
582
+ }
583
+ /**
584
+ * The app's variant categories and the generate-brief queue, over an adapter
585
+ * onto the app's own registry. Generating needs `variants.generate`. Client only.
586
+ */
587
+ function variants(adapter) {
588
+ function Panel({ ctx }) {
589
+ return /* @__PURE__ */ jsx(VariantsPanel, {
590
+ adapter,
591
+ ctx
592
+ });
593
+ }
594
+ return definePlugin({
595
+ id: "variants",
596
+ title: "Variants",
597
+ panel: Panel
598
+ });
599
+ }
600
+ /**
601
+ * Forces the page's `StateBoundary`s (`@hansenexus/hud/state-boundary`) to
602
+ * loading, empty or error. The pill shows how many are forced. Client only.
603
+ */
604
+ function forceState() {
605
+ return definePlugin({
606
+ id: "states",
607
+ title: "States",
608
+ capability: "state.force",
609
+ pill: ForceStatePill,
610
+ panel: ForceStatePanel,
611
+ commands: [{
612
+ id: "reset",
613
+ title: "Reset forced states",
614
+ run: () => getForceStateStore().clearOverrides()
615
+ }]
616
+ });
617
+ }
618
+ //#endregion
619
+ export { FORCED_STATES, FORCE_STATE_MARKER, FORCE_STATE_PARAM, forceState, tokens, variants };
@@ -0,0 +1,64 @@
1
+ import { c as HudPluginProps } from "../plugins-CQ_0w1ot.js";
2
+ //#region src/plugins/observe/capture.d.ts
3
+ /**
4
+ * Browser-side capture into the shared ring buffer: console.log/warn/error,
5
+ * uncaught errors and rejections, fetch timing, and long tasks. The observe
6
+ * pill installs it when the HUD mounts, so whatever happens before the panel
7
+ * is first opened is already there.
8
+ */
9
+ interface CaptureOptions {
10
+ /** Requests to leave out of the log. Default: the HUD's own `/api/hud/` calls. */
11
+ ignore?: (url: string) => boolean;
12
+ }
13
+ //#endregion
14
+ //#region src/plugins/observe/info-tab.d.ts
15
+ /** The signed-in user as the app knows it. Every field is optional. */
16
+ interface HudUserInfo {
17
+ name?: string;
18
+ email?: string;
19
+ id?: string;
20
+ role?: string;
21
+ /** App-specific extras (organization, tier, locale ...). */
22
+ extra?: Record<string, string>;
23
+ }
24
+ //#endregion
25
+ //#region src/plugins/observe/capture-buffer.d.ts
26
+ type CaptureKind = "log" | "warn" | "error" | "fetch" | "longtask";
27
+ interface CaptureEntry {
28
+ id: number;
29
+ ts: number;
30
+ kind: CaptureKind;
31
+ text: string;
32
+ status?: number;
33
+ durationMs?: number;
34
+ }
35
+ //#endregion
36
+ //#region src/plugins/observe/index.d.ts
37
+ export interface ObserveOptions extends CaptureOptions {
38
+ /**
39
+ * Values for the Info tab, read at the app's import site so Next can inline
40
+ * them: `{ NODE_ENV: process.env.NODE_ENV, CONVEX_URL: process.env.NEXT_PUBLIC_CONVEX_URL }`.
41
+ * `undefined` shows as "not set".
42
+ */
43
+ env?: Readonly<Record<string, string | undefined>>;
44
+ /** A hook returning the signed-in user, called inside the Info tab. */
45
+ useUser?: () => HudUserInfo | null | undefined;
46
+ }
47
+ /**
48
+ * Console and network capture, an app/build/browser/user snapshot and the
49
+ * page's Web Vitals. The pill counts errors; opening the panel while there are
50
+ * any lands on the console filtered to them.
51
+ */
52
+ export declare function observe({ env, useUser, ignore }?: ObserveOptions): {
53
+ readonly id: "observe";
54
+ readonly title: "Observe";
55
+ readonly pill: () => import("react").JSX.Element;
56
+ readonly panel: ({ ctx }: HudPluginProps) => import("react").JSX.Element;
57
+ readonly commands: readonly [{
58
+ readonly id: "clear";
59
+ readonly title: "Clear console capture";
60
+ readonly run: () => void;
61
+ }];
62
+ };
63
+ //#endregion
64
+ export type { CaptureEntry, CaptureKind, CaptureOptions, HudUserInfo };