@moldable-ai/ui 0.2.21 → 0.2.22

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 (64) hide show
  1. package/dist/catalog/catalog-types.d.ts +7 -0
  2. package/dist/catalog/catalog-types.d.ts.map +1 -1
  3. package/dist/catalog/catalog-types.js +1 -0
  4. package/dist/catalog/core-catalog-exclusions.d.ts.map +1 -1
  5. package/dist/catalog/core-catalog-exclusions.js +4 -0
  6. package/dist/catalog/core-collection-stories.js +1 -1
  7. package/dist/catalog/core-control-stories.d.ts.map +1 -1
  8. package/dist/catalog/core-control-stories.js +6 -3
  9. package/dist/components/app-error-boundary.d.ts.map +1 -1
  10. package/dist/components/app-error-boundary.js +3 -2
  11. package/dist/components/ui/alert-dialog.d.ts +4 -2
  12. package/dist/components/ui/alert-dialog.d.ts.map +1 -1
  13. package/dist/components/ui/alert-dialog.js +8 -4
  14. package/dist/components/ui/alert.d.ts +1 -1
  15. package/dist/components/ui/button.d.ts +6 -4
  16. package/dist/components/ui/button.d.ts.map +1 -1
  17. package/dist/components/ui/button.js +40 -8
  18. package/dist/components/ui/confirm-dialog.d.ts.map +1 -1
  19. package/dist/components/ui/confirm-dialog.js +1 -1
  20. package/dist/components/ui/dialog-primary-action.d.ts +4 -0
  21. package/dist/components/ui/dialog-primary-action.d.ts.map +1 -0
  22. package/dist/components/ui/dialog-primary-action.js +37 -0
  23. package/dist/components/ui/dialog.d.ts +6 -2
  24. package/dist/components/ui/dialog.d.ts.map +1 -1
  25. package/dist/components/ui/dialog.js +10 -3
  26. package/dist/components/ui/error-boundary-view.d.ts +12 -0
  27. package/dist/components/ui/error-boundary-view.d.ts.map +1 -0
  28. package/dist/components/ui/error-boundary-view.js +8 -0
  29. package/dist/components/ui/icon-button.d.ts.map +1 -1
  30. package/dist/components/ui/icon-button.js +1 -2
  31. package/dist/components/ui/index.d.ts +1 -0
  32. package/dist/components/ui/index.d.ts.map +1 -1
  33. package/dist/components/ui/index.js +1 -0
  34. package/dist/components/ui/input-group.d.ts +1 -1
  35. package/dist/components/ui/spinner.d.ts.map +1 -1
  36. package/dist/components/ui/spinner.js +1 -1
  37. package/dist/index.d.ts +6 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +7 -1
  40. package/dist/lib/frame-lifecycle.d.ts +22 -0
  41. package/dist/lib/frame-lifecycle.d.ts.map +1 -1
  42. package/dist/lib/frame-lifecycle.js +27 -1
  43. package/dist/lib/host-date-time-picker.d.ts +36 -0
  44. package/dist/lib/host-date-time-picker.d.ts.map +1 -0
  45. package/dist/lib/host-date-time-picker.js +162 -0
  46. package/dist/lib/host-file-dialog.d.ts +66 -0
  47. package/dist/lib/host-file-dialog.d.ts.map +1 -0
  48. package/dist/lib/host-file-dialog.js +221 -0
  49. package/dist/lib/host-native-menu.d.ts +75 -0
  50. package/dist/lib/host-native-menu.d.ts.map +1 -0
  51. package/dist/lib/host-native-menu.js +187 -0
  52. package/dist/lib/host-services.d.ts +13 -0
  53. package/dist/lib/host-services.d.ts.map +1 -0
  54. package/dist/lib/host-services.js +161 -0
  55. package/dist/lib/host-share.d.ts +39 -0
  56. package/dist/lib/host-share.d.ts.map +1 -0
  57. package/dist/lib/host-share.js +161 -0
  58. package/docs/README.md +1 -0
  59. package/docs/visual-regression.md +78 -0
  60. package/package.json +12 -3
  61. package/src/components/ui/alert-dialog.md +27 -0
  62. package/src/components/ui/button.md +10 -3
  63. package/src/components/ui/dialog.md +23 -4
  64. package/src/components/ui/error-boundary-view.md +98 -0
