@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.
Files changed (145) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +194 -0
  3. package/README.md +319 -0
  4. package/dist/chief/ChiefDrawer.d.ts +11 -0
  5. package/dist/chief/ChiefDrawer.d.ts.map +1 -0
  6. package/dist/chief/ChiefDrawer.js +49 -0
  7. package/dist/chief/ChiefDrawer.js.map +1 -0
  8. package/dist/chief/ChiefPanel.d.ts +10 -0
  9. package/dist/chief/ChiefPanel.d.ts.map +1 -0
  10. package/dist/chief/ChiefPanel.js +46 -0
  11. package/dist/chief/ChiefPanel.js.map +1 -0
  12. package/dist/chief/ChiefRegion.d.ts +9 -0
  13. package/dist/chief/ChiefRegion.d.ts.map +1 -0
  14. package/dist/chief/ChiefRegion.js +55 -0
  15. package/dist/chief/ChiefRegion.js.map +1 -0
  16. package/dist/chief/ChiefSession.d.ts +21 -0
  17. package/dist/chief/ChiefSession.d.ts.map +1 -0
  18. package/dist/chief/ChiefSession.js +9 -0
  19. package/dist/chief/ChiefSession.js.map +1 -0
  20. package/dist/chief/directive.d.ts +15 -0
  21. package/dist/chief/directive.d.ts.map +1 -0
  22. package/dist/chief/directive.js +20 -0
  23. package/dist/chief/directive.js.map +1 -0
  24. package/dist/chief/turn-adapter.d.ts +52 -0
  25. package/dist/chief/turn-adapter.d.ts.map +1 -0
  26. package/dist/chief/turn-adapter.js +23 -0
  27. package/dist/chief/turn-adapter.js.map +1 -0
  28. package/dist/chief/use-chief-shortcut.d.ts +17 -0
  29. package/dist/chief/use-chief-shortcut.d.ts.map +1 -0
  30. package/dist/chief/use-chief-shortcut.js +61 -0
  31. package/dist/chief/use-chief-shortcut.js.map +1 -0
  32. package/dist/chief/use-chief-turn.d.ts +50 -0
  33. package/dist/chief/use-chief-turn.d.ts.map +1 -0
  34. package/dist/chief/use-chief-turn.js +99 -0
  35. package/dist/chief/use-chief-turn.js.map +1 -0
  36. package/dist/flavor/types.d.ts +189 -0
  37. package/dist/flavor/types.d.ts.map +1 -0
  38. package/dist/flavor/types.js +13 -0
  39. package/dist/flavor/types.js.map +1 -0
  40. package/dist/frame/OperatorShell.d.ts +91 -0
  41. package/dist/frame/OperatorShell.d.ts.map +1 -0
  42. package/dist/frame/OperatorShell.js +54 -0
  43. package/dist/frame/OperatorShell.js.map +1 -0
  44. package/dist/guard/PermissionProvider.d.ts +49 -0
  45. package/dist/guard/PermissionProvider.d.ts.map +1 -0
  46. package/dist/guard/PermissionProvider.js +103 -0
  47. package/dist/guard/PermissionProvider.js.map +1 -0
  48. package/dist/guard/RouteGuard.d.ts +45 -0
  49. package/dist/guard/RouteGuard.d.ts.map +1 -0
  50. package/dist/guard/RouteGuard.js +59 -0
  51. package/dist/guard/RouteGuard.js.map +1 -0
  52. package/dist/guard/permissions.d.ts +88 -0
  53. package/dist/guard/permissions.d.ts.map +1 -0
  54. package/dist/guard/permissions.js +105 -0
  55. package/dist/guard/permissions.js.map +1 -0
  56. package/dist/header/EnvironmentSwitcher.d.ts +5 -0
  57. package/dist/header/EnvironmentSwitcher.d.ts.map +1 -0
  58. package/dist/header/EnvironmentSwitcher.js +51 -0
  59. package/dist/header/EnvironmentSwitcher.js.map +1 -0
  60. package/dist/header/OperatorHeader.d.ts +25 -0
  61. package/dist/header/OperatorHeader.d.ts.map +1 -0
  62. package/dist/header/OperatorHeader.js +13 -0
  63. package/dist/header/OperatorHeader.js.map +1 -0
  64. package/dist/header/OperatorProfileMenu.d.ts +2 -0
  65. package/dist/header/OperatorProfileMenu.d.ts.map +1 -0
  66. package/dist/header/OperatorProfileMenu.js +14 -0
  67. package/dist/header/OperatorProfileMenu.js.map +1 -0
  68. package/dist/header/ThemeToggle.d.ts +2 -0
  69. package/dist/header/ThemeToggle.d.ts.map +1 -0
  70. package/dist/header/ThemeToggle.js +17 -0
  71. package/dist/header/ThemeToggle.js.map +1 -0
  72. package/dist/icons/registry.d.ts +57 -0
  73. package/dist/icons/registry.d.ts.map +1 -0
  74. package/dist/icons/registry.js +83 -0
  75. package/dist/icons/registry.js.map +1 -0
  76. package/dist/index.d.ts +77 -0
  77. package/dist/index.d.ts.map +1 -0
  78. package/dist/index.js +129 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/inspector/InspectorProvider.d.ts +41 -0
  81. package/dist/inspector/InspectorProvider.d.ts.map +1 -0
  82. package/dist/inspector/InspectorProvider.js +63 -0
  83. package/dist/inspector/InspectorProvider.js.map +1 -0
  84. package/dist/inspector/types.d.ts +91 -0
  85. package/dist/inspector/types.d.ts.map +1 -0
  86. package/dist/inspector/types.js +40 -0
  87. package/dist/inspector/types.js.map +1 -0
  88. package/dist/nav/routes.d.ts +39 -0
  89. package/dist/nav/routes.d.ts.map +1 -0
  90. package/dist/nav/routes.js +46 -0
  91. package/dist/nav/routes.js.map +1 -0
  92. package/dist/palette/CommandPalette.d.ts +40 -0
  93. package/dist/palette/CommandPalette.d.ts.map +1 -0
  94. package/dist/palette/CommandPalette.js +108 -0
  95. package/dist/palette/CommandPalette.js.map +1 -0
  96. package/dist/palette/commands.d.ts +58 -0
  97. package/dist/palette/commands.d.ts.map +1 -0
  98. package/dist/palette/commands.js +81 -0
  99. package/dist/palette/commands.js.map +1 -0
  100. package/dist/palette/search-sources.d.ts +126 -0
  101. package/dist/palette/search-sources.d.ts.map +1 -0
  102. package/dist/palette/search-sources.js +101 -0
  103. package/dist/palette/search-sources.js.map +1 -0
  104. package/dist/palette/use-search-results.d.ts +4 -0
  105. package/dist/palette/use-search-results.d.ts.map +1 -0
  106. package/dist/palette/use-search-results.js +51 -0
  107. package/dist/palette/use-search-results.js.map +1 -0
  108. package/dist/sidebar/OperatorSidebar.d.ts +44 -0
  109. package/dist/sidebar/OperatorSidebar.d.ts.map +1 -0
  110. package/dist/sidebar/OperatorSidebar.js +30 -0
  111. package/dist/sidebar/OperatorSidebar.js.map +1 -0
  112. package/dist/workspace/CanvasWorkspace.d.ts +78 -0
  113. package/dist/workspace/CanvasWorkspace.d.ts.map +1 -0
  114. package/dist/workspace/CanvasWorkspace.js +44 -0
  115. package/dist/workspace/CanvasWorkspace.js.map +1 -0
  116. package/dist/workspace/DashboardWorkspace.d.ts +91 -0
  117. package/dist/workspace/DashboardWorkspace.d.ts.map +1 -0
  118. package/dist/workspace/DashboardWorkspace.js +39 -0
  119. package/dist/workspace/DashboardWorkspace.js.map +1 -0
  120. package/dist/workspace/FormWorkspace.d.ts +107 -0
  121. package/dist/workspace/FormWorkspace.d.ts.map +1 -0
  122. package/dist/workspace/FormWorkspace.js +38 -0
  123. package/dist/workspace/FormWorkspace.js.map +1 -0
  124. package/dist/workspace/GraphWorkspace.d.ts +112 -0
  125. package/dist/workspace/GraphWorkspace.d.ts.map +1 -0
  126. package/dist/workspace/GraphWorkspace.js +147 -0
  127. package/dist/workspace/GraphWorkspace.js.map +1 -0
  128. package/dist/workspace/TableWorkspace.d.ts +74 -0
  129. package/dist/workspace/TableWorkspace.d.ts.map +1 -0
  130. package/dist/workspace/TableWorkspace.js +25 -0
  131. package/dist/workspace/TableWorkspace.js.map +1 -0
  132. package/dist/workspace/WorkspaceHost.d.ts +26 -0
  133. package/dist/workspace/WorkspaceHost.d.ts.map +1 -0
  134. package/dist/workspace/WorkspaceHost.js +21 -0
  135. package/dist/workspace/WorkspaceHost.js.map +1 -0
  136. package/dist/workspace/registry.d.ts +30 -0
  137. package/dist/workspace/registry.d.ts.map +1 -0
  138. package/dist/workspace/registry.js +33 -0
  139. package/dist/workspace/registry.js.map +1 -0
  140. package/dist/workspace/types.d.ts +79 -0
  141. package/dist/workspace/types.d.ts.map +1 -0
  142. package/dist/workspace/types.js +48 -0
  143. package/dist/workspace/types.js.map +1 -0
  144. package/package.json +64 -0
  145. 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