@consciousclouds/operator-shell 0.1.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/CHANGELOG.md +36 -0
- package/LICENSE +194 -0
- package/README.md +319 -0
- package/dist/chief/ChiefDrawer.d.ts +11 -0
- package/dist/chief/ChiefDrawer.d.ts.map +1 -0
- package/dist/chief/ChiefDrawer.js +49 -0
- package/dist/chief/ChiefDrawer.js.map +1 -0
- package/dist/chief/ChiefPanel.d.ts +10 -0
- package/dist/chief/ChiefPanel.d.ts.map +1 -0
- package/dist/chief/ChiefPanel.js +46 -0
- package/dist/chief/ChiefPanel.js.map +1 -0
- package/dist/chief/ChiefRegion.d.ts +9 -0
- package/dist/chief/ChiefRegion.d.ts.map +1 -0
- package/dist/chief/ChiefRegion.js +55 -0
- package/dist/chief/ChiefRegion.js.map +1 -0
- package/dist/chief/ChiefSession.d.ts +21 -0
- package/dist/chief/ChiefSession.d.ts.map +1 -0
- package/dist/chief/ChiefSession.js +9 -0
- package/dist/chief/ChiefSession.js.map +1 -0
- package/dist/chief/directive.d.ts +15 -0
- package/dist/chief/directive.d.ts.map +1 -0
- package/dist/chief/directive.js +20 -0
- package/dist/chief/directive.js.map +1 -0
- package/dist/chief/turn-adapter.d.ts +52 -0
- package/dist/chief/turn-adapter.d.ts.map +1 -0
- package/dist/chief/turn-adapter.js +23 -0
- package/dist/chief/turn-adapter.js.map +1 -0
- package/dist/chief/use-chief-shortcut.d.ts +17 -0
- package/dist/chief/use-chief-shortcut.d.ts.map +1 -0
- package/dist/chief/use-chief-shortcut.js +61 -0
- package/dist/chief/use-chief-shortcut.js.map +1 -0
- package/dist/chief/use-chief-turn.d.ts +50 -0
- package/dist/chief/use-chief-turn.d.ts.map +1 -0
- package/dist/chief/use-chief-turn.js +99 -0
- package/dist/chief/use-chief-turn.js.map +1 -0
- package/dist/flavor/types.d.ts +189 -0
- package/dist/flavor/types.d.ts.map +1 -0
- package/dist/flavor/types.js +13 -0
- package/dist/flavor/types.js.map +1 -0
- package/dist/frame/OperatorShell.d.ts +91 -0
- package/dist/frame/OperatorShell.d.ts.map +1 -0
- package/dist/frame/OperatorShell.js +54 -0
- package/dist/frame/OperatorShell.js.map +1 -0
- package/dist/guard/PermissionProvider.d.ts +49 -0
- package/dist/guard/PermissionProvider.d.ts.map +1 -0
- package/dist/guard/PermissionProvider.js +103 -0
- package/dist/guard/PermissionProvider.js.map +1 -0
- package/dist/guard/RouteGuard.d.ts +45 -0
- package/dist/guard/RouteGuard.d.ts.map +1 -0
- package/dist/guard/RouteGuard.js +59 -0
- package/dist/guard/RouteGuard.js.map +1 -0
- package/dist/guard/permissions.d.ts +88 -0
- package/dist/guard/permissions.d.ts.map +1 -0
- package/dist/guard/permissions.js +105 -0
- package/dist/guard/permissions.js.map +1 -0
- package/dist/header/EnvironmentSwitcher.d.ts +5 -0
- package/dist/header/EnvironmentSwitcher.d.ts.map +1 -0
- package/dist/header/EnvironmentSwitcher.js +51 -0
- package/dist/header/EnvironmentSwitcher.js.map +1 -0
- package/dist/header/OperatorHeader.d.ts +25 -0
- package/dist/header/OperatorHeader.d.ts.map +1 -0
- package/dist/header/OperatorHeader.js +13 -0
- package/dist/header/OperatorHeader.js.map +1 -0
- package/dist/header/OperatorProfileMenu.d.ts +2 -0
- package/dist/header/OperatorProfileMenu.d.ts.map +1 -0
- package/dist/header/OperatorProfileMenu.js +14 -0
- package/dist/header/OperatorProfileMenu.js.map +1 -0
- package/dist/header/ThemeToggle.d.ts +2 -0
- package/dist/header/ThemeToggle.d.ts.map +1 -0
- package/dist/header/ThemeToggle.js +17 -0
- package/dist/header/ThemeToggle.js.map +1 -0
- package/dist/icons/registry.d.ts +57 -0
- package/dist/icons/registry.d.ts.map +1 -0
- package/dist/icons/registry.js +83 -0
- package/dist/icons/registry.js.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +129 -0
- package/dist/index.js.map +1 -0
- package/dist/inspector/InspectorProvider.d.ts +41 -0
- package/dist/inspector/InspectorProvider.d.ts.map +1 -0
- package/dist/inspector/InspectorProvider.js +63 -0
- package/dist/inspector/InspectorProvider.js.map +1 -0
- package/dist/inspector/types.d.ts +91 -0
- package/dist/inspector/types.d.ts.map +1 -0
- package/dist/inspector/types.js +40 -0
- package/dist/inspector/types.js.map +1 -0
- package/dist/nav/routes.d.ts +39 -0
- package/dist/nav/routes.d.ts.map +1 -0
- package/dist/nav/routes.js +46 -0
- package/dist/nav/routes.js.map +1 -0
- package/dist/palette/CommandPalette.d.ts +40 -0
- package/dist/palette/CommandPalette.d.ts.map +1 -0
- package/dist/palette/CommandPalette.js +108 -0
- package/dist/palette/CommandPalette.js.map +1 -0
- package/dist/palette/commands.d.ts +58 -0
- package/dist/palette/commands.d.ts.map +1 -0
- package/dist/palette/commands.js +81 -0
- package/dist/palette/commands.js.map +1 -0
- package/dist/palette/search-sources.d.ts +126 -0
- package/dist/palette/search-sources.d.ts.map +1 -0
- package/dist/palette/search-sources.js +101 -0
- package/dist/palette/search-sources.js.map +1 -0
- package/dist/palette/use-search-results.d.ts +4 -0
- package/dist/palette/use-search-results.d.ts.map +1 -0
- package/dist/palette/use-search-results.js +51 -0
- package/dist/palette/use-search-results.js.map +1 -0
- package/dist/sidebar/OperatorSidebar.d.ts +44 -0
- package/dist/sidebar/OperatorSidebar.d.ts.map +1 -0
- package/dist/sidebar/OperatorSidebar.js +30 -0
- package/dist/sidebar/OperatorSidebar.js.map +1 -0
- package/dist/workspace/CanvasWorkspace.d.ts +78 -0
- package/dist/workspace/CanvasWorkspace.d.ts.map +1 -0
- package/dist/workspace/CanvasWorkspace.js +44 -0
- package/dist/workspace/CanvasWorkspace.js.map +1 -0
- package/dist/workspace/DashboardWorkspace.d.ts +91 -0
- package/dist/workspace/DashboardWorkspace.d.ts.map +1 -0
- package/dist/workspace/DashboardWorkspace.js +39 -0
- package/dist/workspace/DashboardWorkspace.js.map +1 -0
- package/dist/workspace/FormWorkspace.d.ts +107 -0
- package/dist/workspace/FormWorkspace.d.ts.map +1 -0
- package/dist/workspace/FormWorkspace.js +38 -0
- package/dist/workspace/FormWorkspace.js.map +1 -0
- package/dist/workspace/GraphWorkspace.d.ts +112 -0
- package/dist/workspace/GraphWorkspace.d.ts.map +1 -0
- package/dist/workspace/GraphWorkspace.js +147 -0
- package/dist/workspace/GraphWorkspace.js.map +1 -0
- package/dist/workspace/TableWorkspace.d.ts +74 -0
- package/dist/workspace/TableWorkspace.d.ts.map +1 -0
- package/dist/workspace/TableWorkspace.js +25 -0
- package/dist/workspace/TableWorkspace.js.map +1 -0
- package/dist/workspace/WorkspaceHost.d.ts +26 -0
- package/dist/workspace/WorkspaceHost.d.ts.map +1 -0
- package/dist/workspace/WorkspaceHost.js +21 -0
- package/dist/workspace/WorkspaceHost.js.map +1 -0
- package/dist/workspace/registry.d.ts +30 -0
- package/dist/workspace/registry.d.ts.map +1 -0
- package/dist/workspace/registry.js +33 -0
- package/dist/workspace/registry.js.map +1 -0
- package/dist/workspace/types.d.ts +79 -0
- package/dist/workspace/types.d.ts.map +1 -0
- package/dist/workspace/types.js +48 -0
- package/dist/workspace/types.js.map +1 -0
- package/package.json +64 -0
- package/styles/source.css +16 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OperatorFlavor — the surface's declarative manifest, per
|
|
3
|
+
* docs/design/operator-platform-contracts.md §2.
|
|
4
|
+
*
|
|
5
|
+
* A Flavor is INERT DATA: references the product implements elsewhere. No
|
|
6
|
+
* functions, no JSX, no components, no endpoint URLs for AI transport
|
|
7
|
+
* (Declarative-Flavor law). One Flavor per surface; it references a Brand by
|
|
8
|
+
* id and never owns it. The moment a Flavor runs logic it has become product
|
|
9
|
+
* code — the review test is that a Flavor file contains zero functions/JSX
|
|
10
|
+
* and zero transport URLs.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The product/project context block the shell's sidebar shows. Labels only —
|
|
14
|
+
* a static indicator, never a switcher (the shell renders it; the product
|
|
15
|
+
* declares it).
|
|
16
|
+
*/
|
|
17
|
+
export interface OperatorFlavorProduct {
|
|
18
|
+
/** Stable product/project id (e.g. `ccu-admin`). */
|
|
19
|
+
id: string;
|
|
20
|
+
/** Display name (e.g. `CCU Admin`). */
|
|
21
|
+
name: string;
|
|
22
|
+
/** Small over-line label (e.g. `Project Zero`). */
|
|
23
|
+
label?: string;
|
|
24
|
+
/** One-line descriptive note rendered under the name. */
|
|
25
|
+
note?: string;
|
|
26
|
+
}
|
|
27
|
+
/** One navigation destination. Everything here is a reference, not behavior. */
|
|
28
|
+
export interface OperatorNavItem {
|
|
29
|
+
id: string;
|
|
30
|
+
label: string;
|
|
31
|
+
/** App route string. Also derives the Chief in-console route set. */
|
|
32
|
+
href: string;
|
|
33
|
+
/**
|
|
34
|
+
* Icon by NAME, resolved through the shell's icon registry — never an
|
|
35
|
+
* imported component (icons stop being product code).
|
|
36
|
+
*/
|
|
37
|
+
icon: string;
|
|
38
|
+
/**
|
|
39
|
+
* Permission REFERENCE the product's injected resolver interprets
|
|
40
|
+
* (docs/design/operator-platform-contracts.md §5). Unknown/unresolved
|
|
41
|
+
* resolves DENIED — fail-closed.
|
|
42
|
+
*/
|
|
43
|
+
requiredPermission?: string;
|
|
44
|
+
}
|
|
45
|
+
/** A labeled group of navigation items. */
|
|
46
|
+
export interface OperatorNavGroup {
|
|
47
|
+
id: string;
|
|
48
|
+
label: string;
|
|
49
|
+
items: OperatorNavItem[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* One command-palette entry beyond the nav-derived set. INERT REFERENCES
|
|
53
|
+
* only (Declarative-Flavor law): exactly one of `href` (a navigation
|
|
54
|
+
* command) or `command` (a command REFERENCE the product's injected
|
|
55
|
+
* `runCommand` interprets — never a function in the Flavor). A `command`
|
|
56
|
+
* entry with no injected runner is HIDDEN (fail-closed: a reference nobody
|
|
57
|
+
* interprets cannot execute).
|
|
58
|
+
*/
|
|
59
|
+
export interface OperatorCommandItem {
|
|
60
|
+
id: string;
|
|
61
|
+
label: string;
|
|
62
|
+
/** Icon by NAME, resolved through the shell's icon registry. */
|
|
63
|
+
icon?: string;
|
|
64
|
+
/** Extra search terms the palette matches beyond the label. */
|
|
65
|
+
keywords?: string[];
|
|
66
|
+
/** Navigation command: an app route string (mutually exclusive with `command`). */
|
|
67
|
+
href?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Command REFERENCE the product's injected runner interprets (mutually
|
|
70
|
+
* exclusive with `href`). The Flavor names it; the product executes it.
|
|
71
|
+
*/
|
|
72
|
+
command?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Permission REFERENCE, resolved exactly like nav items
|
|
75
|
+
* (docs/design/operator-platform-contracts.md §5) — unknown ⇒ denied.
|
|
76
|
+
*/
|
|
77
|
+
requiredPermission?: string;
|
|
78
|
+
}
|
|
79
|
+
/** A labeled group of declarative palette commands. */
|
|
80
|
+
export interface OperatorCommandGroup {
|
|
81
|
+
id: string;
|
|
82
|
+
label: string;
|
|
83
|
+
items: OperatorCommandItem[];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Chief decoration — placement + look + inert refs ONLY (P1-1). No endpoint,
|
|
87
|
+
* no transport, no scope: the product's injected TurnAdapter carries the
|
|
88
|
+
* wire, and enrollment + the verified actor authorize Chief
|
|
89
|
+
* (Chief-authority law). A Flavor can move Chief's panel; it can never widen
|
|
90
|
+
* Chief's reach.
|
|
91
|
+
*/
|
|
92
|
+
export interface OperatorFlavorChief {
|
|
93
|
+
/**
|
|
94
|
+
* Where the AI Region mounts (defaults to `rail`). `rail`: the always-open
|
|
95
|
+
* inflow right rail — below the xl breakpoint (where the rail is hidden)
|
|
96
|
+
* the shell falls back to the drawer so Chief stays reachable. `drawer`:
|
|
97
|
+
* the same content behind the sheet primitive + a floating trigger, with
|
|
98
|
+
* assistant-surface's open/close lifecycle. Placement moves the panel and
|
|
99
|
+
* NOTHING else — turn flow, TurnAdapter seam, and the routed-directive
|
|
100
|
+
* guard are identical in every placement.
|
|
101
|
+
*/
|
|
102
|
+
placement?: "rail" | "drawer";
|
|
103
|
+
/** Panel display title (defaults to `Chief`). */
|
|
104
|
+
title?: string;
|
|
105
|
+
/** Idle hint shown before the first turn (plain text — no JSX). */
|
|
106
|
+
hint?: string;
|
|
107
|
+
/** Optional emphasized example question rendered after the hint. */
|
|
108
|
+
hintExample?: string;
|
|
109
|
+
/** Input placeholder (defaults to `Ask {title}…`). */
|
|
110
|
+
placeholder?: string;
|
|
111
|
+
/**
|
|
112
|
+
* Inert personality REFERENCE the product's TurnAdapter may read. Never an
|
|
113
|
+
* endpoint, never a credential, never authority.
|
|
114
|
+
*/
|
|
115
|
+
personalityRef?: string;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Workspace REFERENCE — one entry of the future workspace switcher (a
|
|
119
|
+
* sibling operating context of `product`: another project/console this
|
|
120
|
+
* operator could pivot the surface to). Labels/refs ONLY (Declarative-Flavor
|
|
121
|
+
* law) — no URL, no credentials, no transport; the product's runtime maps a
|
|
122
|
+
* workspace id to whatever it means.
|
|
123
|
+
*
|
|
124
|
+
* TYPED SEAM, INERT TODAY — GATED ON BACKEND PRE-1 (do not "activate" this
|
|
125
|
+
* from the frontend): per-(project, environment) enrollment keying
|
|
126
|
+
* (docs/design/ccu-control-plane.md §8) is the prerequisite for a second
|
|
127
|
+
* workspace existing at all. Until PRE-1 lands, declaring entries here
|
|
128
|
+
* changes nothing and the shell renders NO switcher — the static `product`
|
|
129
|
+
* block in the sidebar stays the whole context chrome. A missing switcher
|
|
130
|
+
* beats a fake one.
|
|
131
|
+
*/
|
|
132
|
+
export interface OperatorFlavorWorkspace {
|
|
133
|
+
/** Stable workspace/project id (e.g. `ccu-admin`). */
|
|
134
|
+
id: string;
|
|
135
|
+
/** Display name. */
|
|
136
|
+
name: string;
|
|
137
|
+
/** Small over-line label (mirrors OperatorFlavorProduct.label). */
|
|
138
|
+
label?: string;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Inspector declaration — the entity-kind REFERENCES this surface may
|
|
142
|
+
* inspect (inert strings; Declarative-Flavor law). The product injects the
|
|
143
|
+
* detail-view components through the shell's `inspectorViews` prop keyed by
|
|
144
|
+
* these references — components can't live in a Flavor. A kind missing here
|
|
145
|
+
* OR missing an injected view fails closed (no panel).
|
|
146
|
+
*/
|
|
147
|
+
export interface OperatorFlavorInspector {
|
|
148
|
+
/** Entity-kind ids (e.g. `personality`) the shell's inspector may open. */
|
|
149
|
+
entityKinds: string[];
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The Operator Flavor — the frontend dual of the Product Manifest: what runs
|
|
153
|
+
* on this operator surface and how it is arranged, resolved by the shell.
|
|
154
|
+
*/
|
|
155
|
+
export interface OperatorFlavor {
|
|
156
|
+
/** Unique flavor id (e.g. `ccu-admin`). One Flavor per surface. */
|
|
157
|
+
id: string;
|
|
158
|
+
/** Brand REFERENCE resolved by the Brand Platform — never an inline brand blob. */
|
|
159
|
+
brandId: string;
|
|
160
|
+
/** The product/project context block. */
|
|
161
|
+
product: OperatorFlavorProduct;
|
|
162
|
+
/** Groups → items; the shell's nav engine renders it. */
|
|
163
|
+
nav: OperatorNavGroup[];
|
|
164
|
+
/** Route string for the default surface. */
|
|
165
|
+
defaultLanding: string;
|
|
166
|
+
/**
|
|
167
|
+
* Optional palette commands beyond the nav-derived set (the palette always
|
|
168
|
+
* derives route commands from `nav`; these ADD entries, they never replace
|
|
169
|
+
* navigation). Inert references only — see OperatorCommandItem.
|
|
170
|
+
*/
|
|
171
|
+
commands?: OperatorCommandGroup[];
|
|
172
|
+
/** Chief placement/decoration (see OperatorFlavorChief). */
|
|
173
|
+
chief?: OperatorFlavorChief;
|
|
174
|
+
/**
|
|
175
|
+
* Inspector declaration — which entity kinds this surface may inspect
|
|
176
|
+
* (inert references only; see OperatorFlavorInspector). Absent ⇒ the
|
|
177
|
+
* inspector never opens on this surface.
|
|
178
|
+
*/
|
|
179
|
+
inspector?: OperatorFlavorInspector;
|
|
180
|
+
/**
|
|
181
|
+
* Workspace-switcher REFERENCES (typed seam, INERT until backend PRE-1 —
|
|
182
|
+
* see OperatorFlavorWorkspace for the gate). The shell ships the slot
|
|
183
|
+
* (OperatorShellProps.workspaceSwitcher); the product will one day inject
|
|
184
|
+
* the switcher component that interprets these refs. Today: declare
|
|
185
|
+
* nothing (CCU declares nothing), render nothing.
|
|
186
|
+
*/
|
|
187
|
+
workspaces?: OperatorFlavorWorkspace[];
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/flavor/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OperatorFlavor — the surface's declarative manifest, per
|
|
3
|
+
* docs/design/operator-platform-contracts.md §2.
|
|
4
|
+
*
|
|
5
|
+
* A Flavor is INERT DATA: references the product implements elsewhere. No
|
|
6
|
+
* functions, no JSX, no components, no endpoint URLs for AI transport
|
|
7
|
+
* (Declarative-Flavor law). One Flavor per surface; it references a Brand by
|
|
8
|
+
* id and never owns it. The moment a Flavor runs logic it has become product
|
|
9
|
+
* code — the review test is that a Flavor file contains zero functions/JSX
|
|
10
|
+
* and zero transport URLs.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/flavor/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OperatorShell — the operator-console frame (the one experience abstraction
|
|
3
|
+
* of an operator surface): full-width header, icon-collapsible sidebar, main
|
|
4
|
+
* content, and the AI Region (Chief — rail or drawer, resolved from
|
|
5
|
+
* flavor.chief.placement). Composed from the open shadcn
|
|
6
|
+
* registry's sidebar patterns (sidebar-16's header-above-sidebar geometry,
|
|
7
|
+
* sidebar-07's icon-collapsible rail) over @consciousclouds/ui primitives.
|
|
8
|
+
*
|
|
9
|
+
* The shell owns the experience; the product injects meaning:
|
|
10
|
+
* - `flavor` — the declarative surface manifest (nav, product context,
|
|
11
|
+
* palette commands, Chief decoration; inert data),
|
|
12
|
+
* - `turnAdapter` — Chief's transport (P1-1: the product owns the wire),
|
|
13
|
+
* - `resolvePermission` — the guard resolution (§5; fail-closed default),
|
|
14
|
+
* - `runCommand` — the executor for the Flavor's `command`-reference
|
|
15
|
+
* palette entries (same injection pattern as the resolver),
|
|
16
|
+
* - `inspectorViews` — the detail views for the Flavor's declared
|
|
17
|
+
* inspector entity-kind references (the shell owns the
|
|
18
|
+
* panel; the product owns the views),
|
|
19
|
+
* - `icons` / `profile` — vocabulary and identity slot fillings.
|
|
20
|
+
*
|
|
21
|
+
* The frame owns the command palette's open state so the header's search
|
|
22
|
+
* button and the ⌘K keybinding open the one shell-owned instance.
|
|
23
|
+
*
|
|
24
|
+
* Brand and theme arrive from the layers below (BrandProvider /
|
|
25
|
+
* ThemeProvider mounted by the app) — the shell only reads them.
|
|
26
|
+
*/
|
|
27
|
+
import * as React from "react";
|
|
28
|
+
import type { TurnAdapter } from "../chief/turn-adapter.js";
|
|
29
|
+
import type { OperatorFlavor } from "../flavor/types.js";
|
|
30
|
+
import type { PermissionResolver } from "../guard/permissions.js";
|
|
31
|
+
import type { IconRegistry } from "../icons/registry.js";
|
|
32
|
+
import type { InspectorViewRegistry } from "../inspector/types.js";
|
|
33
|
+
import type { SearchSource } from "../palette/search-sources.js";
|
|
34
|
+
export interface OperatorShellProps {
|
|
35
|
+
/** The surface's declarative manifest (inert data — see flavor/types.ts). */
|
|
36
|
+
flavor: OperatorFlavor;
|
|
37
|
+
/** The product's Chief transport (P1-1). No adapter → no wire; the shell ships none. */
|
|
38
|
+
turnAdapter: TurnAdapter;
|
|
39
|
+
/**
|
|
40
|
+
* The product's permission resolution (§5). Defaults to `unknown` for
|
|
41
|
+
* every reference — i.e. every gated item is DENIED until the product
|
|
42
|
+
* injects a resolver (fail-closed; pass `allowAllPermissions` for a
|
|
43
|
+
* dev-open posture).
|
|
44
|
+
*/
|
|
45
|
+
resolvePermission?: PermissionResolver;
|
|
46
|
+
/**
|
|
47
|
+
* Product-injected executor for the Flavor's `command`-reference palette
|
|
48
|
+
* entries (the Flavor names a reference; the product runs it). Without
|
|
49
|
+
* one, those entries are hidden — fail-closed; nav-derived route commands
|
|
50
|
+
* always work (the shell itself navigates).
|
|
51
|
+
*/
|
|
52
|
+
runCommand?: (commandRef: string) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Product-injected inspector detail views, keyed by the Flavor's declared
|
|
55
|
+
* `inspector.entityKinds` references (the same injection pattern as icons
|
|
56
|
+
* and the resolver — the Flavor names the reference, the product implements
|
|
57
|
+
* it). A kind that is undeclared OR view-less fails closed: no panel.
|
|
58
|
+
*/
|
|
59
|
+
inspectorViews?: InspectorViewRegistry;
|
|
60
|
+
/**
|
|
61
|
+
* Product-injected searchable sources for the ⌘K palette — the Search
|
|
62
|
+
* experience's injection slot (palette/search-sources.ts; the same
|
|
63
|
+
* injection pattern as icons / the resolver / `inspectorViews`). Each
|
|
64
|
+
* source is product code (its `query` runs the product's own reads — no
|
|
65
|
+
* transport in this package) and gates through the SAME fail-closed
|
|
66
|
+
* permission snapshot as the palette's commands; an erroring, in-flight,
|
|
67
|
+
* or absent source contributes nothing. Pass a STABLE array (module
|
|
68
|
+
* const / memo) — identity is the re-query key.
|
|
69
|
+
*/
|
|
70
|
+
searchSources?: readonly SearchSource[];
|
|
71
|
+
/** Product extensions to the icon registry (string name → Lucide icon). */
|
|
72
|
+
icons?: IconRegistry;
|
|
73
|
+
/** Product-injected profile dropdown; a neutral operator menu by default. */
|
|
74
|
+
profile?: React.ReactNode;
|
|
75
|
+
/** Header search-button copy (the button opens the ⌘K palette). */
|
|
76
|
+
searchPlaceholder?: string;
|
|
77
|
+
/**
|
|
78
|
+
* Workspace-switcher SLOT — typed seam, INERT until backend PRE-1
|
|
79
|
+
* (per-(project, environment) enrollment keying,
|
|
80
|
+
* docs/design/ccu-control-plane.md §8: the prerequisite for a second
|
|
81
|
+
* workspace existing at all). When PRE-1 lands, the product injects a
|
|
82
|
+
* switcher here that interprets the Flavor's `workspaces` references
|
|
83
|
+
* (same injection pattern as icons / the resolver / inspectorViews).
|
|
84
|
+
* Until then products pass nothing, the shell renders nothing, and the
|
|
85
|
+
* sidebar's static product block stays the whole context chrome.
|
|
86
|
+
*/
|
|
87
|
+
workspaceSwitcher?: React.ReactNode;
|
|
88
|
+
children: React.ReactNode;
|
|
89
|
+
}
|
|
90
|
+
export declare function OperatorShell({ flavor, turnAdapter, resolvePermission, runCommand, inspectorViews, searchSources, icons, profile, searchPlaceholder, workspaceSwitcher, children, }: OperatorShellProps): import("react/jsx-runtime").JSX.Element;
|
|
91
|
+
//# sourceMappingURL=OperatorShell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OperatorShell.d.ts","sourceRoot":"","sources":["../../src/frame/OperatorShell.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAGlE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGzD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAEnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAGjE,MAAM,WAAW,kBAAkB;IACjC,6EAA6E;IAC7E,MAAM,EAAE,cAAc,CAAC;IACvB,wFAAwF;IACxF,WAAW,EAAE,WAAW,CAAC;IACzB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,kBAAkB,CAAC;IACvC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C;;;;;OAKG;IACH,cAAc,CAAC,EAAE,qBAAqB,CAAC;IACvC;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IACxC,2EAA2E;IAC3E,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,mEAAmE;IACnE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACpC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,WAAW,EACX,iBAAsC,EACtC,UAAU,EACV,cAAc,EACd,aAAa,EACb,KAAK,EACL,OAAO,EACP,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,GACT,EAAE,kBAAkB,2CAyDpB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* OperatorShell — the operator-console frame (the one experience abstraction
|
|
5
|
+
* of an operator surface): full-width header, icon-collapsible sidebar, main
|
|
6
|
+
* content, and the AI Region (Chief — rail or drawer, resolved from
|
|
7
|
+
* flavor.chief.placement). Composed from the open shadcn
|
|
8
|
+
* registry's sidebar patterns (sidebar-16's header-above-sidebar geometry,
|
|
9
|
+
* sidebar-07's icon-collapsible rail) over @consciousclouds/ui primitives.
|
|
10
|
+
*
|
|
11
|
+
* The shell owns the experience; the product injects meaning:
|
|
12
|
+
* - `flavor` — the declarative surface manifest (nav, product context,
|
|
13
|
+
* palette commands, Chief decoration; inert data),
|
|
14
|
+
* - `turnAdapter` — Chief's transport (P1-1: the product owns the wire),
|
|
15
|
+
* - `resolvePermission` — the guard resolution (§5; fail-closed default),
|
|
16
|
+
* - `runCommand` — the executor for the Flavor's `command`-reference
|
|
17
|
+
* palette entries (same injection pattern as the resolver),
|
|
18
|
+
* - `inspectorViews` — the detail views for the Flavor's declared
|
|
19
|
+
* inspector entity-kind references (the shell owns the
|
|
20
|
+
* panel; the product owns the views),
|
|
21
|
+
* - `icons` / `profile` — vocabulary and identity slot fillings.
|
|
22
|
+
*
|
|
23
|
+
* The frame owns the command palette's open state so the header's search
|
|
24
|
+
* button and the ⌘K keybinding open the one shell-owned instance.
|
|
25
|
+
*
|
|
26
|
+
* Brand and theme arrive from the layers below (BrandProvider /
|
|
27
|
+
* ThemeProvider mounted by the app) — the shell only reads them.
|
|
28
|
+
*/
|
|
29
|
+
import * as React from "react";
|
|
30
|
+
import { useBrand } from "@consciousclouds/brand";
|
|
31
|
+
import { SidebarInset, SidebarProvider } from "@consciousclouds/ui";
|
|
32
|
+
import { ChiefRegion } from "../chief/ChiefRegion.js";
|
|
33
|
+
import { PermissionProvider } from "../guard/PermissionProvider.js";
|
|
34
|
+
import { unknownPermissions } from "../guard/permissions.js";
|
|
35
|
+
import { OperatorHeader } from "../header/OperatorHeader.js";
|
|
36
|
+
import { resolveIconFamily } from "../icons/registry.js";
|
|
37
|
+
import { InspectorProvider } from "../inspector/InspectorProvider.js";
|
|
38
|
+
import { CommandPalette } from "../palette/CommandPalette.js";
|
|
39
|
+
import { OperatorSidebar } from "../sidebar/OperatorSidebar.js";
|
|
40
|
+
export function OperatorShell({ flavor, turnAdapter, resolvePermission = unknownPermissions, runCommand, inspectorViews, searchSources, icons, profile, searchPlaceholder, workspaceSwitcher, children, }) {
|
|
41
|
+
const [paletteOpen, setPaletteOpen] = React.useState(false);
|
|
42
|
+
// The Brand's icon-family declaration (inert data from the layer below),
|
|
43
|
+
// READ-AND-DEFAULTED here: the shell ships exactly one family (lucide, per
|
|
44
|
+
// the license allowlist), so any other declaration resolves to it —
|
|
45
|
+
// fail-closed, deterministic. When a second family ships in the registry,
|
|
46
|
+
// this same read starts selecting between built-in vocabularies.
|
|
47
|
+
const brand = useBrand();
|
|
48
|
+
const iconFamily = resolveIconFamily(brand.iconFamily);
|
|
49
|
+
return (
|
|
50
|
+
// The guard context: pages below read the SAME injected resolution the
|
|
51
|
+
// sidebar and palette gate on (usePermission / RouteGuard, contracts §5).
|
|
52
|
+
_jsx(PermissionProvider, { resolver: resolvePermission, children: _jsx(InspectorProvider, { entityKinds: flavor.inspector?.entityKinds, views: inspectorViews, children: _jsxs(SidebarProvider, { className: "h-svh flex-col overflow-hidden", style: { "--header-height": "3.5rem" }, children: [_jsx(OperatorHeader, { profile: profile, searchPlaceholder: searchPlaceholder, onSearchOpen: () => setPaletteOpen(true) }), _jsx(CommandPalette, { flavor: flavor, resolvePermission: resolvePermission, icons: icons, iconFamily: iconFamily, runCommand: runCommand, searchSources: searchSources, open: paletteOpen, onOpenChange: setPaletteOpen }), _jsxs("div", { className: "flex min-h-0 flex-1", children: [_jsx(OperatorSidebar, { flavor: flavor, resolvePermission: resolvePermission, icons: icons, iconFamily: iconFamily, workspaceSwitcher: workspaceSwitcher }), _jsxs(SidebarInset, { className: "min-h-0 flex-row overflow-hidden", children: [_jsx("div", { className: "min-w-0 flex-1 overflow-y-auto p-4 lg:p-6", children: children }), _jsx(ChiefRegion, { flavor: flavor, adapter: turnAdapter })] })] })] }) }) }));
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=OperatorShell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OperatorShell.js","sourceRoot":"","sources":["../../src/frame/OperatorShell.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAGtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAEtE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE9D,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AA2DhE,MAAM,UAAU,aAAa,CAAC,EAC5B,MAAM,EACN,WAAW,EACX,iBAAiB,GAAG,kBAAkB,EACtC,UAAU,EACV,cAAc,EACd,aAAa,EACb,KAAK,EACL,OAAO,EACP,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,GACW;IACnB,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAE5D,yEAAyE;IACzE,2EAA2E;IAC3E,oEAAoE;IACpE,0EAA0E;IAC1E,iEAAiE;IACjE,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,UAAU,GAAG,iBAAiB,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAEvD,OAAO;IACL,uEAAuE;IACvE,0EAA0E;IAC1E,KAAC,kBAAkB,IAAC,QAAQ,EAAE,iBAAiB,YAC7C,KAAC,iBAAiB,IAChB,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE,WAAW,EAC1C,KAAK,EAAE,cAAc,YAErB,MAAC,eAAe,IACd,SAAS,EAAC,gCAAgC,EAC1C,KAAK,EAAE,EAAE,iBAAiB,EAAE,QAAQ,EAAyB,aAE7D,KAAC,cAAc,IACb,OAAO,EAAE,OAAO,EAChB,iBAAiB,EAAE,iBAAiB,EACpC,YAAY,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,GACxC,EACF,KAAC,cAAc,IACb,MAAM,EAAE,MAAM,EACd,iBAAiB,EAAE,iBAAiB,EACpC,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,UAAU,EACtB,aAAa,EAAE,aAAa,EAC5B,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,cAAc,GAC5B,EACF,eAAK,SAAS,EAAC,qBAAqB,aAClC,KAAC,eAAe,IACd,MAAM,EAAE,MAAM,EACd,iBAAiB,EAAE,iBAAiB,EACpC,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,UAAU,EACtB,iBAAiB,EAAE,iBAAiB,GACpC,EACF,MAAC,YAAY,IAAC,SAAS,EAAC,kCAAkC,aACxD,cAAK,SAAS,EAAC,2CAA2C,YAAE,QAAQ,GAAO,EAG3E,KAAC,WAAW,IAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAI,IACxC,IACX,IACU,GACA,GACD,CACtB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The React half of the permission-guard seam (§5): a context that carries
|
|
3
|
+
* the product-injected resolver down to pages, plus the hooks every gate
|
|
4
|
+
* reads through.
|
|
5
|
+
*
|
|
6
|
+
* - PermissionProvider — mounted by the OperatorShell frame with the
|
|
7
|
+
* injected resolver; pages never receive the resolver as a prop.
|
|
8
|
+
* - usePermissionDecisions — the batch gate (sidebar, palette): a set of
|
|
9
|
+
* references → a live statuses map. Sync decisions land on the FIRST
|
|
10
|
+
* render (no loading flash for sync resolvers); async decisions start
|
|
11
|
+
* `loading` and settle through effects; rejections settle `denied`
|
|
12
|
+
* (fail-closed).
|
|
13
|
+
* - usePermission — the single-reference gate (RouteGuard, page-level
|
|
14
|
+
* checks).
|
|
15
|
+
*
|
|
16
|
+
* OUTSIDE a provider the hooks fall back to `unknownPermissions`: every
|
|
17
|
+
* gated reference is denied. Fail-closed is the ambient default; a
|
|
18
|
+
* permissive posture is a choice the product mounts explicitly.
|
|
19
|
+
*/
|
|
20
|
+
import * as React from "react";
|
|
21
|
+
import type { PermissionGateStatus, PermissionResolver } from "./permissions.js";
|
|
22
|
+
export interface PermissionProviderProps {
|
|
23
|
+
/** The product's resolution (§5) — sync or async; fail-closed on unknown. */
|
|
24
|
+
resolver: PermissionResolver;
|
|
25
|
+
children: React.ReactNode;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Carries the product's resolver to every gate below it. The OperatorShell
|
|
29
|
+
* frame mounts one with its `resolvePermission` prop; standalone compositions
|
|
30
|
+
* may mount their own.
|
|
31
|
+
*/
|
|
32
|
+
export declare function PermissionProvider({ resolver, children }: PermissionProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
/**
|
|
34
|
+
* Resolve a set of permission references to live gate statuses.
|
|
35
|
+
*
|
|
36
|
+
* Each reference is resolved ONCE per (references, resolver) pair: the
|
|
37
|
+
* evaluation runs synchronously so sync resolvers gate without a loading
|
|
38
|
+
* frame; promises settle through an effect (allowed/denied on resolution,
|
|
39
|
+
* DENIED on rejection — fail-closed). Changing the reference set or the
|
|
40
|
+
* resolver identity re-evaluates from scratch.
|
|
41
|
+
*/
|
|
42
|
+
export declare function usePermissionDecisions(permissions: readonly string[], resolver?: PermissionResolver): Record<string, PermissionGateStatus>;
|
|
43
|
+
/**
|
|
44
|
+
* The single-reference gate: `allowed` for an ungated (undefined) reference,
|
|
45
|
+
* otherwise the live status of the resolution — `loading` while an async
|
|
46
|
+
* decision is in flight, `denied` on deny/unknown/rejection (fail-closed).
|
|
47
|
+
*/
|
|
48
|
+
export declare function usePermission(permission?: string, resolver?: PermissionResolver): PermissionGateStatus;
|
|
49
|
+
//# sourceMappingURL=PermissionProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PermissionProvider.d.ts","sourceRoot":"","sources":["../../src/guard/PermissionProvider.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAEV,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AAW1B,MAAM,WAAW,uBAAuB;IACtC,6EAA6E;IAC7E,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,uBAAuB,2CAMjF;AAQD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,QAAQ,CAAC,EAAE,kBAAkB,GAC5B,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAyDtC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,UAAU,CAAC,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,kBAAkB,GAC5B,oBAAoB,CAOtB"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* The React half of the permission-guard seam (§5): a context that carries
|
|
5
|
+
* the product-injected resolver down to pages, plus the hooks every gate
|
|
6
|
+
* reads through.
|
|
7
|
+
*
|
|
8
|
+
* - PermissionProvider — mounted by the OperatorShell frame with the
|
|
9
|
+
* injected resolver; pages never receive the resolver as a prop.
|
|
10
|
+
* - usePermissionDecisions — the batch gate (sidebar, palette): a set of
|
|
11
|
+
* references → a live statuses map. Sync decisions land on the FIRST
|
|
12
|
+
* render (no loading flash for sync resolvers); async decisions start
|
|
13
|
+
* `loading` and settle through effects; rejections settle `denied`
|
|
14
|
+
* (fail-closed).
|
|
15
|
+
* - usePermission — the single-reference gate (RouteGuard, page-level
|
|
16
|
+
* checks).
|
|
17
|
+
*
|
|
18
|
+
* OUTSIDE a provider the hooks fall back to `unknownPermissions`: every
|
|
19
|
+
* gated reference is denied. Fail-closed is the ambient default; a
|
|
20
|
+
* permissive posture is a choice the product mounts explicitly.
|
|
21
|
+
*/
|
|
22
|
+
import * as React from "react";
|
|
23
|
+
import { evaluatePermissions, gateStatus, toGateStatus, unknownPermissions, } from "./permissions.js";
|
|
24
|
+
const PermissionResolverContext = React.createContext(unknownPermissions);
|
|
25
|
+
/**
|
|
26
|
+
* Carries the product's resolver to every gate below it. The OperatorShell
|
|
27
|
+
* frame mounts one with its `resolvePermission` prop; standalone compositions
|
|
28
|
+
* may mount their own.
|
|
29
|
+
*/
|
|
30
|
+
export function PermissionProvider({ resolver, children }) {
|
|
31
|
+
return (_jsx(PermissionResolverContext.Provider, { value: resolver, children: children }));
|
|
32
|
+
}
|
|
33
|
+
/** The resolver in scope — the explicit argument beats the context. */
|
|
34
|
+
function useResolver(explicit) {
|
|
35
|
+
const fromContext = React.useContext(PermissionResolverContext);
|
|
36
|
+
return explicit ?? fromContext;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolve a set of permission references to live gate statuses.
|
|
40
|
+
*
|
|
41
|
+
* Each reference is resolved ONCE per (references, resolver) pair: the
|
|
42
|
+
* evaluation runs synchronously so sync resolvers gate without a loading
|
|
43
|
+
* frame; promises settle through an effect (allowed/denied on resolution,
|
|
44
|
+
* DENIED on rejection — fail-closed). Changing the reference set or the
|
|
45
|
+
* resolver identity re-evaluates from scratch.
|
|
46
|
+
*/
|
|
47
|
+
export function usePermissionDecisions(permissions, resolver) {
|
|
48
|
+
const resolve = useResolver(resolver);
|
|
49
|
+
// Stable identity key for the reference set (order-insensitive; NUL can't
|
|
50
|
+
// appear in a reference string, so the key never collides).
|
|
51
|
+
const key = React.useMemo(() => [...permissions].sort().join("\u0000"),
|
|
52
|
+
// The array's CONTENT is the dependency; callers may rebuild it per render.
|
|
53
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
54
|
+
[permissions.join("\u0000")]);
|
|
55
|
+
const [evaluation, setEvaluation] = React.useState(() => evaluatePermissions(permissions, resolve));
|
|
56
|
+
// Re-evaluate during render when the inputs change (the documented
|
|
57
|
+
// derived-state reset pattern) so a resolver/reference change never shows
|
|
58
|
+
// one frame of stale decisions.
|
|
59
|
+
const inputs = React.useRef({ key, resolve });
|
|
60
|
+
if (inputs.current.key !== key || inputs.current.resolve !== resolve) {
|
|
61
|
+
inputs.current = { key, resolve };
|
|
62
|
+
setEvaluation(evaluatePermissions(permissions, resolve));
|
|
63
|
+
}
|
|
64
|
+
// Settle the in-flight decisions. Each settlement removes its reference
|
|
65
|
+
// from `pending`, producing a new evaluation; the effect re-runs and
|
|
66
|
+
// re-arms only the still-pending promises (superseded handlers are inert
|
|
67
|
+
// via the cancellation flag).
|
|
68
|
+
React.useEffect(() => {
|
|
69
|
+
if (evaluation.pending.length === 0)
|
|
70
|
+
return;
|
|
71
|
+
let cancelled = false;
|
|
72
|
+
const settle = (permission, status) => {
|
|
73
|
+
if (cancelled)
|
|
74
|
+
return;
|
|
75
|
+
setEvaluation((current) => {
|
|
76
|
+
if (current.statuses[permission] !== "loading")
|
|
77
|
+
return current;
|
|
78
|
+
return {
|
|
79
|
+
statuses: { ...current.statuses, [permission]: status },
|
|
80
|
+
pending: current.pending.filter((entry) => entry.permission !== permission),
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
for (const { permission, promise } of evaluation.pending) {
|
|
85
|
+
promise.then((decision) => settle(permission, toGateStatus(decision)), () => settle(permission, "denied"));
|
|
86
|
+
}
|
|
87
|
+
return () => {
|
|
88
|
+
cancelled = true;
|
|
89
|
+
};
|
|
90
|
+
}, [evaluation]);
|
|
91
|
+
return evaluation.statuses;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The single-reference gate: `allowed` for an ungated (undefined) reference,
|
|
95
|
+
* otherwise the live status of the resolution — `loading` while an async
|
|
96
|
+
* decision is in flight, `denied` on deny/unknown/rejection (fail-closed).
|
|
97
|
+
*/
|
|
98
|
+
export function usePermission(permission, resolver) {
|
|
99
|
+
const refs = React.useMemo(() => (permission ? [permission] : []), [permission]);
|
|
100
|
+
const decisions = usePermissionDecisions(refs, resolver);
|
|
101
|
+
return gateStatus(permission, decisions);
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=PermissionProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PermissionProvider.js","sourceRoot":"","sources":["../../src/guard/PermissionProvider.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RouteGuard — page-level gating over the permission seam (§5): the shell
|
|
3
|
+
* owns the fail-closed gating BEHAVIOR (loading / denied / redirect states);
|
|
4
|
+
* the product owns the resolution (the resolver mounted by the frame's
|
|
5
|
+
* PermissionProvider) and may replace the state visuals through slots.
|
|
6
|
+
*
|
|
7
|
+
* - No `permission` → children render (an ungated route is open).
|
|
8
|
+
* - `loading` → the loading state (skeleton block by default) while
|
|
9
|
+
* an async decision is in flight — never the page.
|
|
10
|
+
* - `denied`/unknown → the denied state (a composed registry Empty by
|
|
11
|
+
* default), or a client redirect when `redirectTo` is
|
|
12
|
+
* set (the denied fallback shows while the router
|
|
13
|
+
* replaces — the gated page itself NEVER paints).
|
|
14
|
+
*
|
|
15
|
+
* The redirect effect lives in a child component mounted only on denial, so
|
|
16
|
+
* a guard without `redirectTo` has no router dependency at all (and stays
|
|
17
|
+
* provable outside a Next app-router context).
|
|
18
|
+
*/
|
|
19
|
+
import * as React from "react";
|
|
20
|
+
import type { PermissionResolver } from "./permissions.js";
|
|
21
|
+
export interface RouteGuardProps {
|
|
22
|
+
/**
|
|
23
|
+
* Permission REFERENCE gating this subtree (same vocabulary as nav items'
|
|
24
|
+
* `requiredPermission`). Absent ⇒ the guard is a pass-through.
|
|
25
|
+
*/
|
|
26
|
+
permission?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Resolver override for standalone use; normally omitted — the guard reads
|
|
29
|
+
* the frame's PermissionProvider (outside any provider: fail-closed, every
|
|
30
|
+
* gated reference denied).
|
|
31
|
+
*/
|
|
32
|
+
resolver?: PermissionResolver;
|
|
33
|
+
/** Replaces the default loading skeleton. */
|
|
34
|
+
loading?: React.ReactNode;
|
|
35
|
+
/** Replaces the default denied state. */
|
|
36
|
+
denied?: React.ReactNode;
|
|
37
|
+
/**
|
|
38
|
+
* On denial, client-replace to this route instead of standing on the
|
|
39
|
+
* denied state (the denied/`denied`-slot content shows during the swap).
|
|
40
|
+
*/
|
|
41
|
+
redirectTo?: string;
|
|
42
|
+
children: React.ReactNode;
|
|
43
|
+
}
|
|
44
|
+
export declare function RouteGuard({ permission, resolver, loading, denied, redirectTo, children, }: RouteGuardProps): import("react/jsx-runtime").JSX.Element;
|
|
45
|
+
//# sourceMappingURL=RouteGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RouteGuard.d.ts","sourceRoot":"","sources":["../../src/guard/RouteGuard.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAa/B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAG3D,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,6CAA6C;IAC7C,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,yCAAyC;IACzC,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,UAAU,CAAC,EACzB,UAAU,EACV,QAAQ,EACR,OAAO,EACP,MAAM,EACN,UAAU,EACV,QAAQ,GACT,EAAE,eAAe,2CAcjB"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* RouteGuard — page-level gating over the permission seam (§5): the shell
|
|
5
|
+
* owns the fail-closed gating BEHAVIOR (loading / denied / redirect states);
|
|
6
|
+
* the product owns the resolution (the resolver mounted by the frame's
|
|
7
|
+
* PermissionProvider) and may replace the state visuals through slots.
|
|
8
|
+
*
|
|
9
|
+
* - No `permission` → children render (an ungated route is open).
|
|
10
|
+
* - `loading` → the loading state (skeleton block by default) while
|
|
11
|
+
* an async decision is in flight — never the page.
|
|
12
|
+
* - `denied`/unknown → the denied state (a composed registry Empty by
|
|
13
|
+
* default), or a client redirect when `redirectTo` is
|
|
14
|
+
* set (the denied fallback shows while the router
|
|
15
|
+
* replaces — the gated page itself NEVER paints).
|
|
16
|
+
*
|
|
17
|
+
* The redirect effect lives in a child component mounted only on denial, so
|
|
18
|
+
* a guard without `redirectTo` has no router dependency at all (and stays
|
|
19
|
+
* provable outside a Next app-router context).
|
|
20
|
+
*/
|
|
21
|
+
import * as React from "react";
|
|
22
|
+
import { useRouter } from "next/navigation";
|
|
23
|
+
import { ShieldX } from "lucide-react";
|
|
24
|
+
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, Skeleton, } from "@consciousclouds/ui";
|
|
25
|
+
import { usePermission } from "./PermissionProvider.js";
|
|
26
|
+
export function RouteGuard({ permission, resolver, loading, denied, redirectTo, children, }) {
|
|
27
|
+
const status = usePermission(permission, resolver);
|
|
28
|
+
if (status === "allowed")
|
|
29
|
+
return _jsx(_Fragment, { children: children });
|
|
30
|
+
if (status === "loading") {
|
|
31
|
+
return _jsx(_Fragment, { children: loading ?? _jsx(RouteGuardLoading, {}) });
|
|
32
|
+
}
|
|
33
|
+
const deniedState = denied ?? _jsx(RouteGuardDenied, {});
|
|
34
|
+
if (redirectTo) {
|
|
35
|
+
return _jsx(RedirectOnDenied, { to: redirectTo, children: deniedState });
|
|
36
|
+
}
|
|
37
|
+
return _jsx(_Fragment, { children: deniedState });
|
|
38
|
+
}
|
|
39
|
+
/** Default loading state: a quiet page-shaped skeleton, never the page. */
|
|
40
|
+
function RouteGuardLoading() {
|
|
41
|
+
return (_jsxs("div", { "data-slot": "route-guard-loading", className: "space-y-4", children: [_jsx(Skeleton, { className: "h-7 w-48" }), _jsx(Skeleton, { className: "h-4 w-72" }), _jsx(Skeleton, { className: "h-40 w-full" })] }));
|
|
42
|
+
}
|
|
43
|
+
/** Default denied state: the composed registry Empty, fail-closed copy. */
|
|
44
|
+
function RouteGuardDenied() {
|
|
45
|
+
return (_jsx(Empty, { "data-slot": "route-guard-denied", className: "border border-dashed", children: _jsxs(EmptyHeader, { children: [_jsx(EmptyMedia, { variant: "icon", children: _jsx(ShieldX, {}) }), _jsx(EmptyTitle, { children: "Not permitted" }), _jsx(EmptyDescription, { children: "Your operator session doesn't resolve the permission this page requires. The guard fails closed \u2014 ask an administrator if you believe you should have access." })] }) }));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Client redirect, mounted ONLY when a guarded route is denied with a
|
|
49
|
+
* `redirectTo`. Isolating the router hook here keeps redirect-less guards
|
|
50
|
+
* free of the app-router context.
|
|
51
|
+
*/
|
|
52
|
+
function RedirectOnDenied({ to, children }) {
|
|
53
|
+
const router = useRouter();
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
router.replace(to);
|
|
56
|
+
}, [router, to]);
|
|
57
|
+
return _jsx(_Fragment, { children: children });
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=RouteGuard.js.map
|