elowen-plugin-ui-kit 0.2.0 → 0.6.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/build.js CHANGED
@@ -47,8 +47,8 @@ export async function buildPluginUiBundle({ entry, outfile, minify = false, node
47
47
  * utility rendered unstyled there, with nothing the user could do about it. So the plugin brings its own.
48
48
  *
49
49
  * Three scoping rules make that safe to drop into a running app, and each is load-bearing:
50
- * - everything lands in `@layer utilities`, so the host's own utilities (declared in the same layer, but
51
- * earlier in the cascade order the host establishes) are not globally outranked by a plugin's sheet;
50
+ * - everything lands in `@layer utilities`, and the host loader inserts the plugin sheet BEFORE host styles,
51
+ * so plugin-only classes exist while matching host utilities win by source order;
52
52
  * - NO preflight — a plugin must never reset the host's elements;
53
53
  * - NO prefix — the plugin shares class names with the shared components it renders from
54
54
  * `window.ElowenUiRuntime`, which are styled by the host's sheet.
@@ -65,7 +65,7 @@ export async function buildPluginUiCss({ bundle, outfile }) {
65
65
 
66
66
  const source = resolve(bundle);
67
67
  const css = [
68
- // Declare the layer order the host uses, so `utilities` sorts where the host puts it.
68
+ // Declare the standard Tailwind order; the host loader owns source order between plugin and host sheets.
69
69
  '@layer theme, base, components, utilities;',
70
70
  // Reference-only: gives the compiler the design system (Tailwind's defaults + the host tokens)
71
71
  // WITHOUT emitting a single variable or preflight rule of its own.
package/index.d.ts CHANGED
@@ -10,7 +10,74 @@ import type { ComponentType } from 'react';
10
10
  /** See index.js — bump on incompatible changes to `ElowenUiRuntime`. Deliberately a LITERAL type:
11
11
  * the web app re-declares the value and annotates it with `typeof PLUGIN_UI_API_VERSION`, so a kit
12
12
  * bump that forgets the host fails the web typecheck instead of drifting silently. */
13
- export declare const PLUGIN_UI_API_VERSION: 1;
13
+ export declare const PLUGIN_UI_API_VERSION: 11;
14
+
15
+ /** Public props of `ElowenUiRuntime.components.Slider`. */
16
+ export interface SliderProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'min' | 'max' | 'step' | 'type'> {
17
+ value: number;
18
+ onChange: (value: number) => void;
19
+ min?: number;
20
+ max?: number;
21
+ step?: number;
22
+ }
23
+
24
+ /** Public props of `ElowenUiRuntime.components.DirectoryPicker`. Selection reports the currently open
25
+ * server directory; closing has no side effect. */
26
+ export interface DirectoryPickerProps {
27
+ initialPath?: string;
28
+ onSelect: (path: string) => void;
29
+ onClose: () => void;
30
+ }
31
+
32
+ export type ConfirmDialogButtonVariant = 'default' | 'accent' | 'ghost' | 'danger' | 'ghost-danger' | 'outline' | 'outline-danger';
33
+
34
+ /** Public props of the API 11 async-safe `ElowenUiRuntime.components.ConfirmDialog`. */
35
+ export interface ConfirmDialogProps {
36
+ open: boolean;
37
+ title: string;
38
+ description?: string;
39
+ confirmLabel?: string;
40
+ confirmVariant?: ConfirmDialogButtonVariant;
41
+ pendingLabel?: string;
42
+ pending?: boolean;
43
+ disabled?: boolean;
44
+ /** Legacy confirm-only disable; unlike `disabled`, Cancel remains available. */
45
+ confirmDisabled?: boolean;
46
+ error?: React.ReactNode;
47
+ /** A promise enables the built-in pending lock; synchronous callbacks remain source-compatible. */
48
+ onConfirm: () => unknown;
49
+ onConfirmError?: (error: unknown) => void;
50
+ onClose: () => void;
51
+ }
52
+
53
+ /** Project metadata exposed to a contextual plugin panel. The Project remains core-owned; a panel uses
54
+ * this identity to address only its own project-scoped API data. */
55
+ export interface PluginUiProject {
56
+ id: number;
57
+ slug: string;
58
+ path: string;
59
+ notes: string;
60
+ icon?: string;
61
+ }
62
+
63
+ /** The selected core User DTO exposed only to administrator user-detail panels. Compatibility fields stay
64
+ * present because plugins receive the same object as the host Users screen, not a second partial identity. */
65
+ export interface PluginUiUser {
66
+ id: number;
67
+ username: string;
68
+ created_at: string;
69
+ is_admin: boolean;
70
+ allowed_execs: string[];
71
+ disabled_tools: string[];
72
+ allowed_tools: string[];
73
+ granted_plugins: string[];
74
+ name: string;
75
+ email: string;
76
+ avatar: string;
77
+ default_exec: string;
78
+ advisor_exec: string;
79
+ advisor_autostart: boolean;
80
+ }
14
81
 
15
82
  /** Props every plugin page/settings component receives. */
16
83
  export interface PluginPageProps {
@@ -33,13 +100,53 @@ export interface PluginPageProps {
33
100
  onSaveState?: (status: 'idle' | 'saving' | 'saved' | 'error', retry?: () => void) => void;
34
101
  }
35
102
 
103
+ /** Props for a contextual Project panel. It is never a standalone route: the selected Project and panel
104
+ * id come from the host's Project detail rail. */
105
+ export interface PluginProjectPanelProps {
106
+ plugin: string;
107
+ panelId: string;
108
+ project: PluginUiProject;
109
+ surface: 'project';
110
+ }
111
+
112
+ /** Props for an administrator panel mounted for one selected core User. */
113
+ export interface PluginUserPanelProps {
114
+ plugin: string;
115
+ panelId: string;
116
+ user: PluginUiUser;
117
+ surface: 'user';
118
+ }
119
+
36
120
  /** What a bundle hands to window.__elowenRegisterPluginUi. Routes are `/`-joined segment patterns
37
- * (`''` = the root page, `detail/:id` captures params). `settings` components are keyed by the
38
- * manifest's `web.settings[].id` and render inside the Settings page's control deck. */
121
+ * (`''` = the root page, `detail/:id` captures params). Contextual component maps are keyed by their
122
+ * matching manifest panel ids and mount only in the corresponding host surface. */
39
123
  export interface PluginUiRegistration {
40
124
  requiresApiVersion: number;
41
125
  pages?: Record<string, ComponentType<PluginPageProps>>;
126
+ account?: Record<string, ComponentType<PluginPageProps>>;
127
+ /** For an `account` entry placed as `linkedAccount`: its one-line claim in the CLOSED Linked accounts
128
+ * summary, keyed by the same panel id. Whether a connector is currently linked is a fact only the
129
+ * bundle holds, so the host cannot draw this chip on its behalf — but it also must not mount the
130
+ * PANEL to ask, which is why this is its own entry. Omit it and the summary simply says nothing
131
+ * about this connector. Draw it with `components.SummaryChip` so it matches the chips beside it. */
132
+ accountChip?: Record<string, ComponentType<PluginPageProps>>;
133
+ user?: Record<string, ComponentType<PluginUserPanelProps>>;
134
+ project?: Record<string, ComponentType<PluginProjectPanelProps>>;
42
135
  settings?: Record<string, ComponentType<PluginPageProps>>;
136
+ /** Ids of `settings` sections that draw their OWN page frame — the shell, the masthead and the save
137
+ * indicator — and therefore want none from the host.
138
+ *
139
+ * The host serves a plugin's SOLE settings section as its page (`/p/<plugin>`) and wraps it in a
140
+ * page column plus a masthead, because a section written for the Settings deck brings neither. A
141
+ * section that renders `components.WorkspaceShell` itself then gets both twice: two nested page
142
+ * frames, so the gutter and the bottom padding apply twice and the page is measurably narrower than
143
+ * its sibling registers, above a masthead that is a zero-height row of margin holding an idle save
144
+ * indicator. Naming the section here is how a bundle says "the frame is mine", and it is a fact only
145
+ * the bundle holds — the host cannot see what a component renders without mounting it.
146
+ *
147
+ * A section listed here owns the whole surface, so it also owns showing its own save state and must
148
+ * not rely on `onSaveState` being displayed anywhere. Ids not present in `settings` are ignored. */
149
+ ownsPageFrame?: string[];
43
150
  }
44
151
 
45
152
  /** The host API surface a bundle finds on `window.ElowenUiRuntime`: the HOST's React instance (a
package/index.js CHANGED
@@ -2,4 +2,4 @@
2
2
  * `window.ElowenUiRuntime` stamped with this number; a bundle whose `requiresApiVersion` is NEWER
3
3
  * renders a placeholder instead of executing against a contract it was not built for. Bump on
4
4
  * incompatible changes to the `ElowenUiRuntime` surface (see index.d.ts). */
5
- export const PLUGIN_UI_API_VERSION = 1;
5
+ export const PLUGIN_UI_API_VERSION = 11;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "elowen-plugin-ui-kit",
3
- "version": "0.2.0",
3
+ "version": "0.6.0",
4
4
  "description": "Contract types and esbuild toolchain for building Elowen plugin browser-UI bundles.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/theme.css CHANGED
@@ -3,34 +3,86 @@
3
3
  * emit `var(--token, fallback)`. tests/contract/pluginUiKitTheme.test.ts fails the moment the two drift.
4
4
  */
5
5
  @theme {
6
- /* OLED Ember: true black canvas, warm near-black surfaces, one unmistakable Elowen accent. */
7
- --color-bg: #000000;
6
+ /* OLED Ember: true black canvas, warm near-black surfaces, one unmistakable Elowen ember. */
7
+ --color-background: #000000;
8
+ --color-foreground: #f7f3f0;
9
+
10
+ /* NOT a shadcn token. One ground between the canvas and a card, for the ambient chrome that has to
11
+ read as raised off `background` without being a card — the orbit pods, the section nodes, the
12
+ settings groups. shadcn's ramp goes straight from `background` to `card`. */
8
13
  --color-document: #030303;
9
- --color-surface: #070707;
10
- --color-elevated: #0d0d0d;
11
- --color-overlay: #151515;
14
+
15
+ --color-card: #070707;
16
+ --color-card-foreground: var(--color-foreground);
17
+ --color-popover: #151515;
18
+ --color-popover-foreground: var(--color-foreground);
19
+
20
+ --color-muted: #0d0d0d;
21
+ --color-muted-foreground: #9d948e;
22
+ /* shadcn separates the secondary-button fill from the muted surface so a design can make one louder
23
+ than the other; ours does not, and an alias says that on purpose rather than by omission. */
24
+ --color-secondary: var(--color-muted);
25
+ --color-secondary-foreground: var(--color-foreground);
26
+
27
+ --color-accent: color-mix(in srgb, var(--color-foreground) 10%, transparent);
28
+ --color-accent-foreground: var(--color-foreground);
29
+
30
+ /* NOT a shadcn token: shadcn's text ramp stops at `muted-foreground`, and the app has a third step
31
+ carrying timestamps, units and table metadata. It still has to be READABLE, not decorative, so it is
32
+ held at >= 4.5:1 (WCAG AA, normal text) against every surface token — 4.57:1 on the lightest one,
33
+ --color-muted — while staying far enough under --color-muted-foreground (6.53:1 there) that the
34
+ three steps remain distinguishable. */
35
+ --color-subtle-foreground: #827974;
36
+
37
+ /* NOT a shadcn token. Opaque backing for anything that scrolls content UNDER itself — sticky table
38
+ headers, sticky toolbars. It must not be translucent or rows show through the header as it passes,
39
+ which rules out `accent`. Derived from the document ground so every skin, including forks this repo
40
+ cannot see, inherits a correct sticky colour without declaring one. */
41
+ --color-sticky: color-mix(in srgb, var(--color-document) 94%, var(--color-foreground) 6%);
42
+
12
43
  --color-border: #242424;
44
+ /* NOT a shadcn token: the heavier rule, for a scrollbar thumb and a focused edge. */
13
45
  --color-border-strong: #363636;
14
- --color-accent: #ff5236;
15
- --color-accent-hot: #ff735c;
46
+ /* Form-control edges are the app's ordinary border. shadcn separates `input` from `border` so a design
47
+ can make fields louder than cards; ours does not, and an alias says that on purpose rather than by
48
+ omission — a skin that wants the distinction overrides this one token. */
49
+ --color-input: var(--color-border);
50
+ --color-ring: var(--color-primary);
51
+
52
+ --color-primary: #ff5236;
53
+ /* The ink on a saturated fill. shadcn gives every fill its own `-foreground`, and the app deliberately
54
+ paints all of them with ONE ink, so the value is stated here once and the others alias it — a skin
55
+ that wants to split them overrides the individual names. */
56
+ --color-primary-foreground: #070707;
57
+ /* NOT shadcn tokens: the brand ember is a RAMP, not a single fill. `-hot` is its hover step and
58
+ `-bright` its pale far stop, so a heat gradient or a chart moves with a repaint instead of staying
59
+ orange under a blue design. */
60
+ --color-primary-hot: #ff735c;
16
61
  --color-ember: #ff9a62;
17
- /* The pale end of the ember ramp — the far stop of a heat gradient, where the accent is the near one.
18
- It is a token so a repaint moves the whole ramp; a literal in a chart would stay orange. */
19
62
  --color-ember-bright: #ffd09a;
20
63
 
21
- --color-text: #f7f3f0;
22
- --color-text-muted: #9d948e;
23
- --color-text-subtle: #6e6661;
24
-
25
- --color-danger: #e94d42;
64
+ --color-destructive: #e94d42;
65
+ --color-destructive-foreground: var(--color-primary-foreground);
66
+ /* NOT shadcn tokens. shadcn ships only `destructive`; an app that reports outcomes needs the other
67
+ three states, and borrowing `primary` or `chart-*` for them would say something they do not mean. */
26
68
  --color-success: #32c986;
69
+ --color-success-foreground: var(--color-primary-foreground);
27
70
  --color-warning: #edae49;
28
- --color-error: #e94d42;
29
71
  --color-info: #62a8ea;
30
- --color-approve: #32c986;
31
- --color-cancelled: #756d68;
32
72
 
33
- /* Organic but disciplined: compact controls, roomier panels, never pill-shaped by default. */
73
+ /* The categorical series. Charts and legends read these and never a literal, so a repaint moves the
74
+ whole set. 1–3 are the brand ramp; 4 and 5 are deliberately UNLIKE it, so a swatch stays
75
+ distinguishable from its neighbours without borrowing `destructive` or `success` and implying a
76
+ state it does not mean. */
77
+ --color-chart-1: var(--color-primary);
78
+ --color-chart-2: var(--color-ember);
79
+ --color-chart-3: var(--color-ember-bright);
80
+ --color-chart-4: #a78bfa;
81
+ --color-chart-5: #f472b6;
82
+
83
+ /* Organic but disciplined: compact controls, roomier panels, never pill-shaped by default.
84
+ This scale ALREADY carries shadcn's names, so components use `rounded-sm|md|lg|xl|2xl` directly and
85
+ must never reach for `rounded-[var(--radius)]` — that would bypass the scale and pin one size. */
34
86
  --radius-sm: 0.375rem;
35
87
  --radius: 0.5rem;
36
88
  --radius-md: 0.625rem;
@@ -38,16 +90,6 @@
38
90
  --radius-xl: 1rem;
39
91
  --radius-2xl: 1.25rem;
40
92
 
41
- --space-1: 0.25rem;
42
- --space-2: 0.5rem;
43
- --space-3: 0.75rem;
44
- --space-4: 1rem;
45
- --space-5: 1.25rem;
46
- --space-6: 1.5rem;
47
- --space-8: 2rem;
48
- --space-10: 2.5rem;
49
- --space-12: 3rem;
50
-
51
93
  --font-mono: var(--font-geist-mono), ui-monospace, "SF Mono", monospace;
52
94
  --font-sans: var(--font-geist-sans), -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
53
95
  --font-display: var(--font-geist-sans), var(--font-sans);
@@ -56,14 +98,14 @@
56
98
  --text-caption: 0.6875rem;
57
99
  --text-tiny: 0.5625rem;
58
100
 
59
- /* The accent as a bare R G B triple, so shadow/glow tokens can compose alpha variants — and so a
101
+ /* The primary as a bare R G B triple, so shadow/glow tokens can compose alpha variants — and so a
60
102
  white-label theme can retint them by overriding this ONE variable instead of every baked shadow. */
61
- --accent-rgb: 255 82 54;
103
+ --primary-rgb: 255 82 54;
62
104
  --shadow-card: 0 1px 0 rgb(255 255 255 / 0.025), 0 14px 40px rgb(0 0 0 / 0.2);
63
105
  --shadow-raised: 0 1px 0 rgb(255 255 255 / 0.04), 0 22px 64px rgb(0 0 0 / 0.48);
64
- --shadow-ember: 0 16px 48px rgb(var(--accent-rgb) / 0.14);
65
- --glow-active: 0 0 0 1px rgb(var(--accent-rgb) / 0.3), 0 0 24px rgb(var(--accent-rgb) / 0.22), 0 0 64px rgb(var(--accent-rgb) / 0.12);
66
- --glow-soft: 0 0 44px rgb(var(--accent-rgb) / 0.1);
106
+ --shadow-ember: 0 16px 48px rgb(var(--primary-rgb) / 0.14);
107
+ --glow-active: 0 0 0 1px rgb(var(--primary-rgb) / 0.3), 0 0 24px rgb(var(--primary-rgb) / 0.22), 0 0 64px rgb(var(--primary-rgb) / 0.12);
108
+ --glow-soft: 0 0 44px rgb(var(--primary-rgb) / 0.1);
67
109
  --hairline: 1px solid rgb(255 255 255 / 0.075);
68
110
 
69
111
  --motion-instant: 80ms;