@@ -0,0 +1,78 @@
1
+ # Visual and accessibility regression
2
+
3
+ The phase 1 runner exercises every authored story in the core component
4
+ catalog. It does not start the desktop shell or any Moldable-managed app.
5
+
6
+ ## What runs
7
+
8
+ `visual-host/` is a small Vite entry that renders one story at a time:
9
+
10
+ ```text
11
+ ?story=controls/button&theme=dark&width=360
12
+ ```
13
+
14
+ Unique bare story ids are also accepted. Optional
15
+ `transparency=reduced` and `contrast=increased` parameters set the same
16
+ manual accessibility attributes used by the catalog. The host bundles Inter
17
+ and Roboto Mono, waits for `document.fonts.ready`, and disables animations,
18
+ transitions, scrolling, and the caret under `data-motion="reduced"`.
19
+
20
+ The Playwright runner builds and serves that static host, blocks non-host
21
+ network requests, freezes time and randomness, and tests Chromium at:
22
+
23
+ - light and dark themes;
24
+ - 360 × 720 and 960 × 720 viewports;
25
+ - rest plus the browser-driven states declared by an exemplar story.
26
+
27
+ A story opts into browser-driven states with `liveStates` and marks exactly
28
+ one element:
29
+
30
+ ```tsx
31
+ <Button data-state-target="hover focus pressed">Continue</Button>
32
+ ```
33
+
34
+ Playwright performs real hover, focus, and held-pointer interactions on that
35
+ element. Button, segmented control, and toggle stories are the phase 1
36
+ exemplars.
37
+
38
+ Every captured case also receives an axe WCAG 2 A/AA and WCAG 2.1 A/AA scan.
39
+ Serious and critical violations fail. Per-story suppressions belong in
40
+ `visual-tests/visual-matrix.ts` and must include a reason. Global
41
+ suppressions are intentionally unsupported.
42
+
43
+ ## Local commands
44
+
45
+ Install the pinned Chromium build once:
46
+
47
+ ```bash
48
+ pnpm -C packages/ui exec playwright install chromium
49
+ ```
50
+
51
+ Run against committed baselines:
52
+
53
+ ```bash
54
+ pnpm -C packages/ui test:visual
55
+ ```
56
+
57
+ Create or intentionally update baselines after reviewing the UI change:
58
+
59
+ ```bash
60
+ pnpm -C packages/ui test:visual:update
61
+ ```
62
+
63
+ Baseline PNGs live in `visual-tests/baselines/`. Failures emit actual,
64
+ expected, diff, and trace artifacts in the gitignored `.visual-results/`
65
+ directory.
66
+
67
+ ## Determinism and skips
68
+
69
+ Stories must use fixed inputs and no live data, network, wall-clock timers, or
70
+ unseeded randomness. Add an unavoidable phase 1 skip to
71
+ `deterministicStorySkips` in `visual-tests/visual-matrix.ts` with a specific
72
+ reason. Skips are reported in the runner summary; silent conditional skips
73
+ are not allowed.
74
+
75
+ ## CI
76
+
77
+ The `ui-regression` CI job installs Chromium and its Linux dependencies,
78
+ builds only `@moldable-ai/ui`, then runs the combined visual and axe suite.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moldable-ai/ui",
3
- "version": "0.2.21",
3
+ "version": "0.2.22",
4
4
  "description": "Shared UI components for Moldable applications",
5
5
  "author": "Desiderata LLC",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -42,11 +42,15 @@
42
42
  ],
43
43
  "scripts": {
44
44
  "build": "tsc",
45
+ "build:visual-host": "vite build --config vite.visual.config.ts",
45
46
  "dev": "tsc --watch",
46
47
  "lint": "eslint . --ext .ts,.tsx --max-warnings 0",
47
48
  "format": "prettier --write . --ignore-path ../../.prettierignore",
48
- "check-types": "tsc --noEmit",
49
- "test": "vitest run"
49
+ "check-types": "tsc --noEmit && tsc --noEmit -p tsconfig.visual.json",
50
+ "test": "vitest run",
51
+ "test:visual": "pnpm build:visual-host && playwright test --config playwright.visual.config.ts",
52
+ "test:visual:update": "pnpm build:visual-host && playwright test --config playwright.visual.config.ts --update-snapshots",
53
+ "visual:serve": "vite preview --config vite.visual.config.ts --host 127.0.0.1 --port 4179 --strictPort"
50
54
  },
