@hansenexus/hud 0.1.1 → 0.3.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 (100) hide show
  1. package/README.md +106 -25
  2. package/dist/define-hud-BHQR8Lbv.d.ts +31 -0
  3. package/dist/index.d.ts +36 -0
  4. package/dist/index.js +1198 -0
  5. package/dist/next.d.ts +48 -0
  6. package/dist/next.js +77 -0
  7. package/dist/plugins/vitals.d.ts +25 -0
  8. package/dist/plugins/vitals.js +109 -0
  9. package/dist/plugins-1QmTmZyL.d.ts +76 -0
  10. package/dist/plugins-CzUiTn_r.js +68 -0
  11. package/dist/server.d.ts +39 -0
  12. package/dist/server.js +106 -0
  13. package/package.json +55 -133
  14. package/chunk-20zke7cs.js +0 -574
  15. package/chunk-24vrg16w.js +0 -39
  16. package/chunk-50pq4sg6.js +0 -41
  17. package/chunk-5gtx3pza.js +0 -9
  18. package/chunk-6cdpasm9.js +0 -79
  19. package/chunk-6jt8yg4m.js +0 -117
  20. package/chunk-6ngkpwz7.js +0 -127
  21. package/chunk-71se605m.js +0 -85
  22. package/chunk-82nm9wcg.js +0 -316
  23. package/chunk-9hw5r9y1.js +0 -67
  24. package/chunk-c5y856ad.js +0 -98
  25. package/chunk-edq4jqch.js +0 -89
  26. package/chunk-emfnvz4p.js +0 -166
  27. package/chunk-h0hmjdqa.js +0 -264
  28. package/chunk-hc2g0n35.js +0 -24
  29. package/chunk-hex3wqag.js +0 -179
  30. package/chunk-kvxve0hx.js +0 -140
  31. package/chunk-kyeb6qeq.js +0 -95
  32. package/chunk-mz55rvw3.js +0 -172
  33. package/chunk-nm82wr8r.js +0 -58
  34. package/chunk-q15xwpy8.js +0 -48
  35. package/chunk-q96mkjve.js +0 -68
  36. package/chunk-tczzx083.js +0 -26
  37. package/chunk-tf774c4w.js +0 -115
  38. package/chunk-v086j53b.js +0 -19
  39. package/chunk-vjh0d29g.js +0 -52
  40. package/chunk-xezxydm3.js +0 -47
  41. package/chunk-xztw0xyt.js +0 -113
  42. package/chunk-yn1d38pb.js +0 -172
  43. package/core/access.d.mts +0 -53
  44. package/core/access.js +0 -27
  45. package/core/api-types.d.mts +0 -146
  46. package/core/api-types.js +0 -9
  47. package/core/capabilities.d.mts +0 -27
  48. package/core/capabilities.js +0 -14
  49. package/core/capture-buffer.d.mts +0 -37
  50. package/core/capture-buffer.js +0 -9
  51. package/core/force-state.d.mts +0 -68
  52. package/core/force-state.js +0 -21
  53. package/core/mock-cookie.d.mts +0 -30
  54. package/core/mock-cookie.js +0 -15
  55. package/core/types.d.mts +0 -59
  56. package/core/types.js +0 -9
  57. package/hud-provider.d.mts +0 -38
  58. package/hud-provider.js +0 -15
  59. package/hud.d.mts +0 -15
  60. package/hud.js +0 -18
  61. package/index.d.mts +0 -94
  62. package/index.js +0 -170
  63. package/lazy-panel.d.mts +0 -28
  64. package/lazy-panel.js +0 -11
  65. package/panels/cicd-panel.d.mts +0 -9
  66. package/panels/cicd-panel.js +0 -13
  67. package/panels/debug-info-panel.d.mts +0 -21
  68. package/panels/debug-info-panel.js +0 -9
  69. package/panels/dispatch-panel.d.mts +0 -10
  70. package/panels/dispatch-panel.js +0 -14
  71. package/panels/env-info-panel.d.mts +0 -9
  72. package/panels/env-info-panel.js +0 -12
  73. package/panels/feature-flags-panel.d.mts +0 -26
  74. package/panels/feature-flags-panel.js +0 -9
  75. package/panels/force-state-panel.d.mts +0 -11
  76. package/panels/force-state-panel.js +0 -113
  77. package/panels/info-row.d.mts +0 -21
  78. package/panels/info-row.js +0 -14
  79. package/panels/mock-seed-panel.d.mts +0 -15
  80. package/panels/mock-seed-panel.js +0 -14
  81. package/panels/network-console-panel.d.mts +0 -6
  82. package/panels/network-console-panel.js +0 -9
  83. package/panels/role-switcher-panel.d.mts +0 -28
  84. package/panels/role-switcher-panel.js +0 -9
  85. package/panels/sessions-panel.d.mts +0 -6
  86. package/panels/sessions-panel.js +0 -13
  87. package/panels/tokens-panel.d.mts +0 -38
  88. package/panels/tokens-panel.js +0 -9
  89. package/panels/unlock-panel.d.mts +0 -33
  90. package/panels/unlock-panel.js +0 -14
  91. package/panels/variants-panel.d.mts +0 -51
  92. package/panels/variants-panel.js +0 -12
  93. package/panels/vitals-panel.d.mts +0 -11
  94. package/panels/vitals-panel.js +0 -15
  95. package/server/guard.d.mts +0 -42
  96. package/server/guard.js +0 -12
  97. package/server/unlock.d.mts +0 -28
  98. package/server/unlock.js +0 -108
  99. package/state-boundary.d.mts +0 -25
  100. package/state-boundary.js +0 -54
