@flowdrop/flowdrop 2.0.0-beta.3 → 2.0.0-beta.4

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 (49) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +5 -5
  3. package/dist/components/App.svelte +15 -146
  4. package/dist/components/Button.stories.svelte +65 -0
  5. package/dist/components/Button.stories.svelte.d.ts +19 -0
  6. package/dist/components/Button.svelte +62 -0
  7. package/dist/components/Button.svelte.d.ts +24 -0
  8. package/dist/components/ConfigForm.svelte +4 -4
  9. package/dist/components/EditorStatusBar.stories.svelte +44 -0
  10. package/dist/components/EditorStatusBar.stories.svelte.d.ts +27 -0
  11. package/dist/components/EditorStatusBar.svelte +99 -0
  12. package/dist/components/EditorStatusBar.svelte.d.ts +15 -0
  13. package/dist/components/IconButton.svelte +80 -0
  14. package/dist/components/IconButton.svelte.d.ts +30 -0
  15. package/dist/components/Input.svelte +74 -0
  16. package/dist/components/Input.svelte.d.ts +17 -0
  17. package/dist/components/Navbar.svelte +9 -4
  18. package/dist/components/Navbar.svelte.d.ts +3 -0
  19. package/dist/components/NodeSidebar.svelte +13 -111
  20. package/dist/components/NodeSwapPicker.svelte +10 -26
  21. package/dist/components/Select.svelte +53 -0
  22. package/dist/components/Select.svelte.d.ts +15 -0
  23. package/dist/components/Textarea.svelte +39 -0
  24. package/dist/components/Textarea.svelte.d.ts +12 -0
  25. package/dist/components/ThemeToggle.svelte +15 -89
  26. package/dist/components/form/FormArray.svelte +37 -157
  27. package/dist/components/form/FormCheckboxGroup.svelte +1 -1
  28. package/dist/components/form/FormField.svelte +5 -44
  29. package/dist/components/form/FormFieldLight.svelte +5 -44
  30. package/dist/components/form/FormFieldset.svelte +1 -1
  31. package/dist/components/form/FormNumberField.svelte +4 -32
  32. package/dist/components/form/FormRangeField.svelte +17 -7
  33. package/dist/components/form/FormSelect.svelte +13 -79
  34. package/dist/components/form/FormTextField.svelte +3 -39
  35. package/dist/components/form/FormTextarea.svelte +4 -43
  36. package/dist/components/form/resolveFieldType.d.ts +24 -0
  37. package/dist/components/form/resolveFieldType.js +55 -0
  38. package/dist/components/icons/CloseIcon.svelte +6 -0
  39. package/dist/components/icons/CloseIcon.svelte.d.ts +26 -0
  40. package/dist/components/playground/InputCollector.svelte +11 -46
  41. package/dist/messages/index.d.ts +1 -1
  42. package/dist/messages/index.js +1 -1
  43. package/dist/openapi/v1/openapi.yaml +2 -2
  44. package/dist/skins/drafter.js +41 -28
  45. package/dist/styles/base.css +247 -5
  46. package/dist/styles/tokens.css +6 -0
  47. package/dist/svelte-app.js +68 -107
  48. package/dist/utils/connections.js +14 -50
  49. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.0.0-beta.4] - 2026-06-13
