@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,153 @@
1
+ //#region src/plugins/design/force-state.ts
2
+ /**
3
+ * Force-state store: lets a reviewer (the States panel) or an agent
4
+ * (`?__state=`) render any registered state boundary as loading, empty or
5
+ * error without faking data.
6
+ *
7
+ * Development only. `StateBoundary` reaches this module behind a
8
+ * `NODE_ENV === "development"` constant, so production builds drop it; a
9
+ * consumer's bundle gate greps client chunks for FORCE_STATE_MARKER to prove it.
10
+ */
11
+ const FORCED_STATES = [
12
+ "loading",
13
+ "empty",
14
+ "error"
15
+ ];
16
+ /** `?__state=loading` forces top-level boundaries; `?__state=<id>:error` one boundary. */
17
+ const FORCE_STATE_PARAM = "__state";
18
+ /**
19
+ * globalThis key of the store singleton, and the bundle-gate marker. Holding
20
+ * the store on globalThis keeps one instance even if the module is evaluated
21
+ * twice: HMR, or the app's `StateBoundary` chunk and the HUD's States panel
22
+ * chunk each carrying a copy.
23
+ */
24
+ const FORCE_STATE_MARKER = "__HANSENEXUS_HUD_FORCE_STATE__";
25
+ function isForcedState(value) {
26
+ return FORCED_STATES.includes(value);
27
+ }
28
+ /** Parses the value of `?__state=`; anything unrecognised is ignored (null). */
29
+ function parseForceStateParam(value) {
30
+ if (!value) return null;
31
+ const separator = value.lastIndexOf(":");
32
+ if (separator === -1) return isForcedState(value) ? {
33
+ state: value,
34
+ target: null
35
+ } : null;
36
+ const target = value.slice(0, separator);
37
+ const state = value.slice(separator + 1);
38
+ if (!target || !isForcedState(state)) return null;
39
+ return {
40
+ state,
41
+ target
42
+ };
43
+ }
44
+ /** Reads the param out of a `location.search` string. */
45
+ function forceStateParamFromSearch(search) {
46
+ return parseForceStateParam(new URLSearchParams(search).get(FORCE_STATE_PARAM));
47
+ }
48
+ /** The param's value as it appears in the URL, `<id>:<state>` or `<state>`. */
49
+ function formatForceStateParam(param) {
50
+ return param.target === null ? param.state : `${param.target}:${param.state}`;
51
+ }
52
+ /**
53
+ * The state a boundary renders. A panel override for the boundary wins; else
54
+ * the URL param applies to its target, or, untargeted, to the nearest
55
+ * boundaries: the top-level ones. Forcing an outer boundary unmounts the
56
+ * inner ones anyway, so the page-level state is what a screenshot wants.
57
+ */
58
+ function resolveForcedState(snapshot, boundary) {
59
+ const override = snapshot.overrides[boundary.id];
60
+ if (override) return override;
61
+ const { param } = snapshot;
62
+ if (!param) return null;
63
+ if (param.target !== null) return param.target === boundary.id ? param.state : null;
64
+ return boundary.parentId === null ? param.state : null;
65
+ }
66
+ var ForceStateStore = class {
67
+ registrations = /* @__PURE__ */ new Map();
68
+ overrides = {};
69
+ search = "";
70
+ snapshot = {
71
+ boundaries: [],
72
+ overrides: {},
73
+ param: null
74
+ };
75
+ listeners = /* @__PURE__ */ new Set();
76
+ getSnapshot = () => this.snapshot;
77
+ subscribe = (listener) => {
78
+ this.listeners.add(listener);
79
+ return () => {
80
+ this.listeners.delete(listener);
81
+ };
82
+ };
83
+ /** Registers a mounted boundary; the returned function unregisters it. Mounts are counted. */
84
+ register(id, parentId) {
85
+ const existing = this.registrations.get(id);
86
+ this.registrations.set(id, {
87
+ parentId,
88
+ count: (existing?.count ?? 0) + 1
89
+ });
90
+ this.emit();
91
+ return () => {
92
+ const current = this.registrations.get(id);
93
+ if (!current) return;
94
+ if (current.count > 1) this.registrations.set(id, {
95
+ ...current,
96
+ count: current.count - 1
97
+ });
98
+ else this.registrations.delete(id);
99
+ this.emit();
100
+ };
101
+ }
102
+ /** Forces one boundary (panel toggle); null returns it to auto. */
103
+ force(id, state) {
104
+ const next = { ...this.overrides };
105
+ if (state) next[id] = state;
106
+ else delete next[id];
107
+ this.overrides = next;
108
+ this.emit();
109
+ }
110
+ clearOverrides() {
111
+ this.overrides = {};
112
+ this.emit();
113
+ }
114
+ /** Feeds the current `location.search`; a no-op when it did not change. */
115
+ syncSearch(search) {
116
+ if (search === this.search) return;
117
+ this.search = search;
118
+ this.emit();
119
+ }
120
+ emit() {
121
+ this.snapshot = {
122
+ boundaries: [...this.registrations].map(([id, { parentId }]) => ({
123
+ id,
124
+ parentId
125
+ })),
126
+ overrides: this.overrides,
127
+ param: forceStateParamFromSearch(this.search)
128
+ };
129
+ for (const listener of this.listeners) listener();
130
+ }
131
+ };
132
+ /** The page's store, created on first use. */
133
+ function getForceStateStore() {
134
+ const holder = globalThis;
135
+ holder[FORCE_STATE_MARKER] ??= new ForceStateStore();
136
+ return holder[FORCE_STATE_MARKER];
137
+ }
138
+ /** Subscribes to the page's store and keeps it in step with back/forward navigation. */
139
+ function subscribeForceState(listener) {
140
+ const store = getForceStateStore();
141
+ const onPopState = () => store.syncSearch(window.location.search);
142
+ window.addEventListener("popstate", onPopState);
143
+ const unsubscribe = store.subscribe(listener);
144
+ return () => {
145
+ window.removeEventListener("popstate", onPopState);
146
+ unsubscribe();
147
+ };
148
+ }
149
+ function getForceStateSnapshot() {
150
+ return getForceStateStore().getSnapshot();
151
+ }
152
+ //#endregion
153
+ export { getForceStateSnapshot as a, subscribeForceState as c, formatForceStateParam as i, FORCE_STATE_MARKER as n, getForceStateStore as o, FORCE_STATE_PARAM as r, resolveForcedState as s, FORCED_STATES as t };
@@ -0,0 +1,23 @@
1
+ //#region src/plugins/design/force-state.d.ts
2
+ /**
3
+ * Force-state store: lets a reviewer (the States panel) or an agent
4
+ * (`?__state=`) render any registered state boundary as loading, empty or
5
+ * error without faking data.
6
+ *
7
+ * Development only. `StateBoundary` reaches this module behind a
8
+ * `NODE_ENV === "development"` constant, so production builds drop it; a
9
+ * consumer's bundle gate greps client chunks for FORCE_STATE_MARKER to prove it.
10
+ */
11
+ declare const FORCED_STATES: readonly ["loading", "empty", "error"];
12
+ type ForcedState = (typeof FORCED_STATES)[number];
13
+ /** `?__state=loading` forces top-level boundaries; `?__state=<id>:error` one boundary. */
14
+ declare const FORCE_STATE_PARAM = "__state";
15
+ /**
16
+ * globalThis key of the store singleton, and the bundle-gate marker. Holding
17
+ * the store on globalThis keeps one instance even if the module is evaluated
18
+ * twice: HMR, or the app's `StateBoundary` chunk and the HUD's States panel
19
+ * chunk each carrying a copy.
20
+ */
21
+ declare const FORCE_STATE_MARKER = "__HANSENEXUS_HUD_FORCE_STATE__";
22
+ //#endregion
23
+ export { ForcedState as i, FORCE_STATE_MARKER as n, FORCE_STATE_PARAM as r, FORCED_STATES as t };
@@ -0,0 +1,39 @@
1
+ import { a as HudHttpMethod, o as HudPlugin, r as HudCapability } from "./plugins-CQ_0w1ot.js";
2
+ //#region src/server/handler.d.ts
3
+ /**
4
+ * Decides whether a request may reach a plugin route. Return a `Response` to
5
+ * refuse it (404, 401, 403 ...) or `null` to let it through. The `access`
6
+ * plugin supplies one for deployed tiers.
7
+ */
8
+ type HudGuard = (request: Request, capability: HudCapability | undefined) => Response | null | Promise<Response | null>;
9
+ interface HudHandlerOptions {
10
+ /** Path the catch-all route is mounted at. Default `/api/hud`. */
11
+ basePath?: string;
12
+ /** Default: `developmentOnly`, which answers 404 unless `NODE_ENV` is `development`. */
13
+ guard?: HudGuard;
14
+ /** Drops plugins server side, like `defineHud({ can })` does client side. Default: grant all. */
15
+ can?: (capability: HudCapability) => boolean;
16
+ }
17
+ type HudRequestHandler = (request: Request) => Promise<Response>;
18
+ /** One function per method, so a Next route can `export const { GET, POST } = createHudHandler(...)`. */
19
+ type HudHandler = {
20
+ handle: HudRequestHandler;
21
+ } & Record<HudHttpMethod, HudRequestHandler>;
22
+ /**
23
+ * The default guard. Core assumes development, so a deployed build that
24
+ * mounts the route without an `access` guard serves nothing and reveals
25
+ * nothing about which plugins exist.
26
+ */
27
+ declare const developmentOnly: HudGuard;
28
+ /**
29
+ * Serves every plugin's routes from one catch-all route
30
+ * (`app/api/hud/[...hud]/route.ts` in Next). A request to
31
+ * `<basePath>/<plugin id>/<path>` reaches that plugin's `routes[path][method]`;
32
+ * unknown plugin or path answers 404, a known path with another method 405.
33
+ *
34
+ * Pass the server halves (`@hansenexus/hud/plugins/<id>/server`): they carry
35
+ * `routes` and no React.
36
+ */
37
+ declare function createHudHandler(plugins: readonly HudPlugin[], options?: HudHandlerOptions): HudHandler;
38
+ //#endregion
39
+ export { createHudHandler as a, HudRequestHandler as i, HudHandler as n, developmentOnly as o, HudHandlerOptions as r, HudGuard as t };
@@ -0,0 +1,55 @@
1
+ import { o as HudPlugin, r as HudCapability } from "./plugins-CQ_0w1ot.js";
2
+ import "./define-hud-BBzsSl8b.js";
3
+ //#region src/core/grabber/picker.d.ts
4
+ interface PickPoint {
5
+ clientX: number;
6
+ clientY: number;
7
+ }
8
+ interface ElementPickerProps {
9
+ active: boolean;
10
+ onPick: (element: Element, point: PickPoint) => void;
11
+ onCancel: () => void;
12
+ /** Banner text while active. */
13
+ hint?: string;
14
+ /** Label next to the hover outline. Default: the tag name. */
15
+ describe?: (element: Element) => string;
16
+ /** Extra elements to leave alone, on top of the picker's own shadow host. */
17
+ ignore?: (element: Element) => boolean;
18
+ }
19
+ /**
20
+ * Styles for the picker overlay. The picker renders where it is mounted, so
21
+ * the caller puts these into the same (shadow) root. Colours fall back when
22
+ * the `--hud-*` tokens are absent.
23
+ */
24
+ declare const PICKER_CSS = "\n.grab-layer { position: fixed; inset: 0; pointer-events: none; }\n.grab-hint {\n position: fixed;\n top: 0;\n left: 50%;\n transform: translateX(-50%);\n padding: 3px 12px;\n border-radius: 0 0 6px 6px;\n white-space: nowrap;\n background: var(--hud-accent, #0369a1);\n color: var(--hud-accent-fg, #ffffff);\n font: 600 11px/1.5 var(--hud-mono, ui-monospace, monospace);\n}\n.grab-outline {\n position: fixed;\n border: 1.5px solid var(--hud-accent, #0369a1);\n background: color-mix(in srgb, var(--hud-accent, #0369a1) 8%, transparent);\n border-radius: 3px;\n}\n.grab-label {\n position: fixed;\n max-width: min(480px, calc(100vw - 8px));\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n padding: 1px 6px;\n border-radius: 3px;\n background: var(--hud-accent, #0369a1);\n color: var(--hud-accent-fg, #ffffff);\n font: 600 10px/1.6 var(--hud-mono, ui-monospace, monospace);\n}\n";
25
+ /**
26
+ * Click-to-pick controller: crosshair, hover outline with a label, the next
27
+ * click picks, Escape cancels. Events inside the picker's own shadow host
28
+ * (the HUD's pill and panels) pass through untouched, so panels stay usable
29
+ * and mounted during a grab.
30
+ */
31
+ declare function ElementPicker({ active, onPick, onCancel, hint, describe, ignore }: ElementPickerProps): import("react").JSX.Element;
32
+ //#endregion
33
+ //#region src/shell/hud.d.ts
34
+ interface HudProps {
35
+ /** App slug: labels the pill and keys the persisted layout. */
36
+ appSlug: string;
37
+ /** Badge shown at the start of the pill. Default "DEV". */
38
+ envLabel?: string;
39
+ /** Client halves of the plugins, in pill order. Keep the array stable across renders. */
40
+ plugins?: readonly HudPlugin[];
41
+ /** Base path of the plugin routes. Default "/api/hud". */
42
+ apiBase?: string;
43
+ /** Capability check. Default: grant all (core assumes development). Keep it stable. */
44
+ can?: (capability: HudCapability) => boolean;
45
+ }
46
+ /**
47
+ * The HUD shell: a shadow-rooted status pill and floating panels.
48
+ *
49
+ * It renders unconditionally. Keeping it out of production builds is the
50
+ * loader's job, at the import site, so a bundler can drop the whole module
51
+ * (see `@hansenexus/hud/next`).
52
+ */
53
+ declare function Hud({ appSlug, envLabel, plugins, apiBase, can }: HudProps): import("react").JSX.Element;
54
+ //#endregion
55
+ export { PICKER_CSS as a, ElementPickerProps as i, HudProps as n, PickPoint as o, ElementPicker as r, Hud as t };
package/dist/index.d.ts CHANGED
@@ -1,39 +1,6 @@
1
- import { Metric } from "web-vitals";
2
- //#region src/core/marker.d.ts
3
- /**
4
- * Literal a consumer's CI bundle gate greps for in production client chunks.
5
- * It is rendered as an attribute on the HUD root, so a bundler cannot drop it
6
- * while keeping the HUD, and it is unmistakably deliberate rather than an
7
- * incidental string a refactor could rename away.
8
- */
9
- export declare const HUD_DEV_MARKER = "__HANSENEXUS_HUD_DEV__";
10
- /** Tag name of the element that hosts the HUD's shadow root. */
11
- export declare const HUD_HOST_TAG = "hn-hud";
12
- //#endregion
13
- //#region src/shell/hud.d.ts
14
- interface HudProps {
15
- /** App slug, used for labels now and for the layout storage key later. */
16
- appSlug: string;
17
- /** Badge shown at the start of the pill. Default "DEV". */
18
- envLabel?: string;
19
- }
20
- /**
21
- * The HUD shell: a shadow-rooted status pill and floating panels.
22
- *
23
- * It renders unconditionally. Keeping it out of production builds is the
24
- * loader's job, at the import site, so a bundler can drop the whole module
25
- * (see the Next adapter, hud#4).
26
- */
27
- export declare function Hud({ appSlug, envLabel }: HudProps): import("react").JSX.Element;
28
- //#endregion
29
- //#region src/vitals/store.d.ts
30
- type VitalName = Metric["name"];
31
- type VitalRating = Metric["rating"];
32
- interface Vital {
33
- name: VitalName;
34
- value: number;
35
- rating: VitalRating;
36
- }
37
- type VitalsSnapshot = Partial<Record<VitalName, Vital>>;
38
- //#endregion
39
- export type { HudProps, Vital, VitalName, VitalRating, VitalsSnapshot };
1
+ import { a as GrabComponent, i as matchComponentHints, n as GrabAnchor, r as GrabPayload, t as ComponentHint } from "./payload-CLKDVZWl.js";
2
+ import { c as HudPluginProps, f as PinEdge, g as useGrab, h as HudGrabState, i as HudCommand, m as HudGrab, n as HudApiError, o as HudPlugin, p as definePlugin, r as HudCapability, s as HudPluginContext, t as HudAccessControl } from "./plugins-CQ_0w1ot.js";
3
+ import { n as HudMountProps, r as defineHud, t as DefineHudOptions } from "./define-hud-BBzsSl8b.js";
4
+ import { a as PICKER_CSS, i as ElementPickerProps, n as HudProps, o as PickPoint, r as ElementPicker, t as Hud } from "./index-BYsdS-7d.js";
5
+ import { n as HUD_HOST_TAG, t as HUD_DEV_MARKER } from "./marker-CMrvxPmS.js";
6
+ export { type ComponentHint, type DefineHudOptions, ElementPicker, type ElementPickerProps, type GrabAnchor, type GrabComponent, type GrabPayload, HUD_DEV_MARKER, HUD_HOST_TAG, Hud, type HudAccessControl, HudApiError, type HudCapability, type HudCommand, type HudGrab, type HudGrabState, type HudMountProps, type HudPlugin, type HudPluginContext, type HudPluginProps, type HudProps, PICKER_CSS, type PickPoint, type PinEdge, defineHud, definePlugin, matchComponentHints, useGrab };