@hansenexus/hud 0.2.0 → 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.
- package/README.md +71 -9
- package/dist/define-hud-BHQR8Lbv.d.ts +31 -0
- package/dist/index.d.ts +12 -15
- package/dist/index.js +991 -192
- package/dist/next.d.ts +48 -0
- package/dist/next.js +77 -0
- package/dist/plugins/vitals.d.ts +25 -0
- package/dist/plugins/vitals.js +109 -0
- package/dist/plugins-1QmTmZyL.d.ts +76 -0
- package/dist/plugins-CzUiTn_r.js +68 -0
- package/dist/server.d.ts +39 -0
- package/dist/server.js +106 -0
- package/package.json +14 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
A dev HUD for React apps: a status pill and floating panels, rendered in a shadow root so a
|
|
4
4
|
client site's CSS cannot touch it and it cannot touch the site.
|
|
5
5
|
|
|
6
|
-
> Early. The
|
|
6
|
+
> Early. The element grabber is tracked in
|
|
7
7
|
> [the PRD](https://github.com/hansenexus/hud/issues/1).
|
|
8
8
|
|
|
9
9
|
## Develop
|
|
@@ -28,19 +28,81 @@ bun add -d @hansenexus/hud # or npm i -D @hansenexus/hud
|
|
|
28
28
|
Peer dependencies: `react` and `react-dom` 19. Nothing else: no Tailwind, no stylesheet to import,
|
|
29
29
|
no host CSS assumptions. The HUD ships its own styles inside its shadow root.
|
|
30
30
|
|
|
31
|
-
## Use
|
|
31
|
+
## Use (Next.js)
|
|
32
|
+
|
|
33
|
+
Three files. The HUD definition, client only:
|
|
32
34
|
|
|
33
35
|
```tsx
|
|
34
|
-
|
|
36
|
+
// src/components/dev/hud.tsx
|
|
37
|
+
import { defineHud } from "@hansenexus/hud";
|
|
38
|
+
import { vitals } from "@hansenexus/hud/plugins/vitals";
|
|
39
|
+
|
|
40
|
+
export default defineHud({ app: "my-app", plugins: [vitals()] });
|
|
41
|
+
```
|
|
42
|
+
|
|
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
|
+
```
|
|
57
|
+
|
|
58
|
+
Render `<HudLoader />` in the root layout. Plugin routes mount through one catch-all route, fed
|
|
59
|
+
the plugins' server halves:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
// src/app/api/hud/[...hud]/route.ts
|
|
63
|
+
import { createHudHandler } from "@hansenexus/hud/server";
|
|
35
64
|
|
|
36
|
-
export
|
|
37
|
-
return <Hud appSlug="my-app" />;
|
|
38
|
-
}
|
|
65
|
+
export const { GET, POST, PUT, PATCH, DELETE } = createHudHandler([/* plugins/<id>/server */]);
|
|
39
66
|
```
|
|
40
67
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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`.
|
|
44
106
|
|
|
45
107
|
## Release
|
|
46
108
|
|
|
@@ -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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
2
3
|
//#region src/core/marker.d.ts
|
|
3
4
|
/**
|
|
4
5
|
* Literal a consumer's CI bundle gate greps for in production client chunks.
|
|
@@ -12,28 +13,24 @@ export declare const HUD_HOST_TAG = "hn-hud";
|
|
|
12
13
|
//#endregion
|
|
13
14
|
//#region src/shell/hud.d.ts
|
|
14
15
|
interface HudProps {
|
|
15
|
-
/** App slug
|
|
16
|
+
/** App slug: labels the pill and keys the persisted layout. */
|
|
16
17
|
appSlug: string;
|
|
17
18
|
/** Badge shown at the start of the pill. Default "DEV". */
|
|
18
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;
|
|
19
26
|
}
|
|
20
27
|
/**
|
|
21
28
|
* The HUD shell: a shadow-rooted status pill and floating panels.
|
|
22
29
|
*
|
|
23
30
|
* It renders unconditionally. Keeping it out of production builds is the
|
|
24
31
|
* loader's job, at the import site, so a bundler can drop the whole module
|
|
25
|
-
* (see
|
|
32
|
+
* (see `@hansenexus/hud/next`).
|
|
26
33
|
*/
|
|
27
|
-
export declare function Hud({ appSlug, envLabel }: HudProps): import("react").JSX.Element;
|
|
34
|
+
export declare function Hud({ appSlug, envLabel, plugins, apiBase, can }: HudProps): import("react").JSX.Element;
|
|
28
35
|
//#endregion
|
|
29
|
-
|
|
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 };
|
|
36
|
+
export { type DefineHudOptions, HudApiError, type HudCapability, type HudCommand, type HudMountProps, type HudPlugin, type HudPluginContext, type HudPluginProps, type HudProps, type PinEdge, defineHud, definePlugin };
|