@w6w/ui 0.1.0 → 0.2.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 (47) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +25 -8
  3. package/dist/AppPicker.d.ts +1 -0
  4. package/dist/ConfirmModal-ChoiGy2K.cjs +11 -0
  5. package/dist/ConfirmModal-ChoiGy2K.cjs.map +1 -0
  6. package/dist/{ConfirmModal-sY1tzkBO.js → ConfirmModal-Wx3i8F1n.js} +4470 -4446
  7. package/dist/ConfirmModal-Wx3i8F1n.js.map +1 -0
  8. package/dist/ConnectionHeader.d.ts +19 -0
  9. package/dist/EditConnectionModal.d.ts +21 -0
  10. package/dist/app-pages.d.ts +3 -0
  11. package/dist/code.cjs +1 -1
  12. package/dist/code.mjs +1 -0
  13. package/dist/code.mjs.map +1 -1
  14. package/dist/components/AuthFieldsForm.d.ts +8 -1
  15. package/dist/components/CallableList.d.ts +57 -0
  16. package/dist/components/ConnectionList.d.ts +44 -0
  17. package/dist/components/ListItem.d.ts +16 -6
  18. package/dist/components/ReliabilityChart.d.ts +10 -0
  19. package/dist/components/ReliabilityList.d.ts +47 -0
  20. package/dist/connection-app-meta.d.ts +32 -0
  21. package/dist/flow.cjs +1 -1
  22. package/dist/flow.cjs.map +1 -1
  23. package/dist/flow.d.ts +3 -1
  24. package/dist/flow.mjs +50 -49
  25. package/dist/flow.mjs.map +1 -1
  26. package/dist/index.cjs +1 -1
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.ts +16 -0
  29. package/dist/index.mjs +1701 -891
  30. package/dist/index.mjs.map +1 -1
  31. package/dist/provider.d.ts +21 -1
  32. package/dist/reliability-board-layout.d.ts +121 -0
  33. package/dist/types.d.ts +8 -0
  34. package/package.json +3 -2
  35. package/src/code.css +23 -4
  36. package/src/styles/_callable-list.scss +60 -0
  37. package/src/styles/_connections.scss +100 -0
  38. package/src/styles/_flow.scss +4 -4
  39. package/src/styles/_lists.scss +4 -0
  40. package/src/styles/_reliability.scss +339 -0
  41. package/src/styles/_tokens.scss +23 -4
  42. package/src/styles.css +4386 -3108
  43. package/src/styles.scss +3 -0
  44. package/dist/ConfirmModal-DJPYXE9t.cjs +0 -11
  45. package/dist/ConfirmModal-DJPYXE9t.cjs.map +0 -1
  46. package/dist/ConfirmModal-sY1tzkBO.js.map +0 -1
  47. package/dist/ui.css +0 -1
@@ -119,12 +119,21 @@ export interface ListAppsPageOptions {
119
119
  compact?: boolean;
120
120
  /** Abort this request. Local request control only; never a wire field. */
121
121
  signal?: AbortSignal;
122
+ /** Snapshot id from a prior {@link AppsPageResult.snapshot}. Navigation normally uses `cursor` alone. */
123
+ snapshot?: string;
124
+ /** Absolute offset into the snapshot. */
125
+ offset?: number;
122
126
  }
123
127
  /** One page of apps, as returned by {@link W6WApi.listAppsPage}. */
124
128
  export interface AppsPageResult {
125
129
  apps: AppSummary[];
126
130
  /** Present unless this is the last page. */
127
131
  nextCursor?: string;
132
+ snapshot?: string;
133
+ total?: number;
134
+ offset?: number;
135
+ limit?: number;
136
+ expiresAt?: string;
128
137
  }
