@w6w/ui 0.2.0 → 0.2.11

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 (78) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/AppPicker.d.ts +47 -2
  3. package/dist/CanvasLayer.d.ts +38 -0
  4. package/dist/{CodeBlock-Dot3Pd7P.js → CodeBlock-BqXDA35P.js} +2 -2
  5. package/dist/{CodeBlock-Dot3Pd7P.js.map → CodeBlock-BqXDA35P.js.map} +1 -1
  6. package/dist/{CodeBlock-CZu4TFSi.cjs → CodeBlock-CulfbnZD.cjs} +2 -2
  7. package/dist/{CodeBlock-CZu4TFSi.cjs.map → CodeBlock-CulfbnZD.cjs.map} +1 -1
  8. package/dist/ConfirmModal-Ln99S9NT.js +7919 -0
  9. package/dist/ConfirmModal-Ln99S9NT.js.map +1 -0
  10. package/dist/ConfirmModal-fjHW5rN-.cjs +14 -0
  11. package/dist/ConfirmModal-fjHW5rN-.cjs.map +1 -0
  12. package/dist/Copyable-DA6eiU7w.cjs +2 -0
  13. package/dist/Copyable-DA6eiU7w.cjs.map +1 -0
  14. package/dist/{Copyable-1niV9gUa.js → Copyable-F4NTaG77.js} +91 -15
  15. package/dist/Copyable-F4NTaG77.js.map +1 -0
  16. package/dist/EditConnectionModal.d.ts +6 -1
  17. package/dist/JsonEditor.d.ts +8 -0
  18. package/dist/Markdown.d.ts +25 -0
  19. package/dist/ParamsForm.d.ts +7 -1
  20. package/dist/StepBuilderModal.d.ts +23 -5
  21. package/dist/WorkflowFlowEditor.d.ts +14 -3
  22. package/dist/app-pages.d.ts +2 -0
  23. package/dist/canvas-types.d.ts +36 -0
  24. package/dist/canvas-utils.d.ts +44 -0
  25. package/dist/code.cjs +1 -1
  26. package/dist/code.mjs +2 -2
  27. package/dist/components/AuthFieldsForm.d.ts +11 -0
  28. package/dist/components/BudgetMeter.d.ts +15 -0
  29. package/dist/components/CallableList.d.ts +3 -2
  30. package/dist/components/Combobox.d.ts +8 -1
  31. package/dist/components/ConnectionList.d.ts +2 -3
  32. package/dist/components/CredentialPicker.d.ts +35 -0
  33. package/dist/components/DataTable.d.ts +27 -0
  34. package/dist/components/EmptyState.d.ts +9 -0
  35. package/dist/components/icons.d.ts +1 -1
  36. package/dist/dynamic-fields.d.ts +60 -0
  37. package/dist/editor-theme.d.ts +24 -0
  38. package/dist/flow-fanout.d.ts +2 -2
  39. package/dist/flow-types.d.ts +18 -1
  40. package/dist/flow-utils.d.ts +10 -3
  41. package/dist/flow.cjs +2 -2
  42. package/dist/flow.cjs.map +1 -1
  43. package/dist/flow.d.ts +9 -0
  44. package/dist/flow.mjs +1519 -1138
  45. package/dist/flow.mjs.map +1 -1
  46. package/dist/index.cjs +1 -1
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.d.ts +14 -2
  49. package/dist/index.mjs +1362 -1123
  50. package/dist/index.mjs.map +1 -1
  51. package/dist/note-markdown.d.ts +22 -0
  52. package/dist/provider.d.ts +56 -7
  53. package/dist/resolve-params.d.ts +5 -0
  54. package/dist/run-visuals.d.ts +4 -1
  55. package/dist/step-problems.d.ts +34 -0
  56. package/dist/types.d.ts +109 -3
  57. package/dist/use-dynamic-fields.d.ts +23 -0
  58. package/package.json +4 -2
  59. package/src/styles/_budget-meter.scss +46 -0
  60. package/src/styles/_callable-list.scss +15 -6
  61. package/src/styles/_canvas.scss +318 -0
  62. package/src/styles/_connections.scss +2 -14
  63. package/src/styles/_data-table.scss +55 -0
  64. package/src/styles/_empty-state.scss +21 -0
  65. package/src/styles/_flow.scss +11 -0
  66. package/src/styles/_fx.scss +23 -0
  67. package/src/styles/_layout.scss +10 -0
  68. package/src/styles/_markdown.scss +20 -0
  69. package/src/styles/_tester.scss +1 -14
  70. package/src/styles.css +572 -39
  71. package/src/styles.scss +5 -0
  72. package/dist/ConfirmModal-ChoiGy2K.cjs +0 -11
  73. package/dist/ConfirmModal-ChoiGy2K.cjs.map +0 -1
  74. package/dist/ConfirmModal-Wx3i8F1n.js +0 -6359
  75. package/dist/ConfirmModal-Wx3i8F1n.js.map +0 -1
  76. package/dist/Copyable-1niV9gUa.js.map +0 -1
  77. package/dist/Copyable-CYZU1o4X.cjs +0 -2
  78. package/dist/Copyable-CYZU1o4X.cjs.map +0 -1
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The note editor's Markdown round-trip. A note is edited in place as a `contentEditable` block
3
+ * (no textarea); these two functions convert between its stored Markdown and that editable DOM.
4
+ *
5
+ * The editable model is line-based and covers exactly what the note toolbar writes: a heading
6
+ * level per line (`#`…`######`) and `**bold**` runs. Everything else (links, lists, `code`, …) is
7
+ * kept as literal text while editing and rendered by `Markdown` when not, so a line the toolbar
8
+ * never touched round-trips byte-for-byte.
9
+ *
10
+ * The DOM is built with `createElement` + text nodes only — note content is imported,
11
+ * attacker-controllable text and must never reach `innerHTML`.
12
+ */
13
+ /** Build the editable DOM for `content` into `root` (its children are replaced). */
14
+ export declare function renderEditable(root: HTMLElement, content: string): void;
15
+ /** Serialize the editable DOM under `root` back to Markdown. */
16
+ export declare function editableToMarkdown(root: HTMLElement): string;
17
+ /**
18
+ * The content handed to `Markdown` for display. A note is typed line by line, so a single
19
+ * newline must show as a line break — Markdown would fold it into a space. A soft break between
20
+ * two non-empty lines becomes a hard break (two trailing spaces); the stored content is untouched.
21
+ */
22
+ export declare function noteDisplayMarkdown(content: string): string;
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- import { ActionDef, ApiCallRecord, AppSummary, AuthDef, ConnectionSummary, FunctionDetail, FunctionSummary, SavedTest, SubscriptionSummary, ThemeMode, TriggerSummary, WorkflowDetail, WorkflowSummary } from './types.ts';
2
+ import { ActionDef, ApiCallRecord, AppSummary, AuthDef, ConnectionBinding, ConnectionSummary, CreateConnectionBody, CredentialDependents, CredentialSummary, CredentialTestResult, FunctionDetail, FunctionSummary, ResolveFieldsBody, ResolveFieldsResult, SavedTest, SubscriptionSummary, ThemeMode, TriggerSummary, WorkflowDetail, WorkflowSummary } from './types.ts';
3
3
  /**
4
4
  * A single tester-run summary from the unified `run_log` ledger, keyed to a
5
5
  * connection. Thin by design — the full result payload stays server-side in
@@ -111,6 +111,8 @@ export interface ListAppsPageOptions {
111
111
  q?: string;
112
112
  /** Server-side category filter (e.g. `"ai"`). */
