@brendanbank/atrium-host-types 0.15.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/LICENCE.md ADDED
@@ -0,0 +1,25 @@
1
+ # BSD 2-Clause License
2
+
3
+ Copyright (c) 2026, Brendan Bank
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice,
9
+ this list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25
+ POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # `@brendanbank/atrium-host-types`
2
+
3
+ TypeScript declarations for the [atrium](https://github.com/brendanbank/atrium)
4
+ host-extension contract.
5
+
6
+ ```ts
7
+ import type {
8
+ AtriumRegistry,
9
+ AtriumNotification,
10
+ AtriumEvent,
11
+ UserContext,
12
+ AdminUserRow,
13
+ } from '@brendanbank/atrium-host-types';
14
+
15
+ const reg = window.__ATRIUM_REGISTRY__ as AtriumRegistry;
16
+ reg.registerHomeWidget({ key: 'my-card', render: () => /* ... */ });
17
+ ```
18
+
19
+ The package is **types-only** — no runtime, no JS payload at runtime.
20
+ Bundlers strip it from the production output.
21
+
22
+ ## Installation
23
+
24
+ The package is published on **GitHub Packages**, not the public npm
25
+ registry. Add an `.npmrc` to your host project that points the
26
+ `@brendanbank` scope at GitHub Packages:
27
+
28
+ ```
29
+ # .npmrc
30
+ @brendanbank:registry=https://npm.pkg.github.com
31
+ ```
32
+
33
+ GitHub Packages requires an authenticated install even for public
34
+ packages — give npm a token with at least the `read:packages` scope:
35
+
36
+ ```
37
+ //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
38
+ ```
39
+
40
+ In CI, `GITHUB_TOKEN` is provided automatically by Actions; locally,
41
+ generate a [classic PAT](https://github.com/settings/tokens) with
42
+ `read:packages` and export it. Then:
43
+
44
+ ```bash
45
+ pnpm add -D @brendanbank/atrium-host-types
46
+ ```
47
+
48
+ ## Versioning
49
+
50
+ The package version tracks atrium's image version. Pin `^0.15` for
51
+ "compatible with atrium 0.15.x"; bump together with the atrium image
52
+ to pick up new registry slots. New slots land as **optional** members
53
+ on `AtriumRegistry` first so a host that hasn't bumped yet still
54
+ type-checks.
55
+
56
+ ## What ships
57
+
58
+ - `AtriumRegistry` — every `register*` slot atrium exposes plus
59
+ `subscribeEvent`.
60
+ - `UserContext` — `/users/me/context` shape.
61
+ - `AdminUserRow` — `/admin/users` row shape.
62
+ - `AtriumNotification` — notification row body.
63
+ - `AtriumEvent` / `AtriumEventHandler` — SSE typed event shape.
64
+ - Per-slot option-bag types: `HomeWidget`, `RouteEntry`, `NavItem`,
65
+ `AdminTab`, `ProfileItem`, `NotificationKindRenderer`,
66
+ `LocaleOverlay`.
67
+ - `ProfileSlot`, `HomeWidgetWidth` — string unions.
68
+ - A `declare global` block that types `window.React`,
69
+ `window.__ATRIUM_VERSION__`, and `window.__ATRIUM_REGISTRY__`.
70
+
71
+ ## See also
72
+
73
+ - [`@brendanbank/atrium-host-bundle-utils`](../host-bundle-utils/) —
74
+ runtime helpers, Vite preset, and React hooks. Re-exports the types
75
+ from this package, so a host adding only one dep still gets the
76
+ declarations.
77
+ - [`docs/published-images.md`](../../docs/published-images.md) — the
78
+ full host-extension contract (image catalogue, loader behaviour,
79
+ registry semantics).
80
+ - [`docs/compat-matrix.md`](../../docs/compat-matrix.md) — which
81
+ atrium release added which slot.
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Source-of-truth TypeScript declarations for the atrium host-extension
3
+ * contract. A host bundle imports these types so its registration calls
4
+ * are checked against the same shapes atrium's own consumer components
5
+ * read.
6
+ *
7
+ * The package version tracks atrium's image version (`^0.14` of this
8
+ * package implies "compatible with atrium 0.14.x"). New registry slots
9
+ * land here when they ship in atrium; bumping the package picks them
10
+ * up. A host that calls a method missing on the pinned major gets a
11
+ * TypeScript error before runtime — the runtime Proxy on
12
+ * `window.__ATRIUM_REGISTRY__` is a belt-and-braces fallback for
13
+ * version skew, not a substitute for typing.
14
+ */
15
+ import type { ReactElement } from 'react';
16
+ /** Shape of `GET /users/me/context` — the atrium-owned RBAC view of the
17
+ * signed-in user. The bundled hook in `@brendanbank/atrium-host-bundle-utils/react`
18
+ * returns this exact shape. */
19
+ export interface UserContext {
20
+ id: number;
21
+ email: string;
22
+ full_name: string;
23
+ is_active: boolean;
24
+ roles: string[];
25
+ permissions: string[];
26
+ impersonating_from: {
27
+ id: number;
28
+ email: string;
29
+ full_name: string;
30
+ } | null;
31
+ }
32
+ /** Shape of `GET /admin/users` rows (and `/admin/users/{id}` responses).
33
+ * Hosts that render their own admin UIs against atrium's user list
34
+ * read this shape. The patch endpoint accepts `role_ids`; the read
35
+ * shape carries both `role_ids` and the human-readable `roles` codes. */
36
+ export interface AdminUserRow {
37
+ id: number;
38
+ email: string;
39
+ is_active: boolean;
40
+ is_verified: boolean;
41
+ full_name: string;
42
+ phone: string | null;
43
+ preferred_language: 'en' | 'nl';
44
+ role_ids: number[];
45
+ roles: string[];
46
+ }
47
+ /** A single notification row as the SPA sees it. `kind` is host-defined;
48
+ * `payload` is opaque JSON. `read_at` is null until the user clears the
49
+ * bell; `created_at` is the server-side timestamp. */
50
+ export interface AtriumNotification {
51
+ id: number;
52
+ kind: string;
53
+ payload: Record<string, unknown>;
54
+ read_at: string | null;
55
+ created_at: string;
56
+ }
57
+ /** SSE `notification` event published by `app.services.notifications.notify_user`.
58
+ * Same wire shape as the row's body — `kind` matches what
59
+ * `registerNotificationKind` listens on. */
60
+ export interface AtriumEvent {
61
+ kind: string;
62
+ payload: Record<string, unknown>;
63
+ }
64
+ export type AtriumEventHandler = (event: AtriumEvent) => void;
65
+ /** Layout width for a home-page widget. `narrow` matches atrium's default
66
+ * 680px column; `wide` extends to a comfortable dashboard column; `full`
67
+ * takes the whole panel inside `AppShell.Main`. Default `narrow`. */
68
+ export type HomeWidgetWidth = 'narrow' | 'wide' | 'full';
69
+ export interface HomeWidget {
70
+ key: string;
71
+ render: () => ReactElement;
72
+ width?: HomeWidgetWidth;
73
+ }
74
+ export interface RouteEntry {
75
+ key: string;
76
+ path: string;
77
+ /** Returns a fresh element each time the route mounts. Strongly
78
+ * preferred — see the host-bundle wrapper-element rationale in
79
+ * `docs/published-images.md`. Exactly one of `render` / `element`
80
+ * must be supplied. */
81
+ render?: () => ReactElement;
82
+ /** @deprecated Pass `render: () => element` instead. Captured
83
+ * elements share the same DOM node across navigations and can carry
84
+ * stale state. Kept for back-compat with hosts built against
85
+ * pre-0.12 atrium. */
86
+ element?: ReactElement;
87
+ /** Default `true`. Set false for public routes (e.g. host landing pages). */
88
+ requireAuth?: boolean;
89
+ /** Default `'shell'`. `'bare'` skips atrium's AppLayout wrapper. */
90
+ layout?: 'shell' | 'bare';
91
+ }
92
+ export interface NavItem {
93
+ key: string;
94
+ label: string;
95
+ to: string;
96
+ icon?: ReactElement;
97
+ /** Optional visibility predicate. Default: always visible. `me` is
98
+ * null until the SPA's auth probe resolves. */
99
+ condition?: (ctx: {
100
+ me: UserContext | null;
101
+ }) => boolean;
102
+ }
103
+ export interface AdminTab {
104
+ key: string;
105
+ label: string;
106
+ icon?: ReactElement;
107
+ /** Permission code; the tab is hidden for users without it. Omit to
108
+ * show the tab to every viewer with admin access. */
109
+ perm?: string;
110
+ /** Returns a fresh element each time the tab is selected. Strongly
111
+ * preferred — see `RouteEntry.render`. Exactly one of `render` /
112
+ * `element` must be supplied. */
113
+ render?: () => ReactElement;
114
+ /** @deprecated Pass `render: () => element` instead. */
115
+ element?: ReactElement;
116
+ }
117
+ /** Insertion slot inside `ProfilePage`'s vertical card stack. Default
118
+ * `'after-roles'` — the natural place for extra preferences. */
119
+ export type ProfileSlot = 'after-profile' | 'after-password' | 'after-2fa' | 'after-roles' | 'after-sessions' | 'before-delete';
120
+ export interface ProfileItem {
121
+ key: string;
122
+ /** Insertion slot. Default `'after-roles'`. */
123
+ slot?: ProfileSlot;
124
+ /** Optional visibility predicate. The profile page early-returns on a
125
+ * missing user, so `me` is never null when this fires. */
126
+ condition?: (ctx: {
127
+ me: UserContext;
128
+ }) => boolean;
129
+ render: () => ReactElement;
130
+ }
131
+ /** Per-kind renderer for a notification row. Atrium emits `{kind,
132
+ * payload}` rows but ships no built-in formatting — host apps register
133
+ * one renderer per kind they care about. */
134
+ export interface NotificationKindRenderer {
135
+ /** Notification `kind` string this renderer handles. Match is exact;
136
+ * no glob / prefix matching. Duplicate registrations for the same
137
+ * kind warn and last-write-wins. */
138
+ kind: string;
139
+ /** Detail-modal body. Receives the full row. */
140
+ render: (n: AtriumNotification) => ReactElement;
141
+ /** Compact summary for the bell + inbox row line and the modal title. */
142
+ title?: (n: AtriumNotification) => string;
143
+ /** App-internal href. When set, the row click navigates instead of
144
+ * opening the detail modal. */
145
+ href?: (n: AtriumNotification) => string;
146
+ }
147
+ /** Locale overlay registered by a host. `strings` is a flat
148
+ * i18next-style bundle (dot-paths or nested object — both accepted).
149
+ * Layered on top of atrium's shipped locale + admin overrides;
150
+ * precedence is shipped < admin overrides < host overlay. */
151
+ export interface LocaleOverlay {
152
+ locale: string;
153
+ strings: Record<string, unknown>;
154
+ }
155
+ /** The runtime-discovered registry exposed at `window.__ATRIUM_REGISTRY__`.
156
+ *
157
+ * Optional methods are slots that landed in a later atrium release
158
+ * than the one that introduced this type — type-narrow before calling
159
+ * if you intend to support older images, or use the runtime
160
+ * `__ATRIUM_VERSION__` global for explicit gating. The Proxy on the
161
+ * registry already turns calls to missing methods into a console
162
+ * warning + no-op; this is for clean TypeScript checking on top of
163
+ * that runtime safety net.
164
+ *
165
+ * When atrium adds a new slot, this type bumps and the slot becomes
166
+ * optional first; once a release window has passed and `^0.X` no
167
+ * longer covers the pre-slot images, the slot becomes required. */
168
+ export interface AtriumRegistry {
169
+ registerHomeWidget: (widget: HomeWidget) => void;
170
+ registerRoute: (route: RouteEntry) => void;
171
+ registerNavItem: (item: NavItem) => void;
172
+ registerAdminTab: (tab: AdminTab) => void;
173
+ registerProfileItem: (item: ProfileItem) => void;
174
+ registerNotificationKind: (renderer: NotificationKindRenderer) => void;
175
+ registerLocale: (overlay: LocaleOverlay) => void;
176
+ subscribeEvent: (kind: string, handler: AtriumEventHandler) => () => void;
177
+ }
178
+ declare global {
179
+ interface Window {
180
+ /** atrium's React instance. Host bundles read this so their
181
+ * wrapper elements can call `React.createElement(...)` without
182
+ * bundling a second React copy that atrium's reconciler would
183
+ * refuse to render. Available since the host-bundle pattern
184
+ * shipped (atrium 0.10+). */
185
+ React?: typeof import('react');
186
+ /** Backend version string (e.g. "0.14.0") mirrored from
187
+ * `GET /app-config` before the host bundle is imported, so
188
+ * import-time code can branch on it. Available since atrium
189
+ * 0.14.0; treat as best-effort on older images. */
190
+ __ATRIUM_VERSION__?: string;
191
+ /** The registry. Populated by atrium before the host bundle
192
+ * imports; host bundles read it and call its `register*` methods
193
+ * at module init. */
194
+ __ATRIUM_REGISTRY__?: AtriumRegistry;
195
+ }
196
+ }
197
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ // Copyright (c) 2026 Brendan Bank
2
+ // SPDX-License-Identifier: BSD-2-Clause
3
+ export {};
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@brendanbank/atrium-host-types",
3
+ "version": "0.15.0",
4
+ "description": "TypeScript declarations for the atrium host-extension contract: AtriumRegistry, UserContext, AdminUserRow, AtriumNotification, AtriumEvent. Source-of-truth shapes a host bundle types against, kept in sync with atrium's published image major.minor.",
5
+ "license": "BSD-2-Clause",
6
+ "type": "module",
7
+ "files": [
8
+ "dist",
9
+ "README.md"
10
+ ],
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ }
18
+ },
19
+ "peerDependencies": {
20
+ "react": "^18.0.0 || ^19.0.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/react": "^19.2.14",
24
+ "react": "^19.2.5",
25
+ "typescript": "^6.0.3"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/brendanbank/atrium.git",
33
+ "directory": "packages/host-types"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc -p tsconfig.json",
37
+ "typecheck": "tsc -p tsconfig.json --noEmit",
38
+ "clean": "rm -rf dist"
39
+ }
40
+ }