package/README.md CHANGED
@@ -1,47 +1,128 @@
1
1
  # @hansenexus/hud
2
2
 
3
- A dev HUD for React apps. It gives you a drawer with panels (vitals, network and console
4
- capture, env info, feature flags, forced UI states and more), and capability-gated access
5
- that stays observe-only in production.
3
+ A dev HUD for React apps: a status pill and floating panels, rendered in a shadow root so a
4
+ client site's CSS cannot touch it and it cannot touch the site.
5
+
6
+ > Early. The element grabber is tracked in
7
+ > [the PRD](https://github.com/hansenexus/hud/issues/1).
8
+
9
+ ## Develop
10
+
11
+ ```sh
12
+ bun install
13
+ bun run dev # playground on http://localhost:5173, HUD imported from source (HMR)
14
+ bun run check # lint, typecheck, test, build
15
+ ```
16
+
17
+ The playground (`playground/`) is a fake client site with every element that has fought a dev
18
+ tool for a corner: sticky nav, a fixed booking CTA, a chat launcher and a cookie banner, plus a
19
+ deliberately hostile stylesheet. If the HUD changes when you edit `playground/src/site.css`, the
20
+ isolation leaks.
6
21
 
7
22
  ## Install
8
23
 
9
24
  ```sh
10
- npm install @hansenexus/hud
25
+ bun add -d @hansenexus/hud # or npm i -D @hansenexus/hud
11
26
  ```
12
27
 
13
- The host provides the peers `react` and `react-dom` 19. `next` is optional: without Next,
14
- alias `next/link` to a plain anchor component in your bundler.
28
+ Peer dependencies: `react` and `react-dom` 19. Nothing else: no Tailwind, no stylesheet to import,
29
+ no host CSS assumptions. The HUD ships its own styles inside its shadow root.
15
30
 
16
- ## Use
31
+ ## Use (Next.js)
17
32
 
18
- ```tsx
19
- import { Hud, type HudPanel, NetworkConsolePanel, VitalsPanel } from "@hansenexus/hud";
20
- import { Activity, Terminal } from "lucide-react";
33
+ Three files. The HUD definition, client only:
21
34
 
22
- const panels: HudPanel[] = [
23
- { id: "vitals", label: "Vitals", icon: Activity, capability: "observe.app", content: <VitalsPanel />, order: 1 },
24
- { id: "net", label: "Net / Console", icon: Terminal, capability: "observe.app", content: <NetworkConsolePanel />, order: 2 },
25
- ];
35
+ ```tsx
36
+ // src/components/dev/hud.tsx
37
+ import { defineHud } from "@hansenexus/hud";
38
+ import { vitals } from "@hansenexus/hud/plugins/vitals";
26
39
 
27
- export function DevHud() {
28
- return <Hud config={{ appSlug: "my-app" }} panels={panels} />;
29
- }
40
+ export default defineHud({ app: "my-app", plugins: [vitals()] });
30
41
  ```
31
42
 
32
- Mount it only in development builds, for example behind `import.meta.env.DEV` and a lazy import.
43
+ The loader, with the build gate as a literal expression around the `import()`. Next inlines both
44
+ env reads, so a production build folds the ternary to `null` and emits no HUD chunk:
45
+
46
+ ```tsx
47
+ // src/components/dev/hud-loader.tsx
48
+ "use client";
49
+ import { createHudLoader } from "@hansenexus/hud/next";
50
+
51
+ export const HudLoader = createHudLoader(
52
+ process.env.NODE_ENV === "development" || process.env.NEXT_PUBLIC_HUD_ENABLED === "true"
53
+ ? () => import("./hud")
54
+ : null
55
+ );
56
+ ```
33
57
 
34
- ## Tailwind v4
58
+ Render `<HudLoader />` in the root layout. Plugin routes mount through one catch-all route, fed
59
+ the plugins' server halves:
35
60
 
36
- The panels use Tailwind utilities, and Tailwind does not scan `node_modules` on its own. Add
37
- the package as a source in the stylesheet that imports Tailwind:
61
+ ```ts
62
+ // src/app/api/hud/[...hud]/route.ts
63
+ import { createHudHandler } from "@hansenexus/hud/server";
38
64
 
39
- ```css
40
- @source "../node_modules/@hansenexus/hud";
65
+ export const { GET, POST, PUT, PATCH, DELETE } = createHudHandler([/* plugins/<id>/server */]);
41
66
  ```
42
67
 
43
- The path is relative to that stylesheet. If a panel renders unstyled, this line is missing
44
- or points at the wrong place.
68
+ `createHudHandler` answers 404 to everything unless `NODE_ENV` is `development`; pass `guard` to
69
+ open deployed tiers deliberately. `HUD_DEV_MARKER` is rendered on the HUD root for a CI grep.
70
+
71
+ ## Write a plugin
72
+
73
+ A plugin is `{ id, title?, capability?, panel?, pill?, commands?, routes? }`, shipped as two
74
+ halves with the same `id`: the client half (`panel`, `pill`, `commands`) and the server half
75
+ (`routes`), so route code never reaches a client bundle. The panel is a plain React component
76
+ receiving `ctx`:
77
+
78
+ | `ctx` | does |
79
+ | ------------- | --------------------------------------------------------------------- |
80
+ | `can(cap)` | capability check (core grants all; the `access` plugin narrows it) |
81
+ | `api(path)` | JSON fetch from the plugin's routes at `/api/hud/<id>/<path>` |
82
+ | `pin(edge)` | pin the panel to `left`, `right` or `bottom` |
83
+ | `close()` | close the panel |
84
+
85
+ Routes are keyed by path, then method: `routes: { "/runs/:id": { GET: (req, { params }) => ... } }`.
86
+ An unknown path answers 404, a known path with another method 405.
87
+
88
+ `playground/src/plugins/server-info` is a working example: the playground serves its routes
89
+ through MSW with the same `createHudHandler` an app mounts.
90
+
91
+ ## Panels and shortcuts
92
+
93
+ - Open panels from the pill (`⋯` lists every panel and command). Drag by the title bar; a click
94
+ raises a panel.
95
+ - Pin a panel with ◧ ⬓ ◨ or by dropping it at the left, right or bottom edge. Pinning pushes the
96
+ page (padding on `<html>`) instead of covering it; pins on one edge stack. The host's
97
+ `position: fixed` elements and media queries still see the full window.
98
+ - The layout (open panels, positions, pins) is kept per `appSlug` in `localStorage`.
99
+ `⋯ → Reset layout` clears it.
100
+ - Below 768 px panels open as one full-width bottom sheet with a tab per panel; no pinning.
101
+ - `⌘⇧H` (Ctrl+Shift+H elsewhere) hides and restores the layout. `⌘⇧G` is reserved for the
102
+ element grabber.
103
+
104
+ A plugin's panel can pin itself with `ctx.pin("right")`. Plugin `commands` join the registry as
105
+ `<plugin id>.<command id>`, appear in the `⋯` menu and bind their `shortcut`.
106
+
107
+ ## Release
108
+
109
+ Versions are driven by [Changesets](https://github.com/changesets/changesets). A PR that changes
110
+ what the package ships adds one with `bun run changeset` (pre-1.0: `minor` for breaking changes,
111
+ `patch` for the rest). On `main`, `.github/workflows/release.yml` gathers pending changesets into
112
+ a "chore: version packages" PR. Merging that PR publishes the new version to npm over Trusted
113
+ Publishing (GitHub OIDC, no `NPM_TOKEN`), tags `v<version>` and cuts a GitHub release from the
114
+ CHANGELOG section. `Actions > Release > Run workflow` with `dry_run` runs the gate and
115
+ `npm publish --dry-run` without publishing.
116
+
117
+ Versions up to 0.1.1 were published from hn-monorepo; 0.2.0 is the first release from this repo.
118
+
119
+ Provenance is off while the repo is private (npm refuses `--provenance` from a private source).
120
+ When the repo goes public, set `NPM_PROVENANCE: "true"` in `release.yml`.
121
+
122
+ **Owner step, once, needs npm 2FA:** npmjs.com → `@hansenexus/hud` → Settings → Trusted
123
+ Publisher → GitHub Actions: organization `hansenexus`, repository `hud`, workflow `release.yml`.
124
+ Do this before merging the first Version Packages PR. Keep the hn-monorepo publisher until
125
+ hn-monorepo#2740 retires its old release workflow.
45
126
 
46
127
  ## License
47
128
 
@@ -0,0 +1,31 @@
1
+ import { a as HudPlugin, n as HudCapability } from "./plugins-1QmTmZyL.js";
2
+ import { ComponentType } from "react";
3
+ //#region src/core/define-hud.d.ts
4
+ interface DefineHudOptions {
5
+ /** App slug: labels the pill and keys the persisted layout. */
6
+ app: string;
7
+ /** Client halves (`@hansenexus/hud/plugins/<id>`), in pill order. */
8
+ plugins: readonly HudPlugin[];
9
+ /** Base path of the catch-all route serving `createHudHandler`. Default "/api/hud". */
10
+ apiBase?: string;
11
+ /** Capability check. Default: grant all (core assumes development). */
12
+ can?: (capability: HudCapability) => boolean;
13
+ }
14
+ /** Props the loader passes in at mount time. */
15
+ interface HudMountProps {
16
+ /** Badge at the start of the pill. Default "DEV". */
17
+ envLabel?: string;
18
+ }
19
+ /**
20
+ * The app's one HUD file:
21
+ *
22
+ * ```tsx
23
+ * export default defineHud({ app: "hansenexus", plugins: [vitals()] });
24
+ * ```
25
+ *
26
+ * Returns a component. The plugin list is frozen here, so the shell sees a
27
+ * stable array across renders.
28
+ */
29
+ declare function defineHud({ app, plugins, apiBase, can }: DefineHudOptions): ComponentType<HudMountProps>;
30
+ //#endregion
31
+ export { HudMountProps as n, defineHud as r, DefineHudOptions as t };
@@ -0,0 +1,36 @@
1
+ import { a as HudPlugin, d as PinEdge, f as definePlugin, n as HudCapability, o as HudPluginContext, r as HudCommand, s as HudPluginProps, t as HudApiError } from "./plugins-1QmTmZyL.js";
2
+ import { n as HudMountProps, r as defineHud, t as DefineHudOptions } from "./define-hud-BHQR8Lbv.js";
3
+ //#region src/core/marker.d.ts
4
+ /**
5
+ * Literal a consumer's CI bundle gate greps for in production client chunks.
6
+ * It is rendered as an attribute on the HUD root, so a bundler cannot drop it
7
+ * while keeping the HUD, and it is unmistakably deliberate rather than an
8
+ * incidental string a refactor could rename away.
9
+ */
10
+ export declare const HUD_DEV_MARKER = "__HANSENEXUS_HUD_DEV__";
11
+ /** Tag name of the element that hosts the HUD's shadow root. */
12
+ export declare const HUD_HOST_TAG = "hn-hud";
13
+ //#endregion
14
+ //#region src/shell/hud.d.ts
15
+ interface HudProps {
16
+ /** App slug: labels the pill and keys the persisted layout. */
17
+ appSlug: string;
18
+ /** Badge shown at the start of the pill. Default "DEV". */
19
+ envLabel?: string;
20
+ /** Client halves of the plugins, in pill order. Keep the array stable across renders. */
21
+ plugins?: readonly HudPlugin[];
22
+ /** Base path of the plugin routes. Default "/api/hud". */
23
+ apiBase?: string;
24
+ /** Capability check. Default: grant all (core assumes development). Keep it stable. */
25
+ can?: (capability: HudCapability) => boolean;
26
+ }
27
+ /**
28
+ * The HUD shell: a shadow-rooted status pill and floating panels.
29
+ *
30
+ * It renders unconditionally. Keeping it out of production builds is the
31
+ * loader's job, at the import site, so a bundler can drop the whole module
32
+ * (see `@hansenexus/hud/next`).
33
+ */
34
+ export declare function Hud({ appSlug, envLabel, plugins, apiBase, can }: HudProps): import("react").JSX.Element;
35
+ //#endregion
36
+ export { type DefineHudOptions, HudApiError, type HudCapability, type HudCommand, type HudMountProps, type HudPlugin, type HudPluginContext, type HudPluginProps, type HudProps, type PinEdge, defineHud, definePlugin };