113
113
  category?: string;
114
+ /** Server-side id allow-list (`GET /apps?ids=a,b`); `total` is then the filtered total. */
115
+ ids?: string[];
114
116
  /** Opaque pagination cursor from a prior {@link AppsPageResult.nextCursor}. */
115
117
  cursor?: string;
116
118
  /** Page size; a host may clamp this to its own bounds. */
@@ -174,15 +176,31 @@ export interface W6WApi {
174
176
  listAppsByIds?(ids: readonly string[], options?: {
175
177
  signal?: AbortSignal;
176
178
  }): Promise<AppSummary[]>;
179
+ /**
180
+ * OPTIONAL: the tenant's model routes, for the `model-route` step-param
181
+ * picker (`@w6w/model` `complete`). Absent, the param renders as a plain text
182
+ * input and this is never called. A `broken` route is still selectable.
183
+ * `project` is the editor's own project (`WorkflowProjectProvider`) when it
184
+ * has one — a host should list THAT project's routes, not guess one from its
185
+ * URL; `undefined` leaves the choice to the host.
186
+ */
187
+ listModelRoutes?(project?: string): Promise<{
188
+ name: string;
189
+ model: string;
190
+ status: "active" | "broken";
191
+ }[]>;
192
+ /**
193
+ * OPTIONAL: resolve the choices of `options: { source: "resolveFields" }`
194
+ * params from the current form (`POST` to the host). Absent, those params
195
+ * render as plain inputs and this is never called.
196
+ */
197
+ resolveActionFields?(appId: string, actionKey: string, body: ResolveFieldsBody, opts?: {
198
+ signal?: AbortSignal;
199
+ }): Promise<ResolveFieldsResult>;
177
200
  /** Load auth methods declared by an app's manifest, with availability flags. */
178
201
  getAppAuth(appId: string): Promise<AuthDef[]>;
179
202
  /** Create a non-OAuth connection with a user-supplied credential. */
180
- createConnection(appId: string, body: {
181
- authKey: string;
182
- credential: Record<string, unknown>;
183
- displayName?: string;
184
- profile?: Record<string, unknown>;
185
- }): Promise<ConnectionSummary>;
203
+ createConnection(appId: string, body: CreateConnectionBody): Promise<ConnectionSummary>;
186
204
  /**
187
205
  * Rename a connection and/or replace some of its credential fields (`PATCH /connections/:id`).
188
206
  * Send only the fields being changed: the server merges a partial `credential` over the stored
@@ -191,8 +209,34 @@ export interface W6WApi {
191
209
  */
192
210
  updateConnection?(id: string, body: {
193
211
  displayName?: string;
212
+ binding?: ConnectionBinding;
213
+ credentialId?: string;
194
214
  credential?: Record<string, unknown>;
195
215
  }): Promise<ConnectionSummary>;
216
+ /** List stored credentials, optionally narrowed (`GET /credentials`). */
217
+ listCredentials?(filter?: {
218
+ contract?: string;
219
+ appId?: string;
220
+ authKey?: string;
221
+ }): Promise<CredentialSummary[]>;
222
+ /** Store a new credential (`POST /credentials`). `source` is the auth's field-value record. */
223
+ createCredential?(body: {
224
+ name: string;
225
+ contract: string;
226
+ source: Record<string, unknown>;
227
+ }): Promise<CredentialSummary>;
228
+ getCredential?(id: string): Promise<CredentialSummary>;
229
+ /** Replace a credential's secret (`POST /credentials/:id/rotate`). */
230
+ rotateCredential?(id: string, body: {
231
+ source: Record<string, unknown>;
232
+ }): Promise<CredentialSummary>;
233
+ /** Disable (`/disable`) or re-enable (`/enable`) a credential. */
234
+ setCredentialStatus?(id: string, status: "active" | "disabled"): Promise<CredentialSummary>;
235
+ /** Mint-level health check; distinct from a connection's app-access test. */
236
+ testCredential?(id: string): Promise<CredentialTestResult>;
237
+ credentialDependents?(id: string): Promise<CredentialDependents>;
238
+ /** Rejects with `ApiError` code `credential_in_use` (body.error.dependents) when in use. */
239
+ deleteCredential?(id: string): Promise<void>;
196
240
  /**
197
241
  * Start an OAuth 2.0 flow. Server builds the provider's authorize URL and
198
242
  * returns it; the caller opens it in a popup and awaits the server's
@@ -431,6 +475,11 @@ export declare function W6WUIProvider({ api, theme, children }: W6WUIProviderPro
431
475
  * `<W6WUIProvider>` — the common mistake is forgetting to wrap the app root.
432
476
  */
433
477
  export declare function useW6WApi(): W6WApi;
478
+ /**
479
+ * Like {@link useW6WApi} but returns `undefined` outside a provider instead of
480
+ * throwing — for optional enhancements (e.g. the `model-route` picker).
481
+ */
482
+ export declare function useOptionalW6WApi(): W6WApi | undefined;
434
483
  /** Scopes test-invokes under it to `project`; the editor wraps its body with this. */
435
484
  export declare function WorkflowProjectProvider({ project, children, }: {
436
485
  project?: string;
@@ -38,6 +38,11 @@ export interface ResolveScope {
38
38
  output?: unknown;
39
39
  meta?: StepMeta;
40
40
  }>;
41
+ /** `nodes.<id>…` root — the SAME object as `steps` (one state, two roots). */
42
+ nodes?: Record<string, {
43
+ output?: unknown;
44
+ meta?: StepMeta;
45
+ }>;
41
46
  trigger: {
42
47
  event?: unknown;
43
48
  };
@@ -28,7 +28,10 @@ export interface StepExecution {
28
28
  * as the engine's own `RunState.steps`. */
29
29
  export interface RunState {
30
30
  status: RunStatus;
31
- steps: Record<string, StepExecution>;
31
+ /** Canonical per-node records (D2). */
32
+ nodes?: Record<string, StepExecution>;
33
+ /** Legacy alias of `nodes`; read as `nodes ?? steps` (un-upgraded server). */
34
+ steps?: Record<string, StepExecution>;
32
35
  }
33
36
  /**
34
37
  * A step node's rendered state — exactly the five {@link StepStatus} values
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Host-supplied per-step problems painted on the canvas. Pure — no React or
3
+ * @xyflow imports. The host owns detection; ui never infers a problem itself.
4
+ */
5
+ export interface StepProblem {
6
+ code: "unknown_connection";
7
+ connection: string;
8
+ }
9
+ /** Keyed by step id. */
10
+ export type StepProblems = Record<string, StepProblem>;
11
+ interface ProblemNode {
12
+ id: string;
13
+ data: {
14
+ stepProblem?: StepProblem;
15
+ };
16
+ }
17
+ /**
18
+ * Stamp `data.stepProblem` from `problems`, clearing it for steps no longer in
19
+ * the map. Returns the SAME node object when nothing changed.
20
+ */
21
+ export declare function applyStepProblems<N extends ProblemNode>(nodes: N[], problems: StepProblems | undefined): N[];
22
+ export declare function stepProblemTooltip(p: StepProblem): string;
23
+ /** True while the step still points at the connection the host reports missing. */
24
+ export declare function isConnectionBlocked(step: {
25
+ uses: {
26
+ connection?: string | null;
27
+ };
28
+ }, problem: StepProblem | undefined): boolean;
29
+ /** The Setup tab's Connection button label — exactly five outcomes. */
30
+ export declare function connectionButtonLabel(connection: string | undefined | null, conns: {
31
+ id: string;
32
+ displayName?: string;
33
+ }[] | null, failed: boolean): string;
34
+ export {};
package/dist/types.d.ts CHANGED
@@ -22,6 +22,11 @@ export interface AppSummary {
22
22
  maturity?: string;
23
23
  visibility?: string;
24
24
  successor?: string;
25
+ /** Model-provider designation; absent when the app does not declare one. */
26
+ modelProvider?: {
27
+ modelTypes: string[];
28
+ modelFamilies?: string[];
29
+ };
25
30
  /**
26
31
  * True when the calling tenant has zero-credential access flagged for this
27
32
  * app — usable with no connection step at all (server-computed per caller;
@@ -68,6 +73,8 @@ export interface AuthField {
68
73
  required?: boolean;
69
74
  default?: unknown;
70
75
  hint?: string;
76
+ /** Masks the input like a `secret` field whatever its `type` (e.g. a `json` key file). */
77
+ secret?: boolean;
71
78
  }
72
79
  /**
73
80
  * One declared parameter of an action, as returned by GET /apps/:id (the
@@ -119,7 +126,7 @@ export interface ActionParam {
119
126
  * Constrained choices. Renders as a single-select dropdown for `select` (or any
120
127
  * param with options), and as a multi-select pill picker for `multiselect`.
121
128
  */
122
- options?: ParamOption[];
129
+ options?: ParamOption[] | DynamicOptions;
123
130
  /** Type-specific render/behavior options. */
124
131
  config?: ParamConfig;
125
132
  /**
@@ -146,6 +153,34 @@ export interface ActionParam {
146
153
  export interface ParamOption {
147
154
  value: string | number;
148
155
  label: string;
156
+ description?: string;
157
+ }
158
+ /** Host-resolved options (mirrors @w6w/types `DynamicOptions`). */
159
+ export interface DynamicOptions {
160
+ source: string;
161
+ searchable?: boolean;
162
+ cache?: "session" | "none";
163
+ refreshOn?: string[];
164
+ }
165
+ /** True for the form-resolved arm: a non-array object with `source === "resolveFields"`. */
166
+ export declare function isFormResolvedOptions(o: unknown): o is DynamicOptions;
167
+ /** Body of `W6WApi.resolveActionFields`. */
168
+ export interface ResolveFieldsBody {
169
+ connectionId: string;
170
+ version?: string;
171
+ reason: "init" | "change";
172
+ form: Record<string, unknown>;
173
+ previous?: Record<string, unknown>;
174
+ changed?: string[];
175
+ fields: string[];
176
+ unresolved: string[];
177
+ }
178
+ /** Result of `W6WApi.resolveActionFields`. */
179
+ export interface ResolveFieldsResult {
180
+ fields: Record<string, {
181
+ options?: ParamOption[];
182
+ error?: string;
183
+ }>;
149
184
  }
150
185
  /**
151
186
  * The element schema for a `type: "array"` param. Either a scalar list
@@ -184,6 +219,8 @@ export interface ParamCondition {
184
219
  export interface ParamConfig {
185
220
  /** Render as a multi-line textarea. Implied by `type: "text"`. */
186
221
  multiline?: boolean;
222
+ /** With options: accept a value not in the list (Combobox `forceSelection` off). */
223
+ allowCustom?: boolean;
187
224
  }
188
225
  /** Summary of an action an app exposes, as returned by GET /apps/:id. */
189
226
  export interface ActionDef {
@@ -267,16 +304,83 @@ export interface WorkflowDetail {
267
304
  name: string;
268
305
  displayName?: string;
269
306
  description?: string;
270
- steps: WorkflowStepSummary[];
307
+ /** The canonical (manifest v2) step list. */
308
+ nodes?: WorkflowStepSummary[];
309
+ /** The legacy (v1) step list; a definition carries one or the other. */
310
+ steps?: WorkflowStepSummary[];
311
+ }
312
+ /** A `serviceAccount` auth's declaration: the credential contract it accepts and its binding. */
313
+ export interface ServiceAccountConfig {
314
+ /** Credential contract ids this auth accepts; the first one is used to list/create. */
315
+ accepts: string[];
316
+ /** Scopes the binding requests by default (shown read-only; the server applies them). */
317
+ scopes: string[];
318
+ /** Whether a connection must/may name an impersonated user (`delegatedUser`). */
319
+ impersonation: "none" | "optional" | "required";
320
+ /** Extra per-connection (binding) fields; each lands in `binding.settings`. */
321
+ bindingFields?: AuthField[];
322
+ }
323
+ export type CredentialStatus = "active" | "disabled" | "revoked";
324
+ /** A stored credential as the API lists it. Never carries the secret payload. */
325
+ export interface CredentialSummary {
326
+ id: string;
327
+ name: string;
328
+ contract: string;
329
+ status: CredentialStatus | string;
330
+ revision: number;
331
+ /** Non-secret display values (e.g. the service account's email). */
332
+ display: Record<string, string>;
333
+ private: boolean;
334
+ connectionCount: number;
335
+ createdAt: string;
336
+ updatedAt: string;
337
+ }
338
+ /** The connections that use a credential. */
339
+ export interface CredentialDependents {
340
+ connections: Array<{
341
+ id: string;
342
+ appId: string;
343
+ authKey: string;
344
+ displayName?: string | null;
345
+ }>;
346
+ }
347
+ export interface CredentialTestResult {
348
+ ok: boolean;
349
+ code?: string;
350
+ message?: string;
351
+ }
352
+ /** Per-connection settings applied on top of a shared credential. */
353
+ export interface ConnectionBinding {
354
+ scopes?: string[];
355
+ /** The user the credential impersonates (Google domain-wide delegation). */
356
+ delegatedUser?: string;
357
+ settings?: Record<string, string | number | boolean>;
271
358
  }
359
+ /** Body of `createConnection`: an inline credential XOR a reference to a stored one. */
360
+ export type CreateConnectionBody = {
361
+ authKey: string;
362
+ credential: Record<string, unknown>;
363
+ credentialId?: never;
364
+ displayName?: string;
365
+ profile?: Record<string, unknown>;
366
+ } | {
367
+ authKey: string;
368
+ credentialId: string;
369
+ credential?: never;
370
+ binding?: ConnectionBinding;
371
+ displayName?: string;
372
+ profile?: never;
373
+ };
272
374
  /** Auth method declaration as exposed by an app's manifest. */
273
375
  export interface AuthDef {
274
376
  key: string;
275
- type: "oauth2" | "apiKey" | "basic" | "bearer" | "custom" | "tenantAuth" | "jit" | string;
377
+ type: "oauth2" | "apiKey" | "basic" | "bearer" | "custom" | "tenantAuth" | "jit" | "serviceAccount" | string;
276
378
  displayName?: string;
277
379
  description?: string;
278
380
  connectionLabel?: string;
279
381
  fields?: AuthField[];
382
+ /** Present when `type === "serviceAccount"`: what credential it accepts and how it binds. */
383
+ serviceAccount?: ServiceAccountConfig;
280
384
  /** OAuth2 endpoints (only present when type === "oauth2"). */
281
385
  oauth2?: Record<string, unknown>;
282
386
  /**
@@ -314,6 +418,8 @@ export interface ConnectionSummary {
314
418
  id: string;
315
419
  appId: string;
316
420
  authKey: string;
421
+ /** The stored credential this connection uses, when it has one. */
422
+ credentialId?: string | null;
317
423
  /** The owning tenant. The server sends `tenant`/`subject`, never `owner`. */
318
424
  tenant?: string;
319
425
  /** The owning subject (the partner's user id). */
@@ -0,0 +1,23 @@
1
+ import { DynamicContext } from './dynamic-fields.ts';
2
+ import { ActionParam, ParamOption } from './types.ts';
3
+ /** Per-field state the form renders from. */
4
+ export interface DynamicFieldState {
5
+ options?: ParamOption[];
6
+ loading: boolean;
7
+ error?: string;
8
+ /** Why the field is free text (no connection / unresolved refreshOn). */
9
+ hint?: string;
10
+ /** Flag for a kept value not in the returned list. */
11
+ notInList?: string;
12
+ }
13
+ export interface UseDynamicFields {
14
+ fields: Record<string, DynamicFieldState>;
15
+ /** A pick committed `value` for `key` (select/Combobox/checkbox): one change call if any field refreshes on it. */
16
+ commit(key: string, value: unknown): void;
17
+ /** Focus entered `key`: remember the pre-edit form (the `previous` of a later blur commit). */
18
+ focus(key: string): void;
19
+ /** Focus left `key`: commit a text edit made since focus (never per keystroke). */
20
+ blur(key: string): void;
21
+ retry(key: string): void;
22
+ }
23
+ export declare function useDynamicFields(params: ActionParam[], values: Record<string, unknown>, dynamic: DynamicContext | undefined, onChange: (next: Record<string, unknown>) => void): UseDynamicFields;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@w6w/ui",
3
- "version": "0.2.0",
3
+ "version": "0.2.11",
4
4
  "description": "React components for w6w — central API management. Build plugins, apps and integrations on them.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://w6w.io",
@@ -73,7 +73,8 @@
73
73
  "@uiw/react-codemirror": "^4.23.7",
74
74
  "@w6w/expr": "^0.1.0",
75
75
  "@xyflow/react": "^12.3.6",
76
- "prism-react-renderer": "^2.4.1"
76
+ "prism-react-renderer": "^2.4.1",
77
+ "react-markdown": "^10.1.0"
77
78
  },
78
79
  "devDependencies": {
79
80
  "@biomejs/biome": "^1.9.4",
@@ -100,6 +101,7 @@
100
101
  "typecheck": "tsc -b --noEmit",
101
102
  "test": "node --max-old-space-size=2048 --conditions=w6w-source --import ./src/test-jsx-loader.mjs --test --test-concurrency=4 --test-timeout=120000 src/__tests__/*.test.ts src/components/__tests__/*.test.ts",
102
103
  "test:picker-layout": "bash test/picker-layout/run.sh",
104
+ "test:canvas-layer": "bash test/canvas-layer/run.sh",
103
105
  "test:expr-template": "bash test/expr-template/run.sh",
104
106
  "test:action-test-form": "bash test/action-test-form/run.sh",
105
107
  "test:host-isolation": "bash test/host-isolation/run.sh",
@@ -0,0 +1,46 @@
1
+ /* BudgetMeter — usage-vs-limit bar with a reserved segment.
2
+ *
3
+ * Token-only: every colour is a --w6w-* custom property, so a host restyles
4
+ * the meter (including the over-limit state) by overriding tokens, never by
5
+ * targeting a hex value here. */
6
+ .w6w-budget-meter {
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: var(--w6w-sp-1);
10
+ }
11
+ .w6w-budget-meter-head {
12
+ display: flex;
13
+ align-items: baseline;
14
+ justify-content: space-between;
15
+ gap: var(--w6w-sp-2);
16
+ color: var(--w6w-muted);
17
+ }
18
+ .w6w-budget-meter-label {
19
+ color: var(--w6w-text);
20
+ }
21
+ .w6w-budget-meter-value {
22
+ font-variant-numeric: tabular-nums;
23
+ }
24
+ .w6w-budget-meter-track {
25
+ display: flex;
26
+ width: 100%;
27
+ height: 8px;
28
+ overflow: hidden;
29
+ background: var(--w6w-panel-2);
30
+ border: 1px solid var(--w6w-border);
31
+ border-radius: var(--w6w-radius);
32
+ }
33
+ .w6w-budget-meter-used {
34
+ height: 100%;
35
+ background: var(--w6w-accent);
36
+ }
37
+ .w6w-budget-meter-reserved {
38
+ height: 100%;
39
+ background: var(--w6w-health-degraded);
40
+ }
41
+ .w6w-budget-meter-over .w6w-budget-meter-used {
42
+ background: var(--w6w-danger);
43
+ }
44
+ .w6w-budget-meter-over .w6w-budget-meter-value {
45
+ color: var(--w6w-danger);
46
+ }
@@ -36,15 +36,21 @@
36
36
  .w6w-callable-list-body {
37
37
  border: 1px solid var(--w6w-border);
38
38
  border-radius: 8px;
39
- padding: var(--w6w-sp-2);
39
+ background: var(--w6w-panel);
40
+ overflow: hidden;
40
41
  }
41
- .w6w-callable-list-rows {
42
- list-style: none;
43
- margin: 0;
44
- padding: 0;
42
+ .w6w-callable-list-main {
45
43
  display: flex;
46
44
  flex-direction: column;
47
- gap: var(--w6w-sp-1);
45
+ gap: var(--w6w-sp-0-5);
46
+ min-width: 0;
47
+ text-align: left;
48
+ }
49
+ .w6w-callable-list-open {
50
+ all: unset;
51
+ cursor: pointer;
52
+ display: block;
53
+ width: 100%;
48
54
  }
49
55
  .w6w-callable-list-trail {
50
56
  display: inline-flex;
@@ -58,3 +64,6 @@
58
64
  color: var(--w6w-muted);
59
65
  font-size: var(--w6w-fs-xs);
60
66
  }
67
+ .w6w-callable-list-row-desc {
68
+ display: block;
69
+ }