@hansenexus/hud 0.3.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.
- package/README.md +186 -4
- package/dist/access-CNvJOwKM.d.ts +54 -0
- package/dist/capabilities-CjulXFQX.js +189 -0
- package/dist/{define-hud-BHQR8Lbv.d.ts → define-hud-BBzsSl8b.d.ts} +6 -1
- package/dist/env-DfxpPfs9.js +28 -0
- package/dist/env-vEjyJfPW.d.ts +17 -0
- package/dist/force-state-BaqWsmrf.js +153 -0
- package/dist/force-state-CCqoTfVB.d.ts +23 -0
- package/dist/handler-WmE4Lu7l.d.ts +39 -0
- package/dist/index-BYsdS-7d.d.ts +55 -0
- package/dist/index.d.ts +6 -36
- package/dist/index.js +119 -202
- package/dist/marker-C66LxOhJ.d.ts +9 -0
- package/dist/marker-CMrvxPmS.d.ts +12 -0
- package/dist/marker-DjeGvL-F.js +9 -0
- package/dist/marker-uZYPOJ4M.js +12 -0
- package/dist/markers.d.ts +3 -0
- package/dist/markers.js +3 -0
- package/dist/next.d.ts +3 -18
- package/dist/next.js +3 -28
- package/dist/payload-CLKDVZWl.d.ts +68 -0
- package/dist/plugins/access/server.d.ts +122 -0
- package/dist/plugins/access/server.js +339 -0
- package/dist/plugins/access.d.ts +41 -0
- package/dist/plugins/access.js +250 -0
- package/dist/plugins/agent/server.d.ts +129 -0
- package/dist/plugins/agent/server.js +429 -0
- package/dist/plugins/agent.d.ts +37 -0
- package/dist/plugins/agent.js +411 -0
- package/dist/plugins/design.d.ts +125 -0
- package/dist/plugins/design.js +619 -0
- package/dist/plugins/observe.d.ts +64 -0
- package/dist/plugins/observe.js +617 -0
- package/dist/plugins/ops/server.d.ts +82 -0
- package/dist/plugins/ops/server.js +380 -0
- package/dist/plugins/ops.d.ts +26 -0
- package/dist/plugins/ops.js +362 -0
- package/dist/plugins/vitals.js +3 -86
- package/dist/{plugins-1QmTmZyL.d.ts → plugins-CQ_0w1ot.d.ts} +39 -1
- package/dist/{plugins-CzUiTn_r.js → plugins-DVy_vaYr.js} +3 -1
- package/dist/review.d.ts +181 -0
- package/dist/review.js +1017 -0
- package/dist/selector-NSYSe7AZ.js +28 -0
- package/dist/server.d.ts +3 -39
- package/dist/server.js +1 -1
- package/dist/shortcut-U6ftVY06.js +222 -0
- package/dist/state-boundary.d.ts +26 -0
- package/dist/state-boundary.js +61 -0
- package/dist/styles--slk86ZQ.js +332 -0
- package/dist/types-DADBRiGk.d.ts +50 -0
- package/dist/types-QJUajwWq.js +35 -0
- package/dist/types-f3jlNxzw.d.ts +92 -0
- package/dist/vitals-panel-Dvc5dnR-.js +87 -0
- package/package.json +23 -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 };
|