129
138
  export interface W6WApi {
130
139
  /**
@@ -174,6 +183,16 @@ export interface W6WApi {
174
183
  displayName?: string;
175
184
  profile?: Record<string, unknown>;
176
185
  }): Promise<ConnectionSummary>;
186
+ /**
187
+ * Rename a connection and/or replace some of its credential fields (`PATCH /connections/:id`).
188
+ * Send only the fields being changed: the server merges a partial `credential` over the stored
189
+ * one. Optional: when absent, `EditConnectionModal` shows no Save button (a one-line notice
190
+ * instead) and hosts should not offer an Edit action. It never throws for the absence.
191
+ */
192
+ updateConnection?(id: string, body: {
193
+ displayName?: string;
194
+ credential?: Record<string, unknown>;
195
+ }): Promise<ConnectionSummary>;
177
196
  /**
178
197
  * Start an OAuth 2.0 flow. Server builds the provider's authorize URL and
179
198
  * returns it; the caller opens it in a popup and awaits the server's
@@ -375,7 +394,8 @@ export interface W6WUIProviderProps {
375
394
  api: W6WApi;
376
395
  /**
377
396
  * Force the theme every `@w6w/ui` component resolves to — both the CSS
378
- * tokens (`styles.css`'s `:where([data-theme=...])` blocks) and the
397
+ * tokens (`styles.css`'s `:where([data-theme=...])` blocks; `color-scheme` is
398
+ * declared on component roots, never on the host `<html>`) and the
379
399
  * JS-side theme-aware components (`AppIcon`, `CodeEditor`, `JsonEditor`,
380
400
  * `WorkflowFlowEditor`, via `useEffectiveTheme`) — instead of each one
381
401
  * independently falling back to `data-theme` on `<html>` or the visitor's
@@ -0,0 +1,121 @@
1
+ /**
2
+ * How the reliability board arranges itself — the decisions that are arithmetic
3
+ * rather than markup, kept pure (no React, no DOM) so they test without a DOM
4
+ * and so `@w6w/embed` can import them beside the components.
5
+ *
6
+ * **None of these decide a STATE.** Every state read below arrived already
7
+ * decided by the server; nothing here compares a ratio, applies a threshold to
8
+ * call data, or invents a fifth word. What these decide is where a row is PUT,
9
+ * which is presentation.
10
+ */
11
+ /** The four words the server uses for a health verdict. */
12
+ export type ReliabilityViewState = "ok" | "degraded" | "down" | "unknown";
13
+ /**
14
+ * The row shape the reliability components render — owned by `@w6w/ui` so it
15
+ * imports nothing from the SDK. The SDK's `ReliabilityService` assigns to it
16
+ * structurally; the generic functions below keep the caller's richer row type.
17
+ */
18
+ export interface ReliabilityServiceView {
19
+ appId: string;
20
+ displayName: string;
21
+ /** Inline icon fields — the board never asks a catalog for them. */
22
+ iconSrc?: string;
23
+ iconSrcDark?: string;
24
+ brandColor?: string;
25
+ /** "Did YOUR calls succeed". */
26
+ state: ReliabilityViewState;
27
+ calls: number;
28
+ errors: {
29
+ e4xx: number;
30
+ e429: number;
31
+ eAuth: number;
32
+ e5xx: number;
33
+ };
34
+ p95Ms: number | null;
35
+ ongoingOutageId: string | null;
36
+ /** One entry per calendar day, oldest first, exactly as the server sent it. */
37
+ uptime: readonly {
38
+ day: string;
39
+ state: ReliabilityViewState;
40
+ }[];
41
+ /** "What does the vendor say about itself"; `null` when no public check exists. */
42
+ vendorStatus: {
43
+ state: ReliabilityViewState;
44
+ message: string | null;
45
+ } | null;
46
+ /** The server's tier for the row — read, never re-derived. */
47
+ attention: "now" | "recent" | "none";
48
+ }
49
+ /** One point of the performance scatter — a service with both a call volume and a measured p95. */
50
+ export interface PerformancePoint {
51
+ appId: string;
52
+ displayName: string;
53
+ calls: number;
54
+ p95Ms: number;
55
+ state: ReliabilityViewState;
56
+ /** Total errors across all four kinds — drives the point's size, not its position. */
57
+ errorCount: number;
58
+ }
59
+ /**
60
+ * Every service with at least one timed call, as a (calls, p95) point — what
61
+ * the performance chart plots. Excludes `p95Ms: null`: there is no Y position
62
+ * for a service with nothing timed.
63
+ */
64
+ export declare function performancePoints(services: readonly ReliabilityServiceView[]): PerformancePoint[];
65
+ /**
66
+ * Does this service want the reader's attention? READS the tier the server
67
+ * sent (`attention`), never re-derives it from `state` or `vendorStatus`: the
68
+ * server's `boardRank` is the one implementation, and a second copy here would
69
+ * drift. `now` and `recent` both count; they differ in tense only.
70
+ */
71
+ export declare function needsAttention(service: Pick<ReliabilityServiceView, "attention">): boolean;
72
+ /** The board in its three groups, each keeping the server's own ordering. */
73
+ export interface BoardPartition<T extends ReliabilityServiceView = ReliabilityServiceView> {
74
+ /** Broken right now, on either signal. */
75
+ now: T[];
76
+ /** Clear now, but this window holds a failed stretch or an open advisory. */
77
+ recent: T[];
78
+ /** Everything else, in the server's recency order. */
79
+ clear: T[];
80
+ }
81
+ /**
82
+ * Group the board without reordering within any group. Bucketing is not
83
+ * sorting: each group comes out in the order it arrived. An unrecognised
84
+ * `attention` value falls to `clear` rather than throwing — a client can be
85
+ * older than the server that answered it.
86
+ */
87
+ export declare function partitionBoard<T extends ReliabilityServiceView>(services: readonly T[]): BoardPartition<T>;
88
+ /**
89
+ * The uptime strip's cells for one service — the server's own array, in the
90
+ * order it arrived (oldest first). A passthrough on purpose: no sort, no
91
+ * reverse, no gap filling. The strip has no date axis, so a reversed array is
92
+ * visually plausible and would silently move an outage to the wrong end.
93
+ */
94
+ export declare function uptimeDays(service: Pick<ReliabilityServiceView, "uptime">): {
95
+ day: string;
96
+ state: ReliabilityViewState;
97
+ }[];
98
+ /** Compact mode shows at most this many needs-attention cards, `now` filling first. */
99
+ export declare const ATTENTION_DISPLAY_CAP = 3;
100
+ /** Compact mode shows this many table rows unless the caller passes `limit`. */
101
+ export declare const RECENT_DISPLAY_CAP = 5;
102
+ /** What the board shows and what a display cap hid. */
103
+ export interface BoardDisplay<T extends ReliabilityServiceView = ReliabilityServiceView> {
104
+ /** TRUE total of attention rows (the whole input, not the capped slice). */
105
+ attentionCount: number;
106
+ shownNow: T[];
107
+ shownRecent: T[];
108
+ hiddenAttentionCount: number;
109
+ /** Table rows in the server's order, every attention tier included. */
110
+ shownTable: T[];
111
+ hiddenTableCount: number;
112
+ }
113
+ /**
114
+ * Apply the display caps to the complete board. Compact: ≤ 3 attention cards
115
+ * (`now` first, `recent` tops up) and `limit ?? 5` table rows; otherwise
116
+ * uncapped. Hidden counts are computed from the full input, so they are real.
117
+ */
118
+ export declare function displayBoard<T extends ReliabilityServiceView>(services: readonly T[], opts?: {
119
+ compact?: boolean;
120
+ limit?: number;
121
+ }): BoardDisplay<T>;
package/dist/types.d.ts CHANGED
@@ -328,6 +328,14 @@ export interface ConnectionSummary {
328
328
  profile?: Record<string, unknown>;
329
329
  createdAt?: string;
330
330
  updatedAt?: string;
331
+ /**
332
+ * True for the server's synthetic "no credential needed" rows. They have no stored credential,
333
+ * so the server 404s get/test/delete on their ids; the connection UI offers nothing on them.
334
+ * Detect with this flag only, never an id prefix.
335
+ */
336
+ builtIn?: boolean;
337
+ /** The last test result: `false` = failed, `null`/absent = never tested. */
338
+ lastTestOk?: boolean | null;
331
339
  }
332
340
  /** A saved set of action-test input values, scoped to a connection + action. */
333
341
  export interface SavedTest {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@w6w/ui",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
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",
@@ -98,10 +98,11 @@
98
98
  "build:css": "node scripts/build-css.mjs",
99
99
  "check:css": "node scripts/build-css.mjs --check",
100
100
  "typecheck": "tsc -b --noEmit",
101
- "test": "node --conditions=w6w-source --import ./src/test-jsx-loader.mjs --test src/__tests__/*.test.ts src/components/__tests__/*.test.ts",
101
+ "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
102
  "test:picker-layout": "bash test/picker-layout/run.sh",
103
103
  "test:expr-template": "bash test/expr-template/run.sh",
104
104
  "test:action-test-form": "bash test/action-test-form/run.sh",
105
+ "test:host-isolation": "bash test/host-isolation/run.sh",
105
106
  "test:copyable": "bash test/copyable/run.sh",
106
107
  "test:pack-smoke": "bash test/pack-smoke/run.sh",
107
108
  "storybook": "storybook dev -p 6006 --no-open",
package/src/code.css CHANGED
@@ -34,7 +34,6 @@
34
34
  */
35
35
  /* Light — the base defaults. */
36
36
  :where(:root) {
37
- color-scheme: light;
38
37
  --w6w-bg: #f7f8fa;
39
38
  --w6w-panel: #ffffff;
40
39
  --w6w-panel-2: #f0f2f6;
@@ -57,13 +56,13 @@
57
56
  --w6w-code-punctuation: #5f6875;
58
57
  --w6w-code-variable: #953800;
59
58
  --w6w-radius: 10px;
59
+ --w6w-scheme: light;
60
60
  }
61
61
 
62
62
  /* Dark — followed automatically from the OS preference (unless a host has
63
63
  forced a mode via data-theme, which is handled by the blocks below). */
64
64
  @media (prefers-color-scheme: dark) {
65
65
  :where(:root) {
66
- color-scheme: dark;
67
66
  --w6w-bg: #0f1115;
68
67
  --w6w-panel: #181b22;
69
68
  --w6w-panel-2: #1f232c;
@@ -78,6 +77,7 @@
78
77
  --w6w-health-down: var(--w6w-danger);
79
78
  --w6w-health-unknown: #9aa3b2;
80
79
  --w6w-icon-swatch: #1f232c;
80
+ --w6w-scheme: dark;
81
81
  --w6w-code-comment: #7d8797;
82
82
  --w6w-code-keyword: #d2a8ff;
83
83
  --w6w-code-string: #7ee787;
@@ -90,7 +90,7 @@
90
90
  /* Explicit mode. Declared AFTER the media query at equal (zero) specificity, so
91
91
  a forced data-theme always beats the OS preference. */
92
92
  :where([data-theme=light]) {
93
- color-scheme: light;
93
+ --w6w-scheme: light;
94
94
  --w6w-bg: #f7f8fa;
95
95
  --w6w-panel: #ffffff;
96
96
  --w6w-panel-2: #f0f2f6;
@@ -115,7 +115,7 @@
115
115
  }
116
116
 
117
117
  :where([data-theme=dark]) {
118
- color-scheme: dark;
118
+ --w6w-scheme: dark;
119
119
  --w6w-bg: #0f1115;
120
120
  --w6w-panel: #181b22;
121
121
  --w6w-panel-2: #1f232c;
@@ -147,6 +147,25 @@
147
147
  font-size: 0.85em;
148
148
  }
149
149
 
150
+ /* color-scheme — declared on ui COMPONENT ROOTS, never on the host's <html>.
151
+ `color-scheme` inherits and also themes the page canvas, scrollbars and form
152
+ controls, so setting it on :root / [data-theme] let importing this file flip a
153
+ host's whole document. Instead each token block above also sets the
154
+ `--w6w-scheme` custom property (light / dark), which INHERITS, so the nearest
155
+ themed ancestor wins however themes nest (a light panel inside a dark page and
156
+ the reverse). Elements ui renders (any `w6w-` class, which covers the native
157
+ <dialog> modals) and the native controls inside them read it with
158
+ `color-scheme: var(--w6w-scheme)`. Not a hand-picked root list: every ui
159
+ component carries a `w6w-` class. Only an element whose class LIST has a token
160
+ starting `w6w-` matches (`class^=` / `class*=" w6w-"`), so host elements are never
161
+ touched: not a host `data-theme` wrapper, not a host class that merely contains
162
+ `w6w-` (`my-w6w-card`), and not a host element's own color-scheme on inputs nested
163
+ in a ui root (color-scheme inherits, so no descendant rule is needed). Nothing here
164
+ ever matches <html>/:root. All zero specificity. */
165
+ :where([class^=w6w-]:not(:root), [class*=" w6w-"]:not(:root)) {
166
+ color-scheme: var(--w6w-scheme);
167
+ }
168
+
150
169
  /*
151
170
  * @w6w/ui — the spacing / typography scale.
152
171
  *
@@ -0,0 +1,60 @@
1
+ .w6w-callable-list {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--w6w-sp-4);
5
+ }
6
+ .w6w-callable-list-head {
7
+ display: flex;
8
+ justify-content: space-between;
9
+ align-items: flex-start;
10
+ gap: var(--w6w-sp-4);
11
+ }
12
+ .w6w-callable-list-heading {
13
+ display: flex;
14
+ flex-direction: column;
15
+ gap: var(--w6w-sp-1);
16
+ min-width: 0;
17
+ }
18
+ .w6w-callable-list-title {
19
+ margin: 0;
20
+ }
21
+ .w6w-callable-list-description {
22
+ margin: 0;
23
+ }
24
+ .w6w-callable-list-toggle {
25
+ display: flex;
26
+ align-items: center;
27
+ gap: var(--w6w-sp-2);
28
+ cursor: pointer;
29
+ }
30
+ .w6w-callable-list-actions {
31
+ display: flex;
32
+ gap: var(--w6w-sp-2);
33
+ flex-shrink: 0;
34
+ white-space: nowrap;
35
+ }
36
+ .w6w-callable-list-body {
37
+ border: 1px solid var(--w6w-border);
38
+ border-radius: 8px;
39
+ padding: var(--w6w-sp-2);
40
+ }
41
+ .w6w-callable-list-rows {
42
+ list-style: none;
43
+ margin: 0;
44
+ padding: 0;
45
+ display: flex;
46
+ flex-direction: column;
47
+ gap: var(--w6w-sp-1);
48
+ }
49
+ .w6w-callable-list-trail {
50
+ display: inline-flex;
51
+ align-items: center;
52
+ gap: var(--w6w-sp-2);
53
+ }
54
+ .w6w-callable-list-badge {
55
+ padding: var(--w6w-sp-0-5) var(--w6w-sp-2);
56
+ border-radius: 999px;
57
+ border: 1px solid var(--w6w-border);
58
+ color: var(--w6w-muted);
59
+ font-size: var(--w6w-fs-xs);
60
+ }
@@ -0,0 +1,100 @@
1
+ /* Connections (D-12u): ConnectionList, ConnectionHeader and the state tags they share. */
2
+ .w6w-connection-list {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: var(--w6w-sp-3);
6
+ }
7
+ .w6w-connection-list-head {
8
+ display: flex;
9
+ justify-content: space-between;
10
+ align-items: flex-start;
11
+ gap: var(--w6w-sp-4);
12
+ }
13
+ .w6w-connection-list-heading {
14
+ min-width: 0;
15
+ }
16
+ .w6w-connection-list-actions {
17
+ display: flex;
18
+ gap: var(--w6w-sp-2);
19
+ flex-shrink: 0;
20
+ }
21
+ .w6w-connection-rows {
22
+ list-style: none;
23
+ margin: 0;
24
+ padding: 0;
25
+ display: flex;
26
+ flex-direction: column;
27
+ gap: var(--w6w-sp-1);
28
+ }
29
+ .w6w-connection-row {
30
+ display: flex;
31
+ align-items: center;
32
+ gap: var(--w6w-sp-2);
33
+ }
34
+ .w6w-connection-row-main {
35
+ flex: 1;
36
+ min-width: 0;
37
+ }
38
+ .w6w-connection-row-actions {
39
+ display: flex;
40
+ gap: var(--w6w-sp-1-5);
41
+ flex-shrink: 0;
42
+ }
43
+ .w6w-connection-name {
44
+ margin-right: var(--w6w-sp-2);
45
+ }
46
+
47
+ /* State tags (connected / needs attention / invalid). */
48
+ .w6w-connection-tag {
49
+ display: inline-block;
50
+ background: var(--w6w-panel-2);
51
+ border: 1px solid var(--w6w-border);
52
+ border-radius: 999px;
53
+ padding: var(--w6w-sp-0-5) var(--w6w-sp-2);
54
+ font-size: var(--w6w-fs-xs);
55
+ margin-right: var(--w6w-sp-1);
56
+ }
57
+ .w6w-connection-tag-warn {
58
+ color: #ffb86b;
59
+ border-color: #ffb86b;
60
+ }
61
+ .w6w-connection-tag-danger {
62
+ color: var(--w6w-danger);
63
+ border-color: var(--w6w-danger);
64
+ }
65
+
66
+ /* ConnectionHeader */
67
+ .w6w-connection-header {
68
+ display: flex;
69
+ align-items: center;
70
+ gap: var(--w6w-sp-3);
71
+ }
72
+ .w6w-connection-header-main {
73
+ flex: 1;
74
+ min-width: 0;
75
+ }
76
+ .w6w-connection-header-title {
77
+ margin: 0;
78
+ }
79
+ .w6w-connection-header-meta {
80
+ margin: 0;
81
+ }
82
+ .w6w-connection-header-actions {
83
+ display: flex;
84
+ gap: var(--w6w-sp-1-5);
85
+ align-items: center;
86
+ }
87
+ /* The id's copy button appears on hover/focus only. */
88
+ .w6w-connection-id-copy.w6w-copyable {
89
+ display: inline-flex;
90
+ width: auto;
91
+ min-width: 0;
92
+ vertical-align: middle;
93
+ }
94
+ .w6w-connection-id-copy .w6w-copyable-btn {
95
+ opacity: 0;
96
+ }
97
+ .w6w-connection-id-copy:hover .w6w-copyable-btn,
98
+ .w6w-connection-id-copy:focus-within .w6w-copyable-btn {
99
+ opacity: 1;
100
+ }
@@ -9,10 +9,10 @@
9
9
  added to that stack later inherits it with no styling of its own.
10
10
 
11
11
  Custom properties, deliberately, rather than a `.react-flow__controls-button`
12
- override: `@xyflow/react/dist/style.css` is imported as a side effect from
13
- WorkflowFlowEditor.tsx, so it rides in the LAZY chunk and is injected *after*
14
- this stylesheet. A bare class override (0,1,0) would tie with the library's
15
- own rule and lose on source order. Custom properties inherit and are
12
+ override: React Flow's own stylesheet is compiled into the same
13
+ styles.css (`@use` in the build-only styles.build.scss), but cascade
14
+ order is not something to lean on: a bare class override (0,1,0) would tie
15
+ with the library's own rule and lose if the order ever changed. Custom properties inherit and are
16
16
  order-independent, and scoping them on `.w6w-flow` keeps them off the rest of
17
17
  the page.
18
18
 
@@ -16,6 +16,10 @@ button.w6w-list-item {
16
16
  font: inherit;
17
17
  font-size: var(--w6w-fs-sm);
18
18
  }
19
+ a.w6w-list-item {
20
+ text-decoration: none;
21
+ color: inherit;
22
+ }
19
23
  .w6w-list-item:hover {
20
24
  background: var(--w6w-panel-2);
21
25
  }