11
+
12
+ Fourth 2.0 beta, published under the npm `beta` dist-tag (`npm install @flowdrop/flowdrop@beta`). `latest` remains 1.15.0 until 2.0.0 GA. This release is mostly an internal design-system consolidation pass — new shared `Button` / `IconButton` / `Input` / `Select` / `Textarea` primitives route every control through the `base.css` class system as the single source of truth, so themes (including Drafter) style fields and buttons consistently. User-visible changes are a navbar `end` snippet for embedding custom trailing controls, a new `--fd-control-radius` theming token, a range-slider fill fix, and opaque Drafter buttons. The archived Astro docs site is retired in favor of the Mintlify docs at `flowdrop.mintlify.app`.
13
+
14
+ ### Added
15
+
16
+ - **`end` snippet on the navbar.** A new `end` snippet prop renders custom trailing content in the navbar (before the settings gear), letting consuming apps inject their own controls — e.g. a theme toggle — without forking the navbar.
17
+ - **`--fd-control-radius` theming token.** Form controls (inputs, selects, textareas, the array action buttons, fieldset/checkbox-group/config-form borders) now read their corner radius from `--fd-control-radius`, so themes can tighten field corners independently of cards and panels. Drafter sets it to `2px` for its crisp blueprint look; other themes inherit the existing radius.
18
+
19
+ ### Fixed
20
+
21
+ - **Range slider fill now tracks the thumb center.** The colored fill measured 0–100% of the raw track width while the browser keeps the thumb inside the track, so the fill and thumb drifted apart — worst at the extremes and visible on load. The fill now mirrors the thumb's actual travel (`thumb/2` to `100% − thumb/2`) via a new `--fd-range-fill` calc, with the thumb size tokenized.
22
+ - **Drafter buttons are opaque and flat.** Several buttons in the Drafter skin (file-upload node config, canvas toggles) rendered with translucent backgrounds and read as see-through; they are now flat and opaque.
23
+
24
+ ### Changed (internal — no API change)
25
+
26
+ - **Shared form-control and button primitives.** New typed `Button`, `IconButton`, `Input`, `Select`, and `Textarea` wrappers sit over the existing `.flowdrop-btn*` / `.flowdrop-input*` / `.flowdrop-btn--icon*` classes in `base.css`, making it the single source for control look (`:disabled` is the only muted state). `ThemeToggle`, the config form, sidebar search, the node swap picker, `FormArray` move/delete buttons, `InputCollector`, and the playground inputs were migrated onto them, dropping their hand-rolled, duplicated CSS. The primitives are internal (not exported), so their APIs aren't frozen before GA; public component prop APIs (e.g. `ThemeToggle`) are unchanged.
27
+ - **`EditorStatusBar` extracted onto the shared button system.** The endpoint-error banner — previously inline in `App.svelte` with its own button CSS and a centered `80rem` layout — is now an `EditorStatusBar` component that routes actions through the `Button` primitive, spans the editor chrome flush, and uses a proper `CloseIcon` for dismissal. Added Storybook stories for `Button` and `EditorStatusBar`.
28
+ - **Navbar control heights from one token.** Three hardcoded `height: 2.5rem` rules now use `var(--fd-size-btn-min)`, sharing a single source of truth with `.flowdrop-btn`.
29
+ - **Extracted shared logic flagged by the duplication review.** `connections.ts` gains a shared `detectCycles()` (used by both `hasCycles` / `hasInvalidCycles`); `svelte-app.ts` extracts a `configureInstance()` shared by `mountFlowDropApp` / `mountWorkflowEditor`; and `FormField` / `FormFieldLight` share a `resolveBaseFieldType()` for the basic-type switch (with a unit test pinning resolution order) while keeping the deliberate full/light rendering split for the bundle-guard contract. No behavioral change.
30
+
31
+ ### Docs
32
+
33
+ - **Retired the archived Astro/Starlight docs site (`apps/docs`)** and its docker-publish workflow; the Mintlify site at `flowdrop.mintlify.app` is canonical. All live documentation links (README, AGENTS, quick-start, i18n guide, API README, dev playground navbar, docker-playground landing, interrupt-feature doc) were repointed there. `apps/api-docs` is kept only to back the `api:lint` / `api:bundle` tooling and CI. Corrected the local dev-server port reference to the recommended Express demo.
34
+
10
35
  ## [2.0.0-beta.3] - 2026-06-12
11
36
 
12
37
  Third 2.0 beta, published under the npm `beta` dist-tag (`npm install @flowdrop/flowdrop@beta`). `latest` remains 1.15.0 until 2.0.0 GA. This release finishes the navbar/theme defaults pass started in beta.2 (navbar opt-in everywhere, light as the default theme, the FlowDrop wordmark in the header), adds the **Drafter** blueprint theme with per-theme canvas grids, and lands a keyboard-navigation and focus-ring overhaul alongside the 20px node-grid alignment.
