@hansenexus/hud 0.1.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 (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +48 -0
  3. package/chunk-0cj8dkk7.js +127 -0
  4. package/chunk-1atxwdhw.js +48 -0
  5. package/chunk-24vrg16w.js +39 -0
  6. package/chunk-3j9sm249.js +26 -0
  7. package/chunk-50pq4sg6.js +41 -0
  8. package/chunk-5gtx3pza.js +9 -0
  9. package/chunk-6gw4x08j.js +264 -0
  10. package/chunk-6jt8yg4m.js +117 -0
  11. package/chunk-71se605m.js +85 -0
  12. package/chunk-82nm9wcg.js +316 -0
  13. package/chunk-8tam7r3h.js +68 -0
  14. package/chunk-babjpqkb.js +52 -0
  15. package/chunk-c5y856ad.js +98 -0
  16. package/chunk-ghxh8926.js +172 -0
  17. package/chunk-hc2g0n35.js +24 -0
  18. package/chunk-k0vr8dsy.js +179 -0
  19. package/chunk-k50yfp5d.js +166 -0
  20. package/chunk-kyeb6qeq.js +95 -0
  21. package/chunk-msqkkqw7.js +140 -0
  22. package/chunk-nm82wr8r.js +58 -0
  23. package/chunk-r06bpmy6.js +79 -0
  24. package/chunk-t9eh4t0b.js +89 -0
  25. package/chunk-tf774c4w.js +115 -0
  26. package/chunk-v086j53b.js +19 -0
  27. package/chunk-wspz08q7.js +67 -0
  28. package/chunk-xezxydm3.js +47 -0
  29. package/chunk-xjn0896g.js +574 -0
  30. package/chunk-xztw0xyt.js +113 -0
  31. package/chunk-zzrpd92r.js +172 -0
  32. package/core/access.d.mts +53 -0
  33. package/core/access.js +27 -0
  34. package/core/api-types.d.mts +146 -0
  35. package/core/api-types.js +9 -0
  36. package/core/capabilities.d.mts +27 -0
  37. package/core/capabilities.js +14 -0
  38. package/core/capture-buffer.d.mts +37 -0
  39. package/core/capture-buffer.js +9 -0
  40. package/core/force-state.d.mts +68 -0
  41. package/core/force-state.js +21 -0
  42. package/core/mock-cookie.d.mts +30 -0
  43. package/core/mock-cookie.js +15 -0
  44. package/core/types.d.mts +59 -0
  45. package/core/types.js +9 -0
  46. package/hud-provider.d.mts +38 -0
  47. package/hud-provider.js +15 -0
  48. package/hud.d.mts +15 -0
  49. package/hud.js +18 -0
  50. package/index.d.mts +94 -0
  51. package/index.js +170 -0
  52. package/lazy-panel.d.mts +28 -0
  53. package/lazy-panel.js +11 -0
  54. package/package.json +153 -0
  55. package/panels/cicd-panel.d.mts +9 -0
  56. package/panels/cicd-panel.js +13 -0
  57. package/panels/debug-info-panel.d.mts +21 -0
  58. package/panels/debug-info-panel.js +9 -0
  59. package/panels/dispatch-panel.d.mts +10 -0
  60. package/panels/dispatch-panel.js +14 -0
  61. package/panels/env-info-panel.d.mts +9 -0
  62. package/panels/env-info-panel.js +12 -0
  63. package/panels/feature-flags-panel.d.mts +26 -0
  64. package/panels/feature-flags-panel.js +9 -0
  65. package/panels/force-state-panel.d.mts +11 -0
  66. package/panels/force-state-panel.js +113 -0
  67. package/panels/info-row.d.mts +21 -0
  68. package/panels/info-row.js +14 -0
  69. package/panels/mock-seed-panel.d.mts +15 -0
  70. package/panels/mock-seed-panel.js +14 -0
  71. package/panels/network-console-panel.d.mts +6 -0
  72. package/panels/network-console-panel.js +9 -0
  73. package/panels/role-switcher-panel.d.mts +28 -0
  74. package/panels/role-switcher-panel.js +9 -0
  75. package/panels/sessions-panel.d.mts +6 -0
  76. package/panels/sessions-panel.js +13 -0
  77. package/panels/tokens-panel.d.mts +38 -0
  78. package/panels/tokens-panel.js +9 -0
  79. package/panels/unlock-panel.d.mts +33 -0
  80. package/panels/unlock-panel.js +14 -0
  81. package/panels/variants-panel.d.mts +51 -0
  82. package/panels/variants-panel.js +12 -0
  83. package/panels/vitals-panel.d.mts +11 -0
  84. package/panels/vitals-panel.js +15 -0
  85. package/server/guard.d.mts +42 -0
  86. package/server/guard.js +12 -0
  87. package/server/unlock.d.mts +28 -0
  88. package/server/unlock.js +108 -0
  89. package/state-boundary.d.mts +25 -0
  90. package/state-boundary.js +54 -0
@@ -0,0 +1,47 @@
1
+ // panels/use-poll.ts
2
+ import { useEffect, useState } from "react";
3
+ "use client";
4
+ function usePoll(url, intervalMs = 60000) {
5
+ const [state, setState] = useState({ loading: url !== null });
6
+ useEffect(() => {
7
+ if (!url)
8
+ return;
9
+ let cancelled = false;
10
+ async function tick() {
11
+ if (document.hidden)
12
+ return;
13
+ try {
14
+ const res = await fetch(url, { headers: { Accept: "application/json" } });
15
+ if (cancelled)
16
+ return;
17
+ if (res.redirected || res.status === 401 || res.status === 403) {
18
+ setState({ loading: false, error: "auth required" });
19
+ return;
20
+ }
21
+ if (!res.ok) {
22
+ setState({ loading: false, error: `HTTP ${res.status}` });
23
+ return;
24
+ }
25
+ const data = await res.json();
26
+ if (!cancelled)
27
+ setState({ loading: false, data });
28
+ } catch (error) {
29
+ if (!cancelled) {
30
+ setState({
31
+ loading: false,
32
+ error: error instanceof Error ? error.message : "request failed"
33
+ });
34
+ }
35
+ }
36
+ }
37
+ tick();
38
+ const timer = setInterval(() => void tick(), intervalMs);
39
+ return () => {
40
+ cancelled = true;
41
+ clearInterval(timer);
42
+ };
43
+ }, [url, intervalMs]);
44
+ return state;
45
+ }
46
+
47
+ export { usePoll };
@@ -0,0 +1,574 @@
1
+ import {
2
+ HudUnlockPanel,
3
+ useHudUnlock
4
+ } from "./chunk-msqkkqw7.js";
5
+ import {
6
+ DevDrawer,
7
+ DevDrawerProvider,
8
+ cancelGrab,
9
+ completeGrab,
10
+ getGrabberState,
11
+ subscribeGrabber,
12
+ useDevDrawer
13
+ } from "./chunk-82nm9wcg.js";
14
+ import {
15
+ getVitalsSnapshot
16
+ } from "./chunk-ghxh8926.js";
17
+ import {
18
+ captureBuffer
19
+ } from "./chunk-24vrg16w.js";
20
+ import {
21
+ hudRequiresUnlock,
22
+ resolveHudAccess
23
+ } from "./chunk-r06bpmy6.js";
24
+ import {
25
+ HudProvider,
26
+ useHud
27
+ } from "./chunk-wspz08q7.js";
28
+ import {
29
+ isHudVisible,
30
+ resolveHudEnv
31
+ } from "./chunk-babjpqkb.js";
32
+ import {
33
+ HUD_BUNDLE_MARKER
34
+ } from "./chunk-v086j53b.js";
35
+
36
+ // ../review-kit/rail/rail-store.ts
37
+ var tools = [];
38
+ var insetPx = 0;
39
+ var listeners = new Set;
40
+ function emit() {
41
+ tools = [...tools];
42
+ for (const listener of listeners)
43
+ listener();
44
+ }
45
+ function subscribeRail(listener) {
46
+ listeners.add(listener);
47
+ return () => listeners.delete(listener);
48
+ }
49
+ function getRailTools() {
50
+ return tools;
51
+ }
52
+ function registerRailTool(tool) {
53
+ const index = tools.findIndex((t) => t.id === tool.id);
54
+ if (index === -1)
55
+ tools.push(tool);
56
+ else
57
+ tools[index] = tool;
58
+ tools.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
59
+ emit();
60
+ }
61
+ function unregisterRailTool(id) {
62
+ tools = tools.filter((t) => t.id !== id);
63
+ emit();
64
+ }
65
+ function getRailInset() {
66
+ return insetPx;
67
+ }
68
+ var host = null;
69
+ function claimRailHost(token) {
70
+ if (host === null)
71
+ host = token;
72
+ return host === token;
73
+ }
74
+ function releaseRailHost(token) {
75
+ if (host === token)
76
+ host = null;
77
+ }
78
+ // ../review-kit/rail/tool-rail.tsx
79
+ import { useEffect as useEffect2, useId, useRef, useSyncExternalStore } from "react";
80
+ import { createPortal as createPortal2 } from "react-dom";
81
+
82
+ // ../review-kit/picker/element-picker.tsx
83
+ import { useEffect, useState } from "react";
84
+ import { createPortal } from "react-dom";
85
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
86
+ "use client";
87
+ var OVERLAY_MARKER = "data-hn-overlay";
88
+ var OVERLAY_Z_INDEX = 2147483000;
89
+ function ElementPicker({
90
+ active,
91
+ chromeSelector,
92
+ onPick,
93
+ onCancel,
94
+ hint = "Element wählen · Esc bricht ab",
95
+ accent = "#fbbf24",
96
+ accentText = "#3a2a00"
97
+ }) {
98
+ const [highlight, setHighlight] = useState(null);
99
+ useEffect(() => {
100
+ if (!active) {
101
+ setHighlight(null);
102
+ return;
103
+ }
104
+ const exclude = chromeSelector ? `[${OVERLAY_MARKER}],${chromeSelector}` : `[${OVERLAY_MARKER}]`;
105
+ document.body.style.cursor = "crosshair";
106
+ function targetFrom(event) {
107
+ const el = event.target;
108
+ if (!(el instanceof Element))
109
+ return null;
110
+ if (el.closest(exclude))
111
+ return null;
112
+ return el;
113
+ }
114
+ function onMove(event) {
115
+ const el = targetFrom(event);
116
+ if (!el) {
117
+ setHighlight(null);
118
+ return;
119
+ }
120
+ const r = el.getBoundingClientRect();
121
+ setHighlight({
122
+ left: r.left,
123
+ top: r.top,
124
+ width: r.width,
125
+ height: r.height,
126
+ label: el.tagName.toLowerCase()
127
+ });
128
+ }
129
+ function onClick(event) {
130
+ const el = targetFrom(event);
131
+ if (!el)
132
+ return;
133
+ event.preventDefault();
134
+ event.stopPropagation();
135
+ onPick(el, { clientX: event.clientX, clientY: event.clientY });
136
+ }
137
+ function onKey(event) {
138
+ if (event.key === "Escape")
139
+ onCancel();
140
+ }
141
+ document.addEventListener("mousemove", onMove, true);
142
+ document.addEventListener("click", onClick, true);
143
+ document.addEventListener("keydown", onKey, true);
144
+ return () => {
145
+ document.body.style.cursor = "";
146
+ document.removeEventListener("mousemove", onMove, true);
147
+ document.removeEventListener("click", onClick, true);
148
+ document.removeEventListener("keydown", onKey, true);
149
+ };
150
+ }, [active, chromeSelector, onPick, onCancel]);
151
+ if (!active || typeof document === "undefined")
152
+ return null;
153
+ const marker = { [OVERLAY_MARKER]: "" };
154
+ return createPortal(/* @__PURE__ */ jsxs("div", {
155
+ ...marker,
156
+ style: { position: "fixed", inset: 0, zIndex: OVERLAY_Z_INDEX, pointerEvents: "none" },
157
+ children: [
158
+ /* @__PURE__ */ jsx("div", {
159
+ style: {
160
+ position: "fixed",
161
+ top: 0,
162
+ left: "50%",
163
+ transform: "translateX(-50%)",
164
+ background: accent,
165
+ color: accentText,
166
+ font: "700 11px ui-monospace, monospace",
167
+ padding: "3px 12px",
168
+ borderRadius: "0 0 6px 6px"
169
+ },
170
+ children: hint
171
+ }),
172
+ highlight && /* @__PURE__ */ jsxs(Fragment, {
173
+ children: [
174
+ /* @__PURE__ */ jsx("div", {
175
+ style: {
176
+ position: "fixed",
177
+ left: highlight.left - 2,
178
+ top: highlight.top - 2,
179
+ width: highlight.width + 4,
180
+ height: highlight.height + 4,
181
+ border: `1.5px solid ${accent}`,
182
+ background: "rgba(251,191,36,.08)",
183
+ borderRadius: 3
184
+ }
185
+ }),
186
+ /* @__PURE__ */ jsx("div", {
187
+ style: {
188
+ position: "fixed",
189
+ left: highlight.left - 2,
190
+ top: Math.max(highlight.top - 20, 0),
191
+ background: accent,
192
+ color: accentText,
193
+ font: "700 10px ui-monospace, monospace",
194
+ padding: "1px 6px",
195
+ borderRadius: 3
196
+ },
197
+ children: highlight.label
198
+ })
199
+ ]
200
+ })
201
+ ]
202
+ }), document.body);
203
+ }
204
+
205
+ // ../review-kit/rail/tool-rail.tsx
206
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
207
+ "use client";
208
+ function ToolRail() {
209
+ const token = useRef(Symbol("rail-host"));
210
+ const id = useId();
211
+ const tools2 = useSyncExternalStore(subscribeRail, getRailTools, getRailTools);
212
+ const inset = useSyncExternalStore(subscribeRail, getRailInset, getRailInset);
213
+ const isHost = claimRailHost(token.current);
214
+ useEffect2(() => {
215
+ const claimed = token.current;
216
+ return () => releaseRailHost(claimed);
217
+ }, []);
218
+ if (!isHost || tools2.length === 0 || typeof document === "undefined")
219
+ return null;
220
+ const marker = { [OVERLAY_MARKER]: "" };
221
+ return createPortal2(/* @__PURE__ */ jsx2("div", {
222
+ ...marker,
223
+ style: {
224
+ position: "fixed",
225
+ right: inset,
226
+ top: "50%",
227
+ transform: "translateY(-50%)",
228
+ zIndex: OVERLAY_Z_INDEX + 100,
229
+ display: "flex",
230
+ flexDirection: "column",
231
+ gap: 4,
232
+ transition: "right 180ms ease",
233
+ pointerEvents: "auto"
234
+ },
235
+ children: tools2.map((tool) => /* @__PURE__ */ jsx2(RailTab, {
236
+ tool
237
+ }, `${id}-${tool.id}`))
238
+ }), document.body);
239
+ }
240
+ function RailTab({ tool }) {
241
+ const accent = tool.accent ?? "#111827";
242
+ return /* @__PURE__ */ jsxs2("button", {
243
+ onClick: tool.onClick,
244
+ style: {
245
+ display: "flex",
246
+ alignItems: "center",
247
+ justifyContent: "center",
248
+ gap: 6,
249
+ padding: "14px 9px",
250
+ borderRadius: "8px 0 0 8px",
251
+ border: "none",
252
+ borderRight: tool.active ? "3px solid #fff" : "none",
253
+ background: accent,
254
+ color: "#fff",
255
+ font: "600 12px ui-sans-serif, system-ui, sans-serif",
256
+ letterSpacing: ".02em",
257
+ cursor: "pointer",
258
+ writingMode: "vertical-rl",
259
+ boxShadow: "0 2px 10px rgba(0,0,0,.3)",
260
+ opacity: tool.active ? 1 : 0.92
261
+ },
262
+ title: tool.label,
263
+ type: "button",
264
+ children: [
265
+ tool.label,
266
+ tool.badge !== undefined && tool.badge !== "" && /* @__PURE__ */ jsx2("span", {
267
+ style: {
268
+ display: "inline-flex",
269
+ alignItems: "center",
270
+ justifyContent: "center",
271
+ minWidth: 16,
272
+ height: 16,
273
+ padding: "0 4px",
274
+ borderRadius: 8,
275
+ background: "rgba(255,255,255,.22)",
276
+ font: "700 10px ui-monospace, monospace",
277
+ writingMode: "horizontal-tb"
278
+ },
279
+ children: tool.badge
280
+ })
281
+ ]
282
+ });
283
+ }
284
+ // hud.tsx
285
+ import { Lock } from "lucide-react";
286
+ import { useEffect as useEffect3, useMemo } from "react";
287
+
288
+ // capture.ts
289
+ var installed = false;
290
+ var restorers = [];
291
+ var MAX_TEXT = 300;
292
+ function toText(args) {
293
+ return args.map((arg) => {
294
+ if (typeof arg === "string")
295
+ return arg;
296
+ if (arg instanceof Error)
297
+ return `${arg.name}: ${arg.message}`;
298
+ try {
299
+ return JSON.stringify(arg);
300
+ } catch {
301
+ return String(arg);
302
+ }
303
+ }).join(" ").slice(0, MAX_TEXT);
304
+ }
305
+ function isHudInternal(url, input, init) {
306
+ if (url.includes("/api/hud/") || url.includes("/api/otel/vitals"))
307
+ return true;
308
+ try {
309
+ if (new Headers(init?.headers).has("x-hud-internal"))
310
+ return true;
311
+ if (input instanceof Request && input.headers.has("x-hud-internal"))
312
+ return true;
313
+ } catch {}
314
+ return false;
315
+ }
316
+ function patchConsole() {
317
+ const kinds = ["log", "warn", "error"];
318
+ for (const kind of kinds) {
319
+ const original = console[kind].bind(console);
320
+ console[kind] = (...args) => {
321
+ captureBuffer.push({ kind, text: toText(args) });
322
+ original(...args);
323
+ };
324
+ restorers.push(() => {
325
+ console[kind] = original;
326
+ });
327
+ }
328
+ }
329
+ function patchFetch() {
330
+ const originalFetch = window.fetch;
331
+ const callOriginal = originalFetch.bind(window);
332
+ const patched = async (input, init) => {
333
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
334
+ const method = init?.method ?? (input instanceof Request ? input.method : "GET");
335
+ if (isHudInternal(url, input, init))
336
+ return callOriginal(input, init);
337
+ const started = performance.now();
338
+ try {
339
+ const response = await callOriginal(input, init);
340
+ captureBuffer.push({
341
+ kind: "fetch",
342
+ text: `${method.toUpperCase()} ${url}`.slice(0, MAX_TEXT),
343
+ status: response.status,
344
+ durationMs: Math.round(performance.now() - started)
345
+ });
346
+ return response;
347
+ } catch (error) {
348
+ captureBuffer.push({
349
+ kind: "fetch",
350
+ text: `${method.toUpperCase()} ${url} — ${toText([error])}`.slice(0, MAX_TEXT),
351
+ status: 0,
352
+ durationMs: Math.round(performance.now() - started)
353
+ });
354
+ throw error;
355
+ }
356
+ };
357
+ window.fetch = Object.assign(patched, originalFetch);
358
+ restorers.push(() => {
359
+ window.fetch = originalFetch;
360
+ });
361
+ }
362
+ function observeLongTasks() {
363
+ if (typeof PerformanceObserver === "undefined")
364
+ return;
365
+ try {
366
+ const observer = new PerformanceObserver((list) => {
367
+ for (const entry of list.getEntries()) {
368
+ captureBuffer.push({
369
+ kind: "longtask",
370
+ text: `long task ${Math.round(entry.duration)} ms`,
371
+ durationMs: Math.round(entry.duration)
372
+ });
373
+ }
374
+ });
375
+ observer.observe({ type: "longtask", buffered: true });
376
+ restorers.push(() => observer.disconnect());
377
+ } catch {}
378
+ }
379
+ function installCapture() {
380
+ if (installed || typeof window === "undefined")
381
+ return;
382
+ installed = true;
383
+ patchConsole();
384
+ patchFetch();
385
+ observeLongTasks();
386
+ }
387
+ function uninstallCapture() {
388
+ if (!installed)
389
+ return;
390
+ while (restorers.length > 0)
391
+ restorers.pop()?.();
392
+ installed = false;
393
+ }
394
+ // grabber/element-grabber.tsx
395
+ import { useCallback, useSyncExternalStore as useSyncExternalStore2 } from "react";
396
+
397
+ // ../review-kit/anchor/selector.ts
398
+ function buildAnchorSelector(el, root) {
399
+ const segments = [];
400
+ let node = el;
401
+ while (node && node !== root) {
402
+ const tag = node.tagName.toLowerCase();
403
+ const parent = node.parentElement;
404
+ if (!parent)
405
+ break;
406
+ const siblings = [...parent.children].filter((c) => c.tagName === node?.tagName);
407
+ const index = siblings.indexOf(node) + 1;
408
+ segments.unshift(siblings.length > 1 ? `${tag}:nth-of-type(${index})` : tag);
409
+ node = parent;
410
+ }
411
+ return segments.join(" > ");
412
+ }
413
+ function anchorLabelFor(el) {
414
+ const text = (el.textContent ?? "").trim().replace(/\s+/g, " ");
415
+ const tag = el.tagName.toLowerCase();
416
+ return text ? `${tag} “${text.slice(0, 32)}${text.length > 32 ? "…" : ""}”` : tag;
417
+ }
418
+ // grabber/payload.ts
419
+ function buildAnchor(el) {
420
+ const r = el.getBoundingClientRect();
421
+ const pct = (part, whole) => whole > 0 ? Number((part / whole * 100).toFixed(1)) : 0;
422
+ return {
423
+ selector: buildAnchorSelector(el, document.body),
424
+ label: anchorLabelFor(el),
425
+ x: pct(r.left, window.innerWidth),
426
+ y: pct(r.top, window.innerHeight),
427
+ width: pct(r.width, window.innerWidth),
428
+ height: pct(r.height, window.innerHeight)
429
+ };
430
+ }
431
+ function matchComponentHints(hints, route, label, max = 5) {
432
+ if (!hints?.length)
433
+ return [];
434
+ const tokens = `${route} ${label}`.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 2);
435
+ if (tokens.length === 0)
436
+ return hints.slice(0, max);
437
+ return hints.map((hint) => {
438
+ const haystack = `${hint.name} ${hint.path}`.toLowerCase();
439
+ const score = tokens.reduce((n, token) => haystack.includes(token) ? n + 1 : n, 0);
440
+ return { hint, score };
441
+ }).filter((h) => h.score > 0).sort((a, b) => b.score - a.score).slice(0, max).map((h) => h.hint);
442
+ }
443
+ function buildDispatchPayload(input) {
444
+ const route = window.location.pathname;
445
+ const recentErrors = captureBuffer.getSnapshot().filter((entry) => entry.kind === "error" || entry.kind === "warn").slice(-5).map((entry) => `[${entry.kind}] ${entry.text.slice(0, 200)}`);
446
+ return {
447
+ app: input.config.appSlug,
448
+ env: input.env,
449
+ route,
450
+ anchor: input.anchor,
451
+ componentHints: matchComponentHints(input.config.componentHints, route, input.anchor.label),
452
+ recentErrors,
453
+ vitals: input.vitals,
454
+ grabbedAt: new Date().toISOString()
455
+ };
456
+ }
457
+
458
+ // grabber/element-grabber.tsx
459
+ import { jsx as jsx3 } from "react/jsx-runtime";
460
+ "use client";
461
+ function GrabberController() {
462
+ const { config, env } = useHud();
463
+ const { openDrawer, setActiveTab } = useDevDrawer();
464
+ const grabber = useSyncExternalStore2(subscribeGrabber, getGrabberState, getGrabberState);
465
+ const onPick = useCallback((el, _point) => {
466
+ completeGrab(buildDispatchPayload({ config, env, anchor: buildAnchor(el), vitals: getVitalsSnapshot() }));
467
+ setActiveTab("dispatch");
468
+ openDrawer();
469
+ }, [config, env, openDrawer, setActiveTab]);
470
+ return /* @__PURE__ */ jsx3(ElementPicker, {
471
+ active: grabber.mode === "grabbing",
472
+ chromeSelector: HUD_CHROME_SELECTOR,
473
+ onPick,
474
+ onCancel: cancelGrab,
475
+ hint: "grab active — click an element · Esc cancels"
476
+ });
477
+ }
478
+ var HUD_CHROME_SELECTOR = "[data-hud-chrome]";
479
+
480
+ // hud.tsx
481
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
482
+ "use client";
483
+ var ENV_BADGE = {
484
+ development: "DEV",
485
+ preview: "PREVIEW",
486
+ staging: "STAGING",
487
+ production: "PROD · READ-ONLY",
488
+ disabled: "OFF"
489
+ };
490
+ function Hud({ config, panels }) {
491
+ const env = resolveHudEnv();
492
+ if (!isHudVisible(env))
493
+ return null;
494
+ return /* @__PURE__ */ jsx4(DevDrawerProvider, {
495
+ config: {
496
+ storageKey: config.storageKey ?? `${config.appSlug}-hud`,
497
+ shortcutKey: config.shortcutKey === undefined ? "h" : config.shortcutKey,
498
+ defaultHeight: 38,
499
+ minHeight: 25,
500
+ maxHeight: 75
501
+ },
502
+ children: /* @__PURE__ */ jsx4(HudProvider, {
503
+ config,
504
+ env,
505
+ panels,
506
+ children: /* @__PURE__ */ jsx4(HudChrome, {})
507
+ })
508
+ });
509
+ }
510
+ function HudChrome() {
511
+ const { config, env, name, panels } = useHud();
512
+ const { isOpen, toggleDrawer } = useDevDrawer();
513
+ const access = resolveHudAccess(env);
514
+ const apiBase = config.apiBase ?? "/api/hud";
515
+ const { state: unlockState, error: unlockError, unlock } = useHudUnlock(apiBase, access);
516
+ const locked = hudRequiresUnlock(access) && unlockState !== "unlocked";
517
+ useEffect3(() => {
518
+ installCapture();
519
+ }, []);
520
+ const tabs = useMemo(() => {
521
+ if (locked) {
522
+ return [
523
+ {
524
+ id: "unlock",
525
+ label: "Locked",
526
+ icon: Lock,
527
+ content: /* @__PURE__ */ jsx4(HudUnlockPanel, {
528
+ env,
529
+ state: unlockState,
530
+ error: unlockError,
531
+ onUnlock: unlock
532
+ })
533
+ }
534
+ ];
535
+ }
536
+ return panels.map((panel) => ({
537
+ id: panel.id,
538
+ label: panel.label,
539
+ icon: panel.icon,
540
+ content: panel.content
541
+ }));
542
+ }, [locked, env, unlockState, unlockError, unlock, panels]);
543
+ useEffect3(() => {
544
+ if (config.showFab === false)
545
+ return;
546
+ registerRailTool({
547
+ id: "hud",
548
+ label: "hud",
549
+ accent: "#0369a1",
550
+ order: 20,
551
+ active: isOpen,
552
+ onClick: toggleDrawer
553
+ });
554
+ return () => unregisterRailTool("hud");
555
+ }, [config.showFab, isOpen, toggleDrawer]);
556
+ if (tabs.length === 0)
557
+ return null;
558
+ return /* @__PURE__ */ jsxs3("div", {
559
+ "data-hud-chrome": true,
560
+ "data-hud-marker": HUD_BUNDLE_MARKER,
561
+ style: { display: "contents" },
562
+ children: [
563
+ /* @__PURE__ */ jsx4(ToolRail, {}),
564
+ /* @__PURE__ */ jsx4(DevDrawer, {
565
+ tabs,
566
+ title: name,
567
+ badgeLabel: ENV_BADGE[env]
568
+ }),
569
+ !locked && /* @__PURE__ */ jsx4(GrabberController, {})
570
+ ]
571
+ });
572
+ }
573
+
574
+ export { installCapture, uninstallCapture, buildAnchorSelector, buildAnchor, matchComponentHints, buildDispatchPayload, Hud };