elowen-plugin-ui-kit 0.6.0 → 0.10.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/index.d.ts +160 -7
- package/index.js +3 -3
- package/package.json +1 -1
- package/theme.css +49 -0
package/index.d.ts
CHANGED
|
@@ -7,10 +7,11 @@ import type * as ReactDom from 'react-dom';
|
|
|
7
7
|
import type * as JsxRuntime from 'react/jsx-runtime';
|
|
8
8
|
import type { ComponentType } from 'react';
|
|
9
9
|
|
|
10
|
-
/** See index.js — bump
|
|
11
|
-
* the web app re-declares the value and annotates it with
|
|
12
|
-
* bump that forgets the host fails the web typecheck instead
|
|
13
|
-
|
|
10
|
+
/** See index.js — bump whenever a released bundle requires a newly published runtime contract.
|
|
11
|
+
* Deliberately a LITERAL type: the web app re-declares the value and annotates it with
|
|
12
|
+
* `typeof PLUGIN_UI_API_VERSION`, so a kit bump that forgets the host fails the web typecheck instead
|
|
13
|
+
* of drifting silently. */
|
|
14
|
+
export declare const PLUGIN_UI_API_VERSION: 16;
|
|
14
15
|
|
|
15
16
|
/** Public props of `ElowenUiRuntime.components.Slider`. */
|
|
16
17
|
export interface SliderProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'min' | 'max' | 'step' | 'type'> {
|
|
@@ -29,6 +30,83 @@ export interface DirectoryPickerProps {
|
|
|
29
30
|
onClose: () => void;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
/** Public props of the API 16 `ElowenUiRuntime.components.ProjectIcon`. */
|
|
34
|
+
export interface ProjectIconProps {
|
|
35
|
+
project: { id: number; icon?: string };
|
|
36
|
+
size?: number;
|
|
37
|
+
className?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PluginConfigField {
|
|
41
|
+
key: string;
|
|
42
|
+
label: string;
|
|
43
|
+
type:
|
|
44
|
+
| 'string' | 'secret' | 'boolean' | 'number' | 'textarea' | 'rolePolicies' | 'model' | 'provider'
|
|
45
|
+
| 'section' | 'enum' | 'multiSelect' | 'code' | 'prompt' | 'json' | 'embeddingModel' | 'mcpServers'
|
|
46
|
+
| 'destination' | 'projects' | 'plugins' | 'tools' | 'models' | 'timezone' | 'tokenList';
|
|
47
|
+
hint?: string;
|
|
48
|
+
required?: boolean;
|
|
49
|
+
min?: number;
|
|
50
|
+
max?: number;
|
|
51
|
+
step?: number;
|
|
52
|
+
placeholder?: string;
|
|
53
|
+
display?: { control?: 'input' | 'slider'; unit?: string; divisor?: number };
|
|
54
|
+
browse?: 'directory';
|
|
55
|
+
default?: string | number | boolean | string[];
|
|
56
|
+
providerType?: string;
|
|
57
|
+
options?: { value: string; label: string }[];
|
|
58
|
+
language?: string;
|
|
59
|
+
help?: string;
|
|
60
|
+
risk?: 'low' | 'medium' | 'high';
|
|
61
|
+
advanced?: boolean;
|
|
62
|
+
fullWidth?: boolean;
|
|
63
|
+
visibleWhen?: { key: string; equals: string | number | boolean };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type SaveStatus = 'idle' | 'saving' | 'saved' | 'pending' | 'error';
|
|
67
|
+
|
|
68
|
+
export interface AutoSaveStatusProps {
|
|
69
|
+
status: SaveStatus;
|
|
70
|
+
onRetry?: () => void | Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface UseAutoSaveStatusOptions {
|
|
74
|
+
ready?: boolean;
|
|
75
|
+
savable?: boolean;
|
|
76
|
+
delay?: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface UseAutoSaveStatusResult {
|
|
80
|
+
status: SaveStatus;
|
|
81
|
+
retry: () => Promise<void>;
|
|
82
|
+
flush: () => Promise<SaveStatus>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type UseAutoSaveStatus = (
|
|
86
|
+
deps: readonly unknown[],
|
|
87
|
+
save: () => unknown | Promise<unknown>,
|
|
88
|
+
options?: UseAutoSaveStatusOptions,
|
|
89
|
+
) => UseAutoSaveStatusResult;
|
|
90
|
+
|
|
91
|
+
export type PluginConfigErrorKind = 'validation' | 'conflict' | 'transport';
|
|
92
|
+
|
|
93
|
+
export interface PluginConfigDraft {
|
|
94
|
+
values: Record<string, unknown>;
|
|
95
|
+
setValue: (key: string, value: unknown) => void;
|
|
96
|
+
commitValue: (key: string, value: unknown) => Promise<{ pending: boolean }>;
|
|
97
|
+
status: SaveStatus;
|
|
98
|
+
errorKind: PluginConfigErrorKind | null;
|
|
99
|
+
retry: () => Promise<void>;
|
|
100
|
+
flush: () => Promise<SaveStatus>;
|
|
101
|
+
ready: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type UsePluginConfigDraft = (
|
|
105
|
+
name: string,
|
|
106
|
+
detail: { config: Record<string, unknown>; configSchema: readonly PluginConfigField[]; revision?: number },
|
|
107
|
+
options?: { save?: (value: { name: string; values: Record<string, unknown>; expectedRevision?: number }) => Promise<unknown> },
|
|
108
|
+
) => PluginConfigDraft;
|
|
109
|
+
|
|
32
110
|
export type ConfirmDialogButtonVariant = 'default' | 'accent' | 'ghost' | 'danger' | 'ghost-danger' | 'outline' | 'outline-danger';
|
|
33
111
|
|
|
34
112
|
/** Public props of the API 11 async-safe `ElowenUiRuntime.components.ConfirmDialog`. */
|
|
@@ -97,7 +175,7 @@ export interface PluginPageProps {
|
|
|
97
175
|
* inside a group header can ignore it; a section declaring `layout: 'orbital'` cannot — the orbital
|
|
98
176
|
* group is a field of pods with no header to hold one, so this channel is the only place its user
|
|
99
177
|
* ever learns that a save failed. */
|
|
100
|
-
onSaveState?: (status:
|
|
178
|
+
onSaveState?: (status: SaveStatus, retry?: () => void | Promise<void>) => void;
|
|
101
179
|
}
|
|
102
180
|
|
|
103
181
|
/** Props for a contextual Project panel. It is never a standalone route: the selected Project and panel
|
|
@@ -117,6 +195,73 @@ export interface PluginUserPanelProps {
|
|
|
117
195
|
surface: 'user';
|
|
118
196
|
}
|
|
119
197
|
|
|
198
|
+
/** JSON payload published with a plugin-owned inline chat artifact. */
|
|
199
|
+
export type PluginChatArtifactData = null | boolean | number | string | PluginChatArtifactData[] | { [key: string]: PluginChatArtifactData };
|
|
200
|
+
|
|
201
|
+
/** An optional authenticated same-plugin SSE source the artifact component may consume. */
|
|
202
|
+
export interface PluginChatArtifactLiveMedia {
|
|
203
|
+
transport: 'sse';
|
|
204
|
+
path: string;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** The normalized open artifact snapshot handed to a registered chat artifact component. */
|
|
208
|
+
export interface PluginChatArtifact {
|
|
209
|
+
id: string;
|
|
210
|
+
plugin: string;
|
|
211
|
+
sessionId: string;
|
|
212
|
+
toolCallId: string;
|
|
213
|
+
view: string;
|
|
214
|
+
fallback: string;
|
|
215
|
+
data?: PluginChatArtifactData;
|
|
216
|
+
media?: PluginChatArtifactLiveMedia;
|
|
217
|
+
expiresAt: string;
|
|
218
|
+
status: 'open';
|
|
219
|
+
createdAt: string;
|
|
220
|
+
updatedAt: string;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** A prompt the host is waiting on, offered to an artifact that covers it (API 15).
|
|
224
|
+
*
|
|
225
|
+
* It carries no content at all — not the question, not its options, not an id. A surface that hides the
|
|
226
|
+
* host's question card only needs to say that one is waiting and to hand the reader back to it, and both
|
|
227
|
+
* of those are host-owned: `label` is the app's own translated line, `reveal` brings the real card into
|
|
228
|
+
* view and focuses it. Everything about answering — the options, validation, the request — stays where it
|
|
229
|
+
* already is, so nothing an artifact renders can drift from what the user is actually being asked. */
|
|
230
|
+
export interface PluginChatPendingInput {
|
|
231
|
+
/** The host's own user-visible line, already translated. Render it as-is. */
|
|
232
|
+
label: string;
|
|
233
|
+
/** Bring the host's prompt into view and focus it. A covering surface should close itself first. */
|
|
234
|
+
reveal: () => void;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Props for a plugin component rendered inline after its artifact's matching tool segment. */
|
|
238
|
+
export interface PluginChatArtifactProps {
|
|
239
|
+
plugin: string;
|
|
240
|
+
artifact: PluginChatArtifact;
|
|
241
|
+
/** API 15. Set while the host is waiting on an answer from the user, `null` when it is not.
|
|
242
|
+
*
|
|
243
|
+
* Same reason as `narration`: an artifact whose surface covers the dock covers the question card with
|
|
244
|
+
* it, and a reader who cannot see that they are being asked something will sit and wait for an agent
|
|
245
|
+
* that is waiting for them. Show that a prompt is waiting, then call `reveal` — after closing your own
|
|
246
|
+
* surface — to put the reader in front of the real card.
|
|
247
|
+
*
|
|
248
|
+
* It clears the moment the prompt is answered, discarded or the conversation is switched. Hosts older
|
|
249
|
+
* than API 15 pass nothing, so read it as optional. */
|
|
250
|
+
pendingInput?: PluginChatPendingInput | null;
|
|
251
|
+
/** API 14. The assistant prose the transcript is rendering RIGHT NOW, for an artifact that draws a
|
|
252
|
+
* surface over the dock and therefore hides the conversation it belongs to.
|
|
253
|
+
*
|
|
254
|
+
* It is the host's own visible text, projected — the newest assistant turn's latest text segment,
|
|
255
|
+
* whitespace-collapsed and capped at 240 characters — and deliberately nothing else: no tool payloads,
|
|
256
|
+
* no hidden reasoning, no system content, no history, and empty (`''`) as soon as the newest turn is
|
|
257
|
+
* the user's or carries no prose. A bundle renders it as PLAIN TEXT; the transcript owns markdown, and
|
|
258
|
+
* a second composition of it would be a second source of truth for what the user was told.
|
|
259
|
+
*
|
|
260
|
+
* It updates as the reply streams, so treat it as a live value: bound how much of it you draw, and do
|
|
261
|
+
* not accumulate it. Hosts older than API 14 pass nothing, so read it as optional. */
|
|
262
|
+
narration?: string;
|
|
263
|
+
}
|
|
264
|
+
|
|
120
265
|
/** What a bundle hands to window.__elowenRegisterPluginUi. Routes are `/`-joined segment patterns
|
|
121
266
|
* (`''` = the root page, `detail/:id` captures params). Contextual component maps are keyed by their
|
|
122
267
|
* matching manifest panel ids and mount only in the corresponding host surface. */
|
|
@@ -132,6 +277,8 @@ export interface PluginUiRegistration {
|
|
|
132
277
|
accountChip?: Record<string, ComponentType<PluginPageProps>>;
|
|
133
278
|
user?: Record<string, ComponentType<PluginUserPanelProps>>;
|
|
134
279
|
project?: Record<string, ComponentType<PluginProjectPanelProps>>;
|
|
280
|
+
/** Inline chat views keyed by the artifact `view` selected by the publishing plugin. */
|
|
281
|
+
chatArtifacts?: Record<string, ComponentType<PluginChatArtifactProps>>;
|
|
135
282
|
settings?: Record<string, ComponentType<PluginPageProps>>;
|
|
136
283
|
/** Ids of `settings` sections that draw their OWN page frame — the shell, the masthead and the save
|
|
137
284
|
* indicator — and therefore want none from the host.
|
|
@@ -157,11 +304,17 @@ export interface ElowenUiRuntime {
|
|
|
157
304
|
react: typeof React;
|
|
158
305
|
reactDom: typeof ReactDom;
|
|
159
306
|
jsxRuntime: typeof JsxRuntime;
|
|
160
|
-
components: Record<string, ComponentType<never
|
|
307
|
+
components: Record<string, ComponentType<never>> & {
|
|
308
|
+
AutoSaveStatus: ComponentType<AutoSaveStatusProps>;
|
|
309
|
+
ProjectIcon: ComponentType<ProjectIconProps>;
|
|
310
|
+
};
|
|
161
311
|
/** Curated React hooks (i18n, toasts, the app's react-query data hooks). Safe across the boundary:
|
|
162
312
|
* the bundle runs on the HOST's React instance, so the rules of hooks hold. A bundle narrows each
|
|
163
313
|
* entry to the signature it expects; an absent name means the host predates the bundle. */
|
|
164
|
-
hooks: Record<string, unknown
|
|
314
|
+
hooks: Record<string, unknown> & {
|
|
315
|
+
useAutoSaveStatus: UseAutoSaveStatus;
|
|
316
|
+
usePluginConfigDraft: UsePluginConfigDraft;
|
|
317
|
+
};
|
|
165
318
|
/** Curated pure helpers (formatting, session/task mapping, error shaping) shared with bundles. */
|
|
166
319
|
utils: Record<string, unknown>;
|
|
167
320
|
api: (path: string, init?: RequestInit) => Promise<unknown>;
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** The single source of truth for the plugin browser-UI contract version. The web app installs
|
|
2
2
|
* `window.ElowenUiRuntime` stamped with this number; a bundle whose `requiresApiVersion` is NEWER
|
|
3
|
-
* renders a placeholder instead of executing against a contract it was not built for. Bump
|
|
4
|
-
*
|
|
5
|
-
export const PLUGIN_UI_API_VERSION =
|
|
3
|
+
* renders a placeholder instead of executing against a contract it was not built for. Bump whenever
|
|
4
|
+
* a released bundle requires a newly published `ElowenUiRuntime` contract (see index.d.ts). */
|
|
5
|
+
export const PLUGIN_UI_API_VERSION = 16;
|
package/package.json
CHANGED
package/theme.css
CHANGED
|
@@ -40,6 +40,22 @@
|
|
|
40
40
|
cannot see, inherits a correct sticky colour without declaring one. */
|
|
41
41
|
--color-sticky: color-mix(in srgb, var(--color-document) 94%, var(--color-foreground) 6%);
|
|
42
42
|
|
|
43
|
+
/* The primary navigation column's own surfaces — shadcn's `sidebar` namespace, which the adopted
|
|
44
|
+
`components/ui/shadcn/sidebar.tsx` compiles its `bg-sidebar` / `bg-sidebar-accent` utilities from.
|
|
45
|
+
A sidebar is a chrome region rather than a document surface: it sits one step quieter than the
|
|
46
|
+
content it insets from, its rows take a fill of their own that is not `--color-muted` (a CONTENT
|
|
47
|
+
step that moves with the cards), and its rules are drawn against its own ground.
|
|
48
|
+
A design with no opinion inherits the aliases below and looks exactly as it did, so this costs
|
|
49
|
+
nothing to a skin that does not want the distinction. They live in `@theme` rather than `:root`
|
|
50
|
+
because Tailwind only emits a `bg-sidebar` utility for a token it can see here; the plugin-ui-kit
|
|
51
|
+
mirror carries them for that reason alone — a plugin bundle still has no sidebar to paint. */
|
|
52
|
+
--color-sidebar: var(--color-background);
|
|
53
|
+
--color-sidebar-foreground: var(--color-muted-foreground);
|
|
54
|
+
--color-sidebar-accent: var(--color-muted);
|
|
55
|
+
--color-sidebar-accent-foreground: var(--color-foreground);
|
|
56
|
+
--color-sidebar-border: var(--color-border);
|
|
57
|
+
--color-sidebar-ring: var(--color-ring);
|
|
58
|
+
|
|
43
59
|
--color-border: #242424;
|
|
44
60
|
/* NOT a shadcn token: the heavier rule, for a scrollbar thumb and a focused edge. */
|
|
45
61
|
--color-border-strong: #363636;
|
|
@@ -67,6 +83,10 @@
|
|
|
67
83
|
three states, and borrowing `primary` or `chart-*` for them would say something they do not mean. */
|
|
68
84
|
--color-success: #32c986;
|
|
69
85
|
--color-success-foreground: var(--color-primary-foreground);
|
|
86
|
+
/* Toasts are outcome banners, not tiny status marks. GitHub's Primer success green gives them the
|
|
87
|
+
saturated diff/merge signal Filip expects while keeping white body text above AA contrast. */
|
|
88
|
+
--color-toast-success: #1f883d;
|
|
89
|
+
--color-toast-success-foreground: #ffffff;
|
|
70
90
|
--color-warning: #edae49;
|
|
71
91
|
--color-info: #62a8ea;
|
|
72
92
|
|
|
@@ -108,6 +128,35 @@
|
|
|
108
128
|
--glow-soft: 0 0 44px rgb(var(--primary-rgb) / 0.1);
|
|
109
129
|
--hairline: 1px solid rgb(255 255 255 / 0.075);
|
|
110
130
|
|
|
131
|
+
/* ---- Depth ----------------------------------------------------------------------------------
|
|
132
|
+
NOT shadcn tokens. shadcn's depth vocabulary is Tailwind's shadow scale, which is a set of fixed
|
|
133
|
+
black shadows — on a true-black canvas every one of them is a no-op, and on a near-white one they
|
|
134
|
+
are all too heavy. These four name the depth EFFECTS the app actually composes, so each design
|
|
135
|
+
states its own version of them once instead of every card guessing.
|
|
136
|
+
|
|
137
|
+
`--shadow-lift` is the hover step above `--shadow-card`: a card that rises under the pointer. It is
|
|
138
|
+
a peer of `--shadow-card`, not a replacement — a resting card keeps the quieter one.
|
|
139
|
+
|
|
140
|
+
`--glow-accent` is the accent halo a FOCUSED or ACTIVE surface takes: the composer under the caret,
|
|
141
|
+
the navigation row you are standing on. Distinct from `--glow-active`, which is the loud ember ring
|
|
142
|
+
the spatial design paints on its orbit nodes; this one has to stay usable behind body text.
|
|
143
|
+
|
|
144
|
+
`--glass-surface` + `--glass-blur` are the translucent working surface (the composer). The fill is a
|
|
145
|
+
MIX of a surface token rather than a fixed alpha white, so a design that has no light to scatter can
|
|
146
|
+
turn the effect off by mixing to 100% and setting the blur to 0 without touching a component. */
|
|
147
|
+
--shadow-lift: 0 1px 0 rgb(255 255 255 / 0.05), 0 24px 60px rgb(0 0 0 / 0.34);
|
|
148
|
+
--glow-accent: 0 0 0 1px rgb(var(--primary-rgb) / 0.28), 0 0 30px rgb(var(--primary-rgb) / 0.18);
|
|
149
|
+
--glass-surface: color-mix(in srgb, var(--color-card) 62%, transparent);
|
|
150
|
+
--glass-border: color-mix(in srgb, var(--color-foreground) 10%, transparent);
|
|
151
|
+
--glass-blur: 18px;
|
|
152
|
+
|
|
153
|
+
/* The accent atmosphere behind a hero. Two off-centre radial stops rather than one centred wash: a
|
|
154
|
+
single centred glow reads as a vignette bug, two offset ones read as light entering the page. It is
|
|
155
|
+
an IMAGE, so it composes over whatever canvas the design owns and needs no surface of its own. */
|
|
156
|
+
--mesh-accent:
|
|
157
|
+
radial-gradient(62% 78% at 16% -8%, rgb(var(--primary-rgb) / 0.16), transparent 68%),
|
|
158
|
+
radial-gradient(52% 68% at 86% 4%, rgb(var(--primary-rgb) / 0.1), transparent 70%);
|
|
159
|
+
|
|
111
160
|
--motion-instant: 80ms;
|
|
112
161
|
--motion-fast: 140ms;
|
|
113
162
|
--motion-base: 240ms;
|