package/README.md CHANGED
@@ -23,10 +23,10 @@
23
23
  </p>
24
24
 
25
25
  <p align="center">
26
- <a href="https://docs.flowdrop.io/getting-started/installation">Quickstart</a> •
26
+ <a href="https://flowdrop.mintlify.app/docs/quickstart">Quickstart</a> •
27
27
  <a href="#features">Features</a> •
28
28
  <a href="#integration">Integration</a> •
29
- <a href="https://docs.flowdrop.io">Docs</a>
29
+ <a href="https://flowdrop.mintlify.app">Docs</a>
30
30
  </p>
31
31
 
32
32
  <p align="center">
@@ -139,7 +139,7 @@ Every user-facing string flows through a typed `Messages` tree. Pass a callback
139
139
  <App messages={() => ({ form: { schema: { save: 'Apply' } } })} />
140
140
  ```
141
141
 
142
- Wire the callback to your i18n library (paraglide-js, sveltekit-i18n, etc.) — locale changes propagate automatically. See the [i18n & Custom Messages guide](https://docs.flowdrop.io/guides/i18n) for the full shape and a paraglide-js worked example.
142
+ Wire the callback to your i18n library (paraglide-js, sveltekit-i18n, etc.) — locale changes propagate automatically. See the [i18n & Custom Messages guide](https://flowdrop.mintlify.app/guides/i18n) for the full shape and a paraglide-js worked example.
143
143
 
144
144
  ## Sub-Module Exports
145
145
 
@@ -283,8 +283,8 @@ Runtime configuration means you build once and deploy to staging, production, or
283
283
 
284
284
  | Resource | Description |
285
285
  | -------------------------------------------------------------------------------------------- | ------------------------ |
286
- | [QUICK_START.md](https://docs.flowdrop.io/getting-started/installation/) | Get running in 5 minutes |
287
- | [API Documentation](https://api.flowdrop.io/v1/) | REST API specification |
286
+ | [QUICK_START.md](https://flowdrop.mintlify.app/docs/quickstart) | Get running in 5 minutes |
287
+ | [API Documentation](https://flowdrop.mintlify.app/api-reference/introduction) | REST API specification |
288
288
  | [CHANGELOG.md](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md) | Version history |
289
289
 
290
290
  ## Development
@@ -10,6 +10,7 @@
10
10
  import WorkflowEditor from './WorkflowEditor.svelte';
11
11
  import NodeSidebar from './NodeSidebar.svelte';
12
12
  import CanvasIconButton from './CanvasIconButton.svelte';
13
+ import EditorStatusBar from './EditorStatusBar.svelte';
13
14
  import MenuIcon from './icons/MenuIcon.svelte';
14
15
  import MenuOpenIcon from './icons/MenuOpenIcon.svelte';
15
16
  import ConfigForm from './ConfigForm.svelte';
@@ -1212,53 +1213,21 @@
1212
1213
  {/snippet}
1213
1214
 
1214
1215
  <!-- Main Content: Workflow Editor with Error Status -->
1215
- <!-- Status Display: aria-live announces API errors dynamically without requiring focus -->
1216
1216
  {#if error}
1217
- <div class="flowdrop-status flowdrop-status--error" aria-live="polite" aria-atomic="true">
1218
- <div class="flowdrop-status__content">
1219
- <div class="flowdrop-flex flowdrop-gap--3">
1220
- <div class="flowdrop-status__indicator flowdrop-status__indicator--error"></div>
1221
- <span class="flowdrop-text--sm flowdrop-font--medium">Error: {error}</span>
1222
- </div>
1223
- <div class="flowdrop-flex flowdrop-gap--2">
1224
- <button
1225
- class="flowdrop-btn flowdrop-btn--sm flowdrop-btn--primary"
1226
- onclick={retryLoad}
1227
- type="button"
1228
- >
1229
- Retry
1230
- </button>
1231
- <button
1232
- class="flowdrop-btn flowdrop-btn--sm flowdrop-btn--outline"
1233
- onclick={() => {
1234
- const defaultUrl = '/api/flowdrop';
1235
- const newUrl = prompt('Enter Backend API URL:', defaultUrl);
1236
- if (newUrl) {
1237
- configureApi(createEndpointConfig(newUrl));
1238
- fetchNodeTypes();
1239
- }
1240
- }}
1241
- type="button"
1242
- >
1243
- Set API URL
1244
- </button>
1245
- <button
1246
- class="flowdrop-btn flowdrop-btn--sm flowdrop-btn--outline"
1247
- onclick={testApiConnection}
1248
- type="button"
1249
- >
1250
- Test API
1251
- </button>
1252
- <button
1253
- class="flowdrop-btn flowdrop-btn--ghost flowdrop-btn--sm"
1254
- onclick={() => (error = null)}
1255
- type="button"
1256
- >
1257
-
1258
- </button>
1259
- </div>
1260
- </div>
1261
- </div>
1217
+ <EditorStatusBar
1218
+ {error}
1219
+ onRetry={retryLoad}
1220
+ onSetApiUrl={() => {
1221
+ const defaultUrl = '/api/flowdrop';
1222
+ const newUrl = prompt('Enter Backend API URL:', defaultUrl);
1223
+ if (newUrl) {
1224
+ configureApi(createEndpointConfig(newUrl));
1225
+ fetchNodeTypes();
1226
+ }
1227
+ }}
1228
+ onTestApi={testApiConnection}
1229
+ onDismiss={() => (error = null)}
1230
+ />
1262
1231
  {/if}
1263
1232
 
1264
1233
  <!-- Main Editor Area -->
@@ -1314,106 +1283,6 @@
1314
1283
  .flowdrop-root {
1315
1284
  display: contents;
1316
1285
  }
1317
- /* Status bar styles */
1318
- .flowdrop-status {
1319
- background-color: var(--fd-info-muted);
1320
- border-bottom: 1px solid var(--fd-info);
1321
- padding: 1rem;
1322
- }
1323
-
1324
- .flowdrop-status--error {
1325
- background-color: var(--fd-error-muted);
1326
- border-bottom: 1px solid var(--fd-error);
1327
- }
1328
-
1329
- .flowdrop-status__content {
1330
- max-width: 80rem;
1331
- margin: 0 auto;
1332
- display: flex;
1333
- align-items: center;
1334
- justify-content: space-between;
1335
- }
1336
-
1337
- .flowdrop-status__indicator {
1338
- width: 0.5rem;
1339
- height: 0.5rem;
1340
- border-radius: 50%;
1341
- }
1342
-
1343
- .flowdrop-status__indicator--error {
1344
- background-color: var(--fd-error);
1345
- }
1346
-
1347
- /* Button styles */
1348
- .flowdrop-btn {
1349
- padding: 0.375rem 0.75rem;
1350
- border-radius: var(--fd-radius-md);
1351
- font-size: 0.75rem;
1352
- font-weight: 500;
1353
- cursor: pointer;
1354
- border: 1px solid transparent;
1355
- transition: all var(--fd-transition-fast);
1356
- }
1357
-
1358
- .flowdrop-btn--sm {
1359
- padding: 0.25rem 0.5rem;
1360
- font-size: 0.625rem;
1361
- }
1362
-
1363
- .flowdrop-btn--outline {
1364
- background-color: transparent;
1365
- border-color: var(--fd-border);
1366
- color: var(--fd-foreground);
1367
- }
1368
-
1369
- .flowdrop-btn--outline:hover {
1370
- background-color: var(--fd-muted);
1371
- border-color: var(--fd-border-strong);
1372
- }
1373
-
1374
- .flowdrop-btn--primary {
1375
- background-color: var(--fd-primary);
1376
- border-color: var(--fd-primary);
1377
- color: var(--fd-primary-foreground);
1378
- }
1379
-
1380
- .flowdrop-btn--primary:hover {
1381
- background-color: var(--fd-primary-hover);
1382
- border-color: var(--fd-primary-hover);
1383
- }
1384
-
1385
- .flowdrop-btn--ghost {
1386
- background-color: transparent;
1387
- border-color: transparent;
1388
- color: var(--fd-muted-foreground);
1389
- }
1390
-
1391
- .flowdrop-btn--ghost:hover {
1392
- background-color: var(--fd-muted);
1393
- color: var(--fd-foreground);
1394
- }
1395
-
1396
- /* Utility classes */
1397
- .flowdrop-flex {
1398
- display: flex;
1399
- }
1400
-
1401
- .flowdrop-gap--2 {
1402
- gap: 0.5rem;
1403
- }
1404
-
1405
- .flowdrop-gap--3 {
1406
- gap: 0.75rem;
1407
- }
1408
-
1409
- .flowdrop-text--sm {
1410
- font-size: 0.875rem;
1411
- line-height: 1.25rem;
1412
- }
1413
-
1414
- .flowdrop-font--medium {
1415
- font-weight: 500;
1416
- }
1417
1286
 
1418
1287
  /* Floating sidebar toggle button — placement only; visuals live in CanvasIconButton */
1419
1288
  :global(.flowdrop-sidebar-fab) {
@@ -0,0 +1,65 @@
1
+ <script module lang="ts">
2
+ import type { ComponentProps } from 'svelte';
3
+ import { defineMeta } from '@storybook/addon-svelte-csf';
4
+ import Button from './Button.svelte';
5
+ import { fn } from 'storybook/test';
6
+
7
+ const { Story } = defineMeta({
8
+ title: 'Display/Button',
9
+ component: Button,
10
+ tags: ['autodocs'],
11
+ parameters: {
12
+ layout: 'centered'
13
+ },
14
+ argTypes: {
15
+ variant: {
16
+ control: { type: 'select' },
17
+ options: ['primary', 'secondary', 'outline', 'ghost']
18
+ },
19
+ size: {
20
+ control: { type: 'select' },
21
+ options: ['sm', 'md', 'lg']
22
+ },
23
+ disabled: { control: 'boolean' }
24
+ },
25
+ args: {
26
+ variant: 'secondary',
27
+ size: 'md',
28
+ disabled: false,
29
+ onclick: fn()
30
+ }
31
+ });
32
+ </script>
33
+
34
+ <!-- Arg-driven template so the Controls panel can tweak variant/size/disabled live. -->
35
+ {#snippet template(args: Omit<ComponentProps<typeof Button>, 'children'>)}
36
+ <Button {...args}>Button</Button>
37
+ {/snippet}
38
+
39
+ <Story name="Primary" args={{ variant: 'primary' }} {template} />
40
+
41
+ <Story name="Secondary" args={{ variant: 'secondary' }} {template} />
42
+
43
+ <Story name="Outline" args={{ variant: 'outline' }} {template} />
44
+
45
+ <Story name="Ghost" args={{ variant: 'ghost' }} {template} />
46
+
47
+ <Story name="Disabled" args={{ variant: 'primary', disabled: true }} {template} />
48
+
49
+ <!-- Static showcases comparing every variant / size side by side. -->
50
+ <Story name="Variants" asChild>
51
+ <div style="display: flex; gap: 0.75rem; align-items: center;">
52
+ <Button variant="primary">Primary</Button>
53
+ <Button variant="secondary">Secondary</Button>
54
+ <Button variant="outline">Outline</Button>
55
+ <Button variant="ghost">Ghost</Button>
56
+ </div>
57
+ </Story>
58
+
59
+ <Story name="Sizes" asChild>
60
+ <div style="display: flex; gap: 0.75rem; align-items: center;">
61
+ <Button variant="primary" size="sm">Small</Button>
62
+ <Button variant="primary" size="md">Medium</Button>
63
+ <Button variant="primary" size="lg">Large</Button>
64
+ </div>
65
+ </Story>
@@ -0,0 +1,19 @@
1
+ import Button from './Button.svelte';
2
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
+ $$bindings?: Bindings;
5
+ } & Exports;
6
+ (internal: unknown, props: {
7
+ $$events?: Events;
8
+ $$slots?: Slots;
9
+ }): Exports & {
10
+ $set?: any;
11
+ $on?: any;
12
+ };
13
+ z_$$bindings?: Bindings;
14
+ }
15
+ declare const Button: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type Button = InstanceType<typeof Button>;
19
+ export default Button;
@@ -0,0 +1,62 @@
1
+ <!--
2
+ Button — typed wrapper over the shared `.flowdrop-btn` system (base.css).
3
+
4
+ All button styling (variants, sizes, the --fd-size-btn-min height token and the
5
+ centralized focus ring) lives in base.css. This component is the ergonomic,
6
+ type-safe entry point so callers pick `variant`/`size` instead of hand-writing
7
+ class strings — the single place new buttons should route through.
8
+
9
+ Internal for now (not exported from any public entry) so the API isn't frozen
10
+ before GA. Existing hand-rolled buttons migrate onto it incrementally.
11
+ -->
12
+
13
+ <script lang="ts">
14
+ import type { Snippet } from 'svelte';
15
+
16
+ interface Props {
17
+ /** Visual style — maps to `.flowdrop-btn--{variant}` in base.css */
18
+ variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
19
+ /** Size — `md` is the base `.flowdrop-btn`; `sm`/`lg` add a modifier */
20
+ size?: 'sm' | 'md' | 'lg';
21
+ /** Native button type */
22
+ type?: 'button' | 'submit' | 'reset';
23
+ /** Tooltip text */
24
+ title?: string;
25
+ /** Accessible label (use when the button is icon-only) */
26
+ ariaLabel?: string;
27
+ /** Disabled state */
28
+ disabled?: boolean;
29
+ /** Extra classes appended to the root button */
30
+ class?: string;
31
+ /** Click handler */
32
+ onclick?: (event: MouseEvent) => void;
33
+ /** Button contents (icon, label, or both) */
34
+ children: Snippet;
35
+ }
36
+
37
+ let {
38
+ variant = 'secondary',
39
+ size = 'md',
40
+ type = 'button',
41
+ title,
42
+ ariaLabel,
43
+ disabled = false,
44
+ class: className = '',
45
+ onclick,
46
+ children
47
+ }: Props = $props();
48
+
49
+ // 'md' is the unmodified base class; only 'sm'/'lg' need a size modifier.
50
+ const sizeClass = $derived(size === 'md' ? '' : `flowdrop-btn--${size}`);
51
+ </script>
52
+
53
+ <button
54
+ class="flowdrop-btn flowdrop-btn--{variant} {sizeClass} {className}"
55
+ {type}
56
+ {title}
57
+ {disabled}
58
+ aria-label={ariaLabel}
59
+ {onclick}
60
+ >
61
+ {@render children()}
62
+ </button>
@@ -0,0 +1,24 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ /** Visual style — maps to `.flowdrop-btn--{variant}` in base.css */
4
+ variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
5
+ /** Size — `md` is the base `.flowdrop-btn`; `sm`/`lg` add a modifier */
6
+ size?: 'sm' | 'md' | 'lg';
7
+ /** Native button type */
8
+ type?: 'button' | 'submit' | 'reset';
9
+ /** Tooltip text */
10
+ title?: string;
11
+ /** Accessible label (use when the button is icon-only) */
12
+ ariaLabel?: string;
13
+ /** Disabled state */
14
+ disabled?: boolean;
15
+ /** Extra classes appended to the root button */
16
+ class?: string;
17
+ /** Click handler */
18
+ onclick?: (event: MouseEvent) => void;
19
+ /** Button contents (icon, label, or both) */
20
+ children: Snippet;
21
+ }
22
+ declare const Button: import("svelte").Component<Props, {}, "">;
23
+ type Button = ReturnType<typeof Button>;
24
+ export default Button;
@@ -1044,7 +1044,7 @@
1044
1044
  justify-content: center;
1045
1045
  gap: var(--fd-space-xs);
1046
1046
  padding: 0.625rem var(--fd-space-xl);
1047
- border-radius: var(--fd-radius-lg);
1047
+ border-radius: var(--fd-control-radius);
1048
1048
  font-size: var(--fd-text-sm);
1049
1049
  font-weight: 600;
1050
1050
  font-family: inherit;
@@ -1100,7 +1100,7 @@
1100
1100
  .config-form__extensions {
1101
1101
  background-color: var(--fd-muted);
1102
1102
  border: 1px solid var(--fd-border);
1103
- border-radius: var(--fd-radius-lg);
1103
+ border-radius: var(--fd-control-radius);
1104
1104
  overflow: hidden;
1105
1105
  margin-top: var(--fd-space-xs);
1106
1106
  }
@@ -1271,7 +1271,7 @@
1271
1271
  .config-form__debug {
1272
1272
  background-color: var(--fd-warning-muted);
1273
1273
  border: 1px solid var(--fd-warning);
1274
- border-radius: var(--fd-radius-lg);
1274
+ border-radius: var(--fd-control-radius);
1275
1275
  overflow: hidden;
1276
1276
  }
1277
1277
 
@@ -1425,7 +1425,7 @@
1425
1425
  .config-form__error {
1426
1426
  background-color: var(--fd-error-muted);
1427
1427
  border: 1px solid var(--fd-error);
1428
- border-radius: var(--fd-radius-lg);
1428
+ border-radius: var(--fd-control-radius);
1429
1429
  overflow: hidden;
1430
1430
  }
1431
1431
 
@@ -0,0 +1,44 @@
1
+ <script module>
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import EditorStatusBar from './EditorStatusBar.svelte';
4
+ import { fn } from 'storybook/test';
5
+
6
+ const { Story } = defineMeta({
7
+ title: 'Editor/EditorStatusBar',
8
+ component: EditorStatusBar,
9
+ tags: ['autodocs'],
10
+ parameters: {
11
+ // The banner is full-width editor chrome — show it edge to edge.
12
+ layout: 'fullscreen'
13
+ },
14
+ args: {
15
+ onRetry: fn(),
16
+ onSetApiUrl: fn(),
17
+ onTestApi: fn(),
18
+ onDismiss: fn()
19
+ }
20
+ });
21
+ </script>
22
+
23
+ <Story
24
+ name="Default"
25
+ args={{
26
+ error: 'API Error: Failed to fetch. No node types available.'
27
+ }}
28
+ />
29
+
30
+ <!-- The real message when the endpoint serves HTML (e.g. a 404 page) instead of JSON. -->
31
+ <Story
32
+ name="Long Message"
33
+ args={{
34
+ error:
35
+ 'API Error: Unexpected token \'<\', "<!doctype "... is not valid JSON. No node types available.'
36
+ }}
37
+ />
38
+
39
+ <Story
40
+ name="Endpoint Unreachable"
41
+ args={{
42
+ error: 'API Error: NetworkError when attempting to fetch resource. No node types available.'
43
+ }}
44
+ />
@@ -0,0 +1,27 @@
1
+ export default EditorStatusBar;
2
+ type EditorStatusBar = SvelteComponent<{
3
+ [x: string]: never;
4
+ }, {
5
+ [evt: string]: CustomEvent<any>;
6
+ }, {}> & {
7
+ $$bindings?: string | undefined;
8
+ };
9
+ declare const EditorStatusBar: $$__sveltets_2_IsomorphicComponent<{
10
+ [x: string]: never;
11
+ }, {
12
+ [evt: string]: CustomEvent<any>;
13
+ }, {}, {}, string>;
14
+ import EditorStatusBar from './EditorStatusBar.svelte';
15
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
16
+ new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
17
+ $$bindings?: Bindings;
18
+ } & Exports;
19
+ (internal: unknown, props: {
20
+ $$events?: Events;
21
+ $$slots?: Slots;
22
+ }): Exports & {
23
+ $set?: any;
24
+ $on?: any;
25
+ };
26
+ z_$$bindings?: Bindings;
27
+ }
@@ -0,0 +1,99 @@
1
+ <!--
2
+ EditorStatusBar — the dismissible error banner shown above the editor canvas
3
+ when node types fail to load (endpoint missing or unreachable).
4
+
5
+ Extracted from App.svelte so it can be storied/tested in isolation. The action
6
+ buttons route through the shared Button primitive; all colours come from design
7
+ tokens, so the banner stays in sync with the rest of the editor chrome.
8
+ -->
9
+ <script lang="ts">
10
+ import Button from './Button.svelte';
11
+ import CloseIcon from './icons/CloseIcon.svelte';
12
+
13
+ interface Props {
14
+ /** The error message to display (rendered after an "Error:" prefix). */
15
+ error: string;
16
+ /** Retry loading node types. */
17
+ onRetry: () => void;
18
+ /** Prompt for / set a new backend API URL. */
19
+ onSetApiUrl: () => void;
20
+ /** Run a connectivity test against the configured endpoint. */
21
+ onTestApi: () => void;
22
+ /** Dismiss the banner. */
23
+ onDismiss: () => void;
24
+ }
25
+
26
+ let { error, onRetry, onSetApiUrl, onTestApi, onDismiss }: Props = $props();
27
+ </script>
28
+
29
+ <!-- aria-live announces the API error dynamically without requiring focus -->
30
+ <div class="flowdrop-status flowdrop-status--error" aria-live="polite" aria-atomic="true">
31
+ <div class="flowdrop-status__content">
32
+ <div class="flowdrop-status__message">
33
+ <div class="flowdrop-status__indicator"></div>
34
+ <span class="flowdrop-status__text">Error: {error}</span>
35
+ </div>
36
+ <div class="flowdrop-status__actions">
37
+ <Button variant="primary" size="sm" onclick={onRetry}>Retry</Button>
38
+ <Button variant="outline" size="sm" onclick={onSetApiUrl}>Set API URL</Button>
39
+ <Button variant="outline" size="sm" onclick={onTestApi}>Test API</Button>
40
+ <Button variant="ghost" size="sm" ariaLabel="Dismiss error" onclick={onDismiss}>
41
+ <span class="flowdrop-status__dismiss-icon"><CloseIcon /></span>
42
+ </Button>
43
+ </div>
44
+ </div>
45
+ </div>
46
+
47
+ <style>
48
+ .flowdrop-status {
49
+ padding: var(--fd-space-xl);
50
+ }
51
+
52
+ .flowdrop-status--error {
53
+ background-color: var(--fd-error-muted);
54
+ border-bottom: 1px solid var(--fd-error);
55
+ }
56
+
57
+ .flowdrop-status__content {
58
+ display: flex;
59
+ align-items: center;
60
+ justify-content: space-between;
61
+ gap: var(--fd-space-md);
62
+ }
63
+
64
+ .flowdrop-status__message {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: var(--fd-space-md);
68
+ min-width: 0;
69
+ }
70
+
71
+ .flowdrop-status__indicator {
72
+ flex-shrink: 0;
73
+ width: 0.5rem;
74
+ height: 0.5rem;
75
+ border-radius: 50%;
76
+ background-color: var(--fd-error);
77
+ }
78
+
79
+ .flowdrop-status__text {
80
+ font-size: var(--fd-text-sm);
81
+ line-height: 1.25rem;
82
+ font-weight: 500;
83
+ }
84
+
85
+ .flowdrop-status__actions {
86
+ display: flex;
87
+ gap: var(--fd-space-xs);
88
+ flex-shrink: 0;
89
+ }
90
+
91
+ .flowdrop-status__dismiss-icon {
92
+ display: inline-flex;
93
+ }
94
+
95
+ .flowdrop-status__dismiss-icon :global(svg) {
96
+ width: 0.875rem;
97
+ height: 0.875rem;
98
+ }
99
+ </style>
@@ -0,0 +1,15 @@
1
+ interface Props {
2
+ /** The error message to display (rendered after an "Error:" prefix). */
3
+ error: string;
4
+ /** Retry loading node types. */
5
+ onRetry: () => void;
6
+ /** Prompt for / set a new backend API URL. */
7
+ onSetApiUrl: () => void;
8
+ /** Run a connectivity test against the configured endpoint. */
9
+ onTestApi: () => void;
10
+ /** Dismiss the banner. */
11
+ onDismiss: () => void;
12
+ }
13
+ declare const EditorStatusBar: import("svelte").Component<Props, {}, "">;
14
+ type EditorStatusBar = ReturnType<typeof EditorStatusBar>;
15
+ export default EditorStatusBar;