51
55
  "dependencies": {
52
56
  "@base-ui/react": "^1.1.0",
@@ -105,14 +109,19 @@
105
109
  "zod": "^4.3.5"
106
110
  },
107
111
  "devDependencies": {
112
+ "@axe-core/playwright": "^4.12.1",
113
+ "@fontsource/inter": "^5.3.0",
114
+ "@fontsource/roboto-mono": "^5.3.0",
108
115
  "@moldable-ai/eslint-config": "workspace:*",
109
116
  "@moldable-ai/prettier-config": "workspace:*",
110
117
  "@moldable-ai/typescript-config": "workspace:*",
118
+ "@playwright/test": "^1.62.0",
111
119
  "@types/node": "^24.0.3",
112
120
  "@types/react": "^19.1.6",
113
121
  "@types/react-dom": "^19.1.6",
114
122
  "jsdom": "^26.1.0",
115
123
  "typescript": "^5.8.3",
124
+ "vite": "^6.4.3",
116
125
  "vitest": "^4.1.8"
117
126
  },
118
127
  "peerDependencies": {
@@ -25,6 +25,33 @@ shown. The default error deliberately avoids exposing internal details.
25
25
  host safe area. Use `AlertDialogBody` when explanation or affected-item content
26
26
  can grow. Keep the consequence visible while the action is pending.
27
27
 
28
+ For composed alert dialogs, put `loading` on `AlertDialogAction` while its
29
+ request is in flight. This forwards the standard `Button` busy state, blocks
30
+ duplicate activation, and preserves the action label width and accessible name.
31
+ Use `pending` with `pendingLabel` when maintaining the confirmation-specific
32
+ status-label pattern; `ConfirmDialog` manages that state automatically. Disable
33
+ the surrounding form only when its other controls must not change during the
34
+ request.
35
+
36
+ Mark one confirming action with `primary` to enable Command+Enter on macOS or
37
+ Control+Enter on other platforms. The shortcut does not run without a marked
38
+ action, while that action is loading or disabled, during key composition or
39
+ repeat, or when a focused control consumes the event with `preventDefault()`.
40
+ Plain Enter remains native and Escape remains the Radix cancel behavior.
41
+
42
+ ```tsx
43
+ <AlertDialogFooter>
44
+ <span className="text-muted-foreground mr-auto inline-flex items-center gap-1 text-xs">
45
+ Confirm <Kbd>⌘</Kbd>
46
+ <Kbd>Enter</Kbd>
47
+ </span>
48
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
49
+ <AlertDialogAction primary loading={isDeleting}>
50
+ Delete recording
51
+ </AlertDialogAction>
52
+ </AlertDialogFooter>
53
+ ```
54
+
28
55
  Use concrete labels such as “Delete recording,” not “OK.” Put the safe,
29
56
  reversible action first in keyboard order and the confirming action last. Do
30
57
  not use an alert dialog for ordinary navigation, long forms, or success
@@ -25,9 +25,15 @@ import { RefreshCw } from 'lucide-react'
25
25
  >
26
26
  <RefreshCw />
27
27
  </Button>
28
+
29
+ <Button type="submit" loading={isSaving}>
30
+ Save changes
31
+ </Button>
28
32
  ```
29
33
 
30
- Keep toolbar actions compact (`xs`, `sm`, `icon-xs`, or `icon-sm`). Reserve `default` or `lg` for the primary action in a form, setup state, or focused control dock. While an async action is running, disable the button, preserve its width, and show a small spinner beside a short status label.
34
+ Keep toolbar actions compact (`xs`, `sm`, `icon-xs`, or `icon-sm`). Reserve `default` or `lg` for the primary action in a form, setup state, or focused control dock. Set `loading` while that button's async action is in flight. The button blocks pointer and keyboard activation, exposes `aria-busy`, keeps its accessible label mounted, and centers a width-stable spinner.
35
+
36
+ Use `loading` for the action that started the request. Disable the whole form only when editing during the request would be unsafe or would invalidate the submitted payload; do not disable unrelated controls merely to communicate progress.
31
37
 
32
38
  For links that should look like buttons:
33
39
 
@@ -42,13 +48,14 @@ For links that should look like buttons:
42
48
  - `variant`: `default`, `destructive`, `outline`, `secondary`, `ghost`, or `link`. Defaults to `default`.
43
49
  - `size`: `default`, `xs`, `sm`, `lg`, `icon`, `icon-xs`, `icon-sm`, or `icon-lg`. Defaults to `default`.
44
50
  - `asChild`: renders a Radix `Slot` so the child owns the element and semantics.
51
+ - `loading`: blocks activation, applies disabled behavior and `aria-busy`, and overlays the shared `Spinner` without unmounting or resizing the label. Defaults to `false`.
45
52
  - Accepts native button props, including `type`, `disabled`, `aria-*`, and event handlers.
46
53
  - `buttonVariants` is exported for cases that need the same visual treatment on another component.
47
54
 
48
55
  ## Do
49
56
 
50
57
  - Set `type="button"` unless the control intentionally submits a form.
51
- - Add `cursor-pointer`; use the real `disabled` attribute for unavailable or busy actions.
58
+ - Add `cursor-pointer`; use the real `disabled` attribute for unavailable actions and `loading` for an in-flight action.
52
59
  - Give icon-only buttons an `aria-label` and a tooltip.
53
60
  - Use a specific Lucide icon that names the action.
54
61
  - Put one visually primary action in a local action group.
@@ -60,7 +67,7 @@ For links that should look like buttons:
60
67
  - Do not rely on color alone to explain a destructive or selected action.
61
68
  - Do not use raw color classes, decorative gradients, or oversized call-to-action styling.
62
69
  - Do not use `Sparkles` as a generic AI marker.
63
- - Do not leave a busy button clickable or allow repeated submission.
70
+ - Do not use `disabled` alone as the progress signal; use `loading` so progress is announced and the label width stays stable.
64
71
  - Do not hide essential actions behind hover-only icon buttons.
65
72
 
66
73
  ## Moldable considerations
@@ -18,7 +18,15 @@ spatial context.
18
18
  <DialogDescription>Update the selected record.</DialogDescription>
19
19
  </DialogHeader>
20
20
  <DialogBody>{/* the only scrolling region */}</DialogBody>
21
- <DialogFooter>{/* actions remain reachable */}</DialogFooter>
21
+ <DialogFooter>
22
+ <span className="text-muted-foreground mr-auto inline-flex items-center gap-1 text-xs">
23
+ Save <Kbd>⌘</Kbd>
24
+ <Kbd>Enter</Kbd>
25
+ </span>
26
+ <DialogAction primary loading={isSaving} onClick={saveChanges}>
27
+ Save changes
28
+ </DialogAction>
29
+ </DialogFooter>
22
30
  </DialogContent>
23
31
  </Dialog>
24
32
  ```
@@ -30,9 +38,20 @@ scrolling; do not put overflow on both the content and body.
30
38
  Keep dialogs opaque and visually stable. Translucent material belongs on small
31
39
  temporary control overlays where seeing the underlying context is useful.
32
40
 
33
- For async actions, keep the dialog controlled, disable duplicate submission,
34
- preserve the action label width, show inline field errors, and close only after
35
- success. Use `ConfirmDialog` when that is the entire workflow.
41
+ Use `DialogAction` for footer commands that need the standard async and keyboard
42
+ contracts. Its `loading` prop forwards to `Button`, preventing duplicate
43
+ activation while retaining the label width and accessible name. Keep the dialog
44
+ controlled, show inline field errors, and close only after success. Disable the
45
+ whole form only when edits during submission would be unsafe. Use
46
+ `ConfirmDialog` when confirmation is the entire workflow.
47
+
48
+ Set `primary` on at most one `DialogAction` to opt into the default-action
49
+ shortcut. Command+Enter activates it on macOS; Control+Enter does so on other
50
+ platforms. The shortcut works from anywhere in the dialog unless a focused
51
+ control consumes the key event with `preventDefault()`, or the primary action is
52
+ loading or disabled. Plain Enter keeps the focused control's native behavior.
53
+ Use `Kbd` to show the shortcut when it improves discovery; render the
54
+ platform-appropriate modifier in app code.
36
55
 
37
56
  Every dialog needs a `DialogTitle`. Add `DialogDescription` when the outcome or
38
57
  scope is not obvious. Escape, outside dismissal, initial focus, and focus return
@@ -0,0 +1,98 @@
1
+ # ErrorBoundaryView
2
+
3
+ `ErrorBoundaryView` presents a compact recovery state without owning error
4
+ catching, reporting, or host policy.
5
+
6
+ ## When to use
7
+
8
+ Use it as the renderer for a React error boundary, route-level failure, or
9
+ host-provided pane error. Supply `actions` when the host owns reporting,
10
+ navigation, or chat recovery.
11
+
12
+ ## When not to use
13
+
14
+ Do not use it for inline field validation, routine empty states, or transient
15
+ request failures that can stay beside the initiating control. It does not catch
16
+ errors itself; use `AppErrorBoundary` when the package should own catching and
17
+ Moldable reporting.
18
+
19
+ ## Anatomy
20
+
21
+ The view contains one compact surface with a title, optional message, optional
22
+ error-details disclosure, and one action row. It does not add illustrations or
23
+ nested cards.
24
+
25
+ ## Usage
26
+
27
+ ```tsx
28
+ import { ErrorBoundaryView } from '@moldable-ai/ui'
29
+
30
+ function PaneError() {
31
+ return (
32
+ <ErrorBoundaryView
33
+ title="Could not load this pane"
34
+ message="Retry the request or report the captured error."
35
+ details={error.stack}
36
+ onRetry={retry}
37
+ actions={
38
+ <Button type="button" variant="secondary" onClick={reportError}>
39
+ Report error
40
+ </Button>
41
+ }
42
+ />
43
+ )
44
+ }
45
+ ```
46
+
47
+ ## Variants and sizes
48
+
49
+ The renderer has one responsive layout. It uses the available height in a pane
50
+ and can fill a window when its parent gives it full height. The content remains
51
+ bounded to a readable width.
52
+
53
+ ## API
54
+
55
+ - `title`: heading content. Defaults to `"Something went wrong"`.
56
+ - `message`: optional supporting copy.
57
+ - `details`: optional string shown in a collapsed disclosure with a bounded,
58
+ scrollable monospace region.
59
+ - `onRetry`: when supplied, renders a `Retry` button and calls the handler once
60
+ per activation.
61
+ - `actions`: optional host-provided action content rendered after Retry.
62
+ - `className`: optional classes merged onto the outer renderer.
63
+
64
+ ## States and interaction
65
+
66
+ Omit `details` when there is no safe diagnostic text to show. Omit both
67
+ `onRetry` and `actions` for a read-only failure. Keep retry state in the caller
68
+ and pass `loading` to a custom action when retrying is asynchronous.
69
+
70
+ ## Keyboard and accessibility
71
+
72
+ The details use native `details` and `summary` semantics. The title is the
73
+ surface heading, the Retry control keeps native button behavior, and diagnostic
74
+ text remains selectable.
75
+
76
+ ## Platform behavior
77
+
78
+ The component has no host calls or platform branches. `AppErrorBoundary`
79
+ supplies Moldable-specific reload and chat-prefill actions.
80
+
81
+ ## Do
82
+
83
+ - Keep the title calm and literal.
84
+ - Give the message a concrete recovery path.
85
+ - Redact secrets before passing diagnostic details.
86
+ - Use semantic theme tokens in custom actions.
87
+
88
+ ## Don't
89
+
90
+ - Do not add decorative error art or nested cards.
91
+ - Do not put binary data or unbounded logs in `details`.
92
+ - Do not make this presentational component report or reload implicitly.
93
+
94
+ ## Testing
95
+
96
+ Contract tests should verify copy, disclosure behavior, Retry activation, and
97
+ host action rendering. Error-catching and reporting tests belong to the owning
98
+ boundary.