@poodle64/ui 2026.8.7 → 2026.8.9

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.
@@ -50,7 +50,10 @@
50
50
  * applied it as a helper class per route, so it reached four routes out of
51
51
  * about fifteen. Which pages wore the house atmosphere was decided by which
52
52
  * pages someone had happened to touch. `texture` is one named picture the
53
- * SHELL paints once, off by default, its inks tuned by custom properties.
53
+ * SHELL paints once, its inks tuned by custom properties. It shipped off by
54
+ * default so its arrival moved nobody; it is ON by default now, because an
55
+ * opt-in house style is not a house style — it is a survey of who
56
+ * remembered. `collapsible` is on for the same reason.
54
57
  *
55
58
  * Everything else the surveyed apps differed on turned out to be a slot, not
56
59
  * a variant: the brand, the identity surface, a context switcher, a banner, a
@@ -83,7 +86,7 @@
83
86
  nav,
84
87
  navLabel = 'Primary',
85
88
  currentPath,
86
- collapsible = false,
89
+ collapsible = true,
87
90
  collapsed = $bindable(false),
88
91
  brand,
89
92
  brandTitle,
@@ -102,7 +105,7 @@
102
105
  onToggleTheme,
103
106
  padded = true,
104
107
  measure = 'full',
105
- texture = 'none',
108
+ texture = 'grid',
106
109
  mainClass,
107
110
  children
108
111
  }: {
@@ -123,7 +126,15 @@
123
126
  * a component that is framework-agnostic and genuinely drivable.
124
127
  */
125
128
  currentPath?: string;
126
- /** Offer an icon-only collapse toggle at the rail head, on the brand row. */
129
+ /**
130
+ * Offer an icon-only collapse toggle at the rail head, on the brand row.
131
+ *
132
+ * Defaults ON. It was opt-in while the control was being introduced, and
133
+ * two of nine household apps opted in — so seven rails could not be
134
+ * collapsed, for no reason anyone had chosen. The shell shape is not an
135
+ * app's to pick (operator ruling, 31/07/2026); set `false` only where a
136
+ * rail genuinely must not collapse.
137
+ */
127
138
  collapsible?: boolean;
128
139
  /** Rail collapse state. Bind it to persist the choice across sessions. */
129
140
  collapsed?: boolean;
@@ -178,12 +189,15 @@
178
189
  * The house atmosphere on the content region: `grid` (a dot-grid floor
179
190
  * plus a corner vignette) or `none`.
180
191
  *
181
- * Defaults to `none`, so a shell that does not mention it is unchanged.
182
- * Set it in the layout, once that the SHELL paints it is the whole
183
- * point, because a helper class apps apply per route is how one consumer
184
- * ended up wearing the house texture on four routes out of fifteen. The
185
- * inks and the pitch are `--ds-shell-texture-*` custom properties an app
186
- * retunes in one declaration.
192
+ * Defaults to `grid` the house atmosphere every app wears. It shipped
193
+ * as `none` so its introduction moved nobody, and the result was the drift
194
+ * it was built to end: five of nine apps had it, three of those through a
195
+ * hand-rolled `*-dotgrid` class in their own app.css under three names,
196
+ * two ink variables and two pitches, only one carrying the
197
+ * `background-attachment: local` that stops the floor sliding under the
198
+ * scroller. One default retires all three. The inks and the pitch are
199
+ * `--ds-shell-texture-*` custom properties an app retunes in one
200
+ * declaration; `none` turns it off.
187
201
  */
188
202
  texture?: ShellTexture;
189
203
  /** Extra classes on the scrolling content container. */
@@ -370,7 +384,22 @@
370
384
  </span>
371
385
  {/if}
372
386
  {#if brandTitle && !railCollapsed}
373
- <span class="font-display text-body font-semibold tracking-tight">{brandTitle}</span>
387
+ <!--
388
+ The wordmark stands down below sm, leaving the mark alone. On a phone
389
+ the rail is gone, so this same lockup is what the top bar carries — and
390
+ that bar also holds the menu button, the search affordance, the theme
391
+ toggle and the identity surface. With the wordmark beside them, search
392
+ truncated to a word (and in one app to a single letter) at 390px.
393
+
394
+ This is a package default because three apps each discovered it
395
+ independently and each wrote its own `brand` override to get it, with
396
+ three near-identical comments. An app that overrides the whole brand
397
+ slot to re-earn a behaviour the shell could give everyone is the shell
398
+ failing to ship something good.
399
+ -->
400
+ <span class="font-display text-body hidden font-semibold tracking-tight sm:inline"
401
+ >{brandTitle}</span
402
+ >
374
403
  {/if}
375
404
  {/if}
376
405
  </a>
@@ -49,7 +49,10 @@
49
49
  * applied it as a helper class per route, so it reached four routes out of
50
50
  * about fifteen. Which pages wore the house atmosphere was decided by which
51
51
  * pages someone had happened to touch. `texture` is one named picture the
52
- * SHELL paints once, off by default, its inks tuned by custom properties.
52
+ * SHELL paints once, its inks tuned by custom properties. It shipped off by
53
+ * default so its arrival moved nobody; it is ON by default now, because an
54
+ * opt-in house style is not a house style — it is a survey of who
55
+ * remembered. `collapsible` is on for the same reason.
53
56
  *
54
57
  * Everything else the surveyed apps differed on turned out to be a slot, not
55
58
  * a variant: the brand, the identity surface, a context switcher, a banner, a
@@ -85,7 +88,15 @@ type $$ComponentProps = {
85
88
  * a component that is framework-agnostic and genuinely drivable.
86
89
  */
87
90
  currentPath?: string;
88
- /** Offer an icon-only collapse toggle at the rail head, on the brand row. */
91
+ /**
92
+ * Offer an icon-only collapse toggle at the rail head, on the brand row.
93
+ *
94
+ * Defaults ON. It was opt-in while the control was being introduced, and
95
+ * two of nine household apps opted in — so seven rails could not be
96
+ * collapsed, for no reason anyone had chosen. The shell shape is not an
97
+ * app's to pick (operator ruling, 31/07/2026); set `false` only where a
98
+ * rail genuinely must not collapse.
99
+ */
89
100
  collapsible?: boolean;
90
101
  /** Rail collapse state. Bind it to persist the choice across sessions. */
91
102
  collapsed?: boolean;
@@ -140,12 +151,15 @@ type $$ComponentProps = {
140
151
  * The house atmosphere on the content region: `grid` (a dot-grid floor
141
152
  * plus a corner vignette) or `none`.
142
153
  *
143
- * Defaults to `none`, so a shell that does not mention it is unchanged.
144
- * Set it in the layout, once that the SHELL paints it is the whole
145
- * point, because a helper class apps apply per route is how one consumer
146
- * ended up wearing the house texture on four routes out of fifteen. The
147
- * inks and the pitch are `--ds-shell-texture-*` custom properties an app
148
- * retunes in one declaration.
154
+ * Defaults to `grid` the house atmosphere every app wears. It shipped
155
+ * as `none` so its introduction moved nobody, and the result was the drift
156
+ * it was built to end: five of nine apps had it, three of those through a
157
+ * hand-rolled `*-dotgrid` class in their own app.css under three names,
158
+ * two ink variables and two pitches, only one carrying the
159
+ * `background-attachment: local` that stops the floor sliding under the
160
+ * scroller. One default retires all three. The inks and the pitch are
161
+ * `--ds-shell-texture-*` custom properties an app retunes in one
162
+ * declaration; `none` turns it off.
149
163
  */
150
164
  texture?: ShellTexture;
151
165
  /** Extra classes on the scrolling content container. */
package/package.json CHANGED
@@ -1,81 +1,84 @@
1
1
  {
2
- "name": "@poodle64/ui",
3
- "version": "2026.8.7",
4
- "description": "Household shared component layer: shadcn-svelte primitives (bits-ui) plus the composed page chrome every app builds its routes from, restyled by each app's @poodle64/design-tokens alias layer. One fix reaches every app.",
5
- "type": "module",
6
- "license": "MIT",
7
- "repository": "github:poodle64/design-system",
8
- "publishConfig": {
9
- "registry": "https://registry.npmjs.org",
10
- "access": "public"
11
- },
12
- "files": [
13
- "dist"
14
- ],
15
- "exports": {
16
- "./package.json": "./package.json",
17
- "./styles.css": "./dist/styles.css",
18
- "./utils": {
19
- "types": "./dist/utils.d.ts",
20
- "svelte": "./dist/utils.js"
21
- },
22
- "./*": {
23
- "types": "./dist/components/ui/*/index.d.ts",
24
- "svelte": "./dist/components/ui/*/index.js"
25
- }
26
- },
27
- "peerDependencies": {
28
- "bits-ui": "^2.18.1",
29
- "mode-watcher": "^1.1.0",
30
- "svelte": "^5.33.0",
31
- "svelte-sonner": "^1.1.1"
32
- },
33
- "peerDependenciesMeta": {
34
- "mode-watcher": {
35
- "optional": true
36
- },
37
- "svelte-sonner": {
38
- "optional": true
39
- }
40
- },
41
- "dependencies": {
42
- "@lucide/svelte": "^1.25.0",
43
- "@tanstack/table-core": "^8.21.3",
44
- "clsx": "^2.1.1",
45
- "tailwind-merge": "^3.6.0",
46
- "tailwind-variants": "^3.2.2",
47
- "tw-animate-css": "^1.4.0"
48
- },
49
- "devDependencies": {
50
- "@poodle64/design-tokens": "workspace:*",
51
- "@sveltejs/kit": "^2.63.0",
52
- "@sveltejs/package": "^2.5.8",
53
- "@sveltejs/vite-plugin-svelte": "^7.1.2",
54
- "@tailwindcss/cli": "^4.3.2",
55
- "@testing-library/jest-dom": "^7.0.0",
56
- "@testing-library/svelte": "^5.4.2",
57
- "@types/node": "^22.20.1",
58
- "bits-ui": "^2.18.1",
59
- "jsdom": "^29.1.1",
60
- "mode-watcher": "^1.1.0",
61
- "playwright": "^1.62.0",
62
- "publint": "^0.3.15",
63
- "svelte": "^5.56.2",
64
- "svelte-check": "^4.6.0",
65
- "svelte-sonner": "^1.1.1",
66
- "tailwindcss": "^4.3.2",
67
- "typescript": "^6.0.3",
68
- "vite": "^8.0.16",
69
- "vitest": "^4.1.10"
70
- },
71
- "scripts": {
72
- "build": "svelte-kit sync && svelte-package && publint",
73
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
74
- "test": "pnpm run build && vitest run",
75
- "test:browser": "pnpm run harness:build && node harness/drive.mjs",
76
- "prepublishOnly": "pnpm run build",
77
- "harness:build": "vite build --config harness/vite.config.ts && tailwindcss -i harness/harness.css -o harness/dist/app.css --minify",
78
- "harness:serve": "python3 -m http.server 4180 --directory harness/dist"
79
- },
80
- "packageManager": "pnpm@10.28.0"
2
+ "name": "@poodle64/ui",
3
+ "version": "2026.8.9",
4
+ "description": "Household shared component layer: shadcn-svelte primitives (bits-ui) plus the composed page chrome every app builds its routes from, restyled by each app's @poodle64/design-tokens alias layer. One fix reaches every app.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": "github:poodle64/design-system",
8
+ "publishConfig": {
9
+ "registry": "https://registry.npmjs.org",
10
+ "access": "public"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "registry"
15
+ ],
16
+ "exports": {
17
+ "./package.json": "./package.json",
18
+ "./styles.css": "./dist/styles.css",
19
+ "./utils": {
20
+ "types": "./dist/utils.d.ts",
21
+ "svelte": "./dist/utils.js"
22
+ },
23
+ "./*": {
24
+ "types": "./dist/components/ui/*/index.d.ts",
25
+ "svelte": "./dist/components/ui/*/index.js"
26
+ }
27
+ },
28
+ "peerDependencies": {
29
+ "bits-ui": "^2.18.1",
30
+ "mode-watcher": "^1.1.0",
31
+ "svelte": "^5.33.0",
32
+ "svelte-sonner": "^1.1.1"
33
+ },
34
+ "peerDependenciesMeta": {
35
+ "mode-watcher": {
36
+ "optional": true
37
+ },
38
+ "svelte-sonner": {
39
+ "optional": true
40
+ }
41
+ },
42
+ "dependencies": {
43
+ "@lucide/svelte": "^1.25.0",
44
+ "@tanstack/table-core": "^8.21.3",
45
+ "clsx": "^2.1.1",
46
+ "tailwind-merge": "^3.6.0",
47
+ "tailwind-variants": "^3.2.2",
48
+ "tw-animate-css": "^1.4.0"
49
+ },
50
+ "devDependencies": {
51
+ "@poodle64/design-tokens": "workspace:*",
52
+ "@sveltejs/kit": "^2.63.0",
53
+ "@sveltejs/package": "^2.5.8",
54
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
55
+ "@tailwindcss/cli": "^4.3.2",
56
+ "@testing-library/jest-dom": "^7.0.0",
57
+ "@testing-library/svelte": "^5.4.2",
58
+ "@types/node": "^22.20.1",
59
+ "bits-ui": "^2.18.1",
60
+ "jsdom": "^29.1.1",
61
+ "mode-watcher": "^1.1.0",
62
+ "playwright": "^1.62.0",
63
+ "publint": "^0.3.15",
64
+ "svelte": "^5.56.2",
65
+ "svelte-check": "^4.6.0",
66
+ "svelte-sonner": "^1.1.1",
67
+ "tailwindcss": "^4.3.2",
68
+ "typescript": "^6.0.3",
69
+ "vite": "^8.0.16",
70
+ "vitest": "^4.1.10"
71
+ },
72
+ "scripts": {
73
+ "build": "node scripts/generate-registry.mjs && svelte-kit sync && svelte-package && publint",
74
+ "registry": "node scripts/generate-registry.mjs",
75
+ "registry:check": "node scripts/generate-registry.mjs --check",
76
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
77
+ "test": "pnpm run build && vitest run",
78
+ "test:browser": "pnpm run harness:build && node harness/drive.mjs",
79
+ "prepublishOnly": "pnpm run build",
80
+ "harness:build": "vite build --config harness/vite.config.ts && tailwindcss -i harness/harness.css -o harness/dist/app.css --minify",
81
+ "harness:serve": "python3 -m http.server 4180 --directory harness/dist"
82
+ },
83
+ "packageManager": "pnpm@10.28.0"
81
84
  }
@@ -0,0 +1,356 @@
1
+ {
2
+ "meta": {
3
+ "package": "@poodle64/ui",
4
+ "version": "2026.8.9",
5
+ "generatedBy": "scripts/generate-registry.mjs",
6
+ "source": "scripts/situations.json + package source (DO NOT EDIT the outputs by hand)",
7
+ "componentCount": 49,
8
+ "situationCount": 12
9
+ },
10
+ "situations": [
11
+ {
12
+ "key": "single-figure",
13
+ "title": "A single labelled figure",
14
+ "description": "One headline metric that earns a tile — a figure with a label, an optional unit, and the state of the thing it measures.",
15
+ "components": [
16
+ {
17
+ "name": "StatCard",
18
+ "dir": "stat-card",
19
+ "import": "@poodle64/ui/stat-card",
20
+ "props": "label, value, unit?, sub?, status?, valueTone?, icon?",
21
+ "insteadOf": "a Card you hand-build with a label over a big number"
22
+ }
23
+ ]
24
+ },
25
+ {
26
+ "key": "compact-figures",
27
+ "title": "Several bare figures together",
28
+ "description": "A block of plain figures read at a glance — an 'at a glance' strip — where each number is too small to earn its own StatCard.",
29
+ "components": [
30
+ {
31
+ "name": "StatList",
32
+ "dir": "stat-list",
33
+ "import": "@poodle64/ui/stat-list",
34
+ "props": "items, title?, info?",
35
+ "insteadOf": "a hand-built <dl> or a stack of label/value <div>s"
36
+ }
37
+ ]
38
+ },
39
+ {
40
+ "key": "ranked-comparison",
41
+ "title": "Comparing magnitudes across rows",
42
+ "description": "A small ranked set of labelled bars — a per-lane usage table, a token-burn chart — where a full StatCard per row would be too heavy.",
43
+ "components": [
44
+ {
45
+ "name": "BarRow",
46
+ "dir": "bar-row",
47
+ "import": "@poodle64/ui/bar-row",
48
+ "props": "label, value, pct, color?, labelWidth?",
49
+ "insteadOf": "a <div> with an inline width:%% bar you style by hand"
50
+ }
51
+ ]
52
+ },
53
+ {
54
+ "key": "share-of-a-whole",
55
+ "title": "A share of a bounded whole",
56
+ "description": "A part of a fixed total — a percentage, a capacity, a rate-limit window — read as one 0–100 figure.",
57
+ "components": [
58
+ {
59
+ "name": "ArcGauge",
60
+ "dir": "arc-gauge",
61
+ "import": "@poodle64/ui/arc-gauge",
62
+ "props": "pct, tone?, size?, showLabel?, label?",
63
+ "insteadOf": "a hand-drawn SVG ring, or printing a bare \"72%\""
64
+ }
65
+ ]
66
+ },
67
+ {
68
+ "key": "trend-over-time",
69
+ "title": "A compact trend",
70
+ "description": "An inline time series in a row or card — a trend the eye reads at a glance, not a full Tier-1 chart.",
71
+ "components": [
72
+ {
73
+ "name": "Sparkline",
74
+ "dir": "sparkline",
75
+ "import": "@poodle64/ui/sparkline",
76
+ "props": "series, width, height, pad?",
77
+ "insteadOf": "a hand-built SVG polyline, or reaching for a charting library for a thumbnail"
78
+ }
79
+ ]
80
+ },
81
+ {
82
+ "key": "state-of-a-thing",
83
+ "title": "The state of a thing",
84
+ "description": "The health or status of something. One state reads as a chip; several independent states read as a dot strip.",
85
+ "components": [
86
+ {
87
+ "name": "StatusBadge",
88
+ "dir": "status-badge",
89
+ "import": "@poodle64/ui/status-badge",
90
+ "props": "status, label, pulse?",
91
+ "insteadOf": "a coloured <span> pill you tint by hand"
92
+ },
93
+ {
94
+ "name": "Scorecard",
95
+ "dir": "scorecard",
96
+ "import": "@poodle64/ui/scorecard",
97
+ "props": "scores",
98
+ "insteadOf": "a hand-built row of coloured dots for several on/off/warn checks"
99
+ }
100
+ ]
101
+ },
102
+ {
103
+ "key": "entity-detail",
104
+ "title": "The detail of one entity",
105
+ "description": "The surface for a single selected record — an eyebrow, a title, its status, and a close affordance over the body.",
106
+ "components": [
107
+ {
108
+ "name": "DetailPanel",
109
+ "dir": "detail-panel",
110
+ "import": "@poodle64/ui/detail-panel",
111
+ "props": "title, eyebrow?, status?, statusLabel?, onClose?, children",
112
+ "insteadOf": "a Card you hand-build with an eyebrow, a title row and a close button"
113
+ }
114
+ ]
115
+ },
116
+ {
117
+ "key": "catalogue-of-like-things",
118
+ "title": "A catalogue of like things",
119
+ "description": "An index of many peer items — a responsive tile wall, or a sortable/filterable table of records.",
120
+ "components": [
121
+ {
122
+ "name": "TileGrid",
123
+ "dir": "tile-grid",
124
+ "import": "@poodle64/ui/tile-grid",
125
+ "props": "children, min?, gap?, tag?",
126
+ "insteadOf": "a grid with fixed sm:grid-cols-* breakpoints you maintain per page"
127
+ },
128
+ {
129
+ "name": "DataTableTanstack",
130
+ "dir": "data-table-tanstack",
131
+ "import": "@poodle64/ui/data-table-tanstack",
132
+ "props": "table, selectable?, selectedId?, onSelect?, emptyMessage?",
133
+ "insteadOf": "a hand-built <table>, or the raw Table primitive wired to TanStack by hand"
134
+ },
135
+ {
136
+ "name": "DataTableToolbar",
137
+ "dir": "data-table-toolbar",
138
+ "import": "@poodle64/ui/data-table-toolbar",
139
+ "props": "searchValue, onSearch, searchPlaceholder?, chipGroups?, trailing?",
140
+ "insteadOf": "a hand-built search + filter-chip bar above a table"
141
+ }
142
+ ]
143
+ },
144
+ {
145
+ "key": "sectioning-card",
146
+ "title": "A titled section of a route",
147
+ "description": "The generic titled card every route reaches for — an optional header with icon, subtitle and actions over a body. The default answer to 'I need a card here'.",
148
+ "components": [
149
+ {
150
+ "name": "Panel",
151
+ "dir": "panel",
152
+ "import": "@poodle64/ui/panel",
153
+ "props": "children, title?, subtitle?, icon?, action?, pad?",
154
+ "insteadOf": "raw Card + CardHeader + CardTitle assembled by hand on every route"
155
+ }
156
+ ]
157
+ },
158
+ {
159
+ "key": "page-frame",
160
+ "title": "The frame every route shares",
161
+ "description": "The standing chrome — the app shell and its nav, the one page-title treatment, the persistent right-hand context column.",
162
+ "components": [
163
+ {
164
+ "name": "AppShell",
165
+ "dir": "app-shell",
166
+ "import": "@poodle64/ui/app-shell",
167
+ "props": "nav, currentPath, brandTitle?, actions?, context?, children",
168
+ "insteadOf": "a bespoke nav rail + header layout per app"
169
+ },
170
+ {
171
+ "name": "PageHeader",
172
+ "dir": "page-header",
173
+ "import": "@poodle64/ui/page-header",
174
+ "props": "title?, eyebrow?, breadcrumbs?, subtitle?, info?, actions?",
175
+ "insteadOf": "a hand-written <h1> and title bar (the drift gate fails this)"
176
+ },
177
+ {
178
+ "name": "ContextColumn",
179
+ "dir": "context-column",
180
+ "import": "@poodle64/ui/context-column",
181
+ "props": "stats, statsTitle?, statsInfo?, detail?, ariaLabel?",
182
+ "insteadOf": "a hand-built right-hand <aside> with a stat card and a detail pane"
183
+ }
184
+ ]
185
+ },
186
+ {
187
+ "key": "dialog-and-overlay",
188
+ "title": "A dialog or overlay",
189
+ "description": "A modal task and the pieces around it — the dialog frame, a section within its body, a ⌘K palette, an inline (i) hint.",
190
+ "components": [
191
+ {
192
+ "name": "AppDialog",
193
+ "dir": "app-dialog",
194
+ "import": "@poodle64/ui/app-dialog",
195
+ "props": "title, open, onOpenChange?, size?, children, footer?",
196
+ "insteadOf": "raw Dialog.* parts assembled into a frame by hand"
197
+ },
198
+ {
199
+ "name": "DialogSection",
200
+ "dir": "dialog-section",
201
+ "import": "@poodle64/ui/dialog-section",
202
+ "props": "children, label?",
203
+ "insteadOf": "hand-drawn dividers and section labels inside a dialog body"
204
+ },
205
+ {
206
+ "name": "CommandPalette",
207
+ "dir": "command-palette",
208
+ "import": "@poodle64/ui/command-palette",
209
+ "props": "nav, open, onNavigate?",
210
+ "insteadOf": "a bespoke ⌘K command menu wired to raw Command.*"
211
+ },
212
+ {
213
+ "name": "InfoTip",
214
+ "dir": "info-tip",
215
+ "import": "@poodle64/ui/info-tip",
216
+ "props": "text, side?, children?",
217
+ "insteadOf": "a standing explainer banner, or a raw Tooltip you wire per use"
218
+ }
219
+ ]
220
+ },
221
+ {
222
+ "key": "empty-loading-error",
223
+ "title": "Empty, loading, or failed",
224
+ "description": "A surface with nothing to show yet, still loading, or that failed to load — the three non-happy states every data view needs.",
225
+ "components": [
226
+ {
227
+ "name": "EmptyState",
228
+ "dir": "empty-state",
229
+ "import": "@poodle64/ui/empty-state",
230
+ "props": "title, icon?, description?, action?",
231
+ "insteadOf": "a hand-centred 'Nothing here' message"
232
+ },
233
+ {
234
+ "name": "LoadingState",
235
+ "dir": "loading-state",
236
+ "import": "@poodle64/ui/loading-state",
237
+ "props": "message?",
238
+ "insteadOf": "a bare spinner or 'Loading…' you place by hand"
239
+ },
240
+ {
241
+ "name": "ErrorState",
242
+ "dir": "error-state",
243
+ "import": "@poodle64/ui/error-state",
244
+ "props": "message, action?",
245
+ "insteadOf": "a hand-built error message with a retry button"
246
+ }
247
+ ]
248
+ }
249
+ ],
250
+ "primitives": [
251
+ {
252
+ "dir": "alert",
253
+ "import": "@poodle64/ui/alert"
254
+ },
255
+ {
256
+ "dir": "alert-dialog",
257
+ "import": "@poodle64/ui/alert-dialog"
258
+ },
259
+ {
260
+ "dir": "avatar",
261
+ "import": "@poodle64/ui/avatar"
262
+ },
263
+ {
264
+ "dir": "badge",
265
+ "import": "@poodle64/ui/badge"
266
+ },
267
+ {
268
+ "dir": "button",
269
+ "import": "@poodle64/ui/button"
270
+ },
271
+ {
272
+ "dir": "card",
273
+ "import": "@poodle64/ui/card"
274
+ },
275
+ {
276
+ "dir": "checkbox",
277
+ "import": "@poodle64/ui/checkbox"
278
+ },
279
+ {
280
+ "dir": "command",
281
+ "import": "@poodle64/ui/command"
282
+ },
283
+ {
284
+ "dir": "data-table",
285
+ "import": "@poodle64/ui/data-table"
286
+ },
287
+ {
288
+ "dir": "dialog",
289
+ "import": "@poodle64/ui/dialog"
290
+ },
291
+ {
292
+ "dir": "dropdown-menu",
293
+ "import": "@poodle64/ui/dropdown-menu"
294
+ },
295
+ {
296
+ "dir": "input",
297
+ "import": "@poodle64/ui/input"
298
+ },
299
+ {
300
+ "dir": "input-group",
301
+ "import": "@poodle64/ui/input-group"
302
+ },
303
+ {
304
+ "dir": "label",
305
+ "import": "@poodle64/ui/label"
306
+ },
307
+ {
308
+ "dir": "password-input",
309
+ "import": "@poodle64/ui/password-input"
310
+ },
311
+ {
312
+ "dir": "popover",
313
+ "import": "@poodle64/ui/popover"
314
+ },
315
+ {
316
+ "dir": "progress",
317
+ "import": "@poodle64/ui/progress"
318
+ },
319
+ {
320
+ "dir": "select",
321
+ "import": "@poodle64/ui/select"
322
+ },
323
+ {
324
+ "dir": "separator",
325
+ "import": "@poodle64/ui/separator"
326
+ },
327
+ {
328
+ "dir": "skeleton",
329
+ "import": "@poodle64/ui/skeleton"
330
+ },
331
+ {
332
+ "dir": "sonner",
333
+ "import": "@poodle64/ui/sonner"
334
+ },
335
+ {
336
+ "dir": "switch",
337
+ "import": "@poodle64/ui/switch"
338
+ },
339
+ {
340
+ "dir": "table",
341
+ "import": "@poodle64/ui/table"
342
+ },
343
+ {
344
+ "dir": "tabs",
345
+ "import": "@poodle64/ui/tabs"
346
+ },
347
+ {
348
+ "dir": "textarea",
349
+ "import": "@poodle64/ui/textarea"
350
+ },
351
+ {
352
+ "dir": "tooltip",
353
+ "import": "@poodle64/ui/tooltip"
354
+ }
355
+ ]
356
+ }
@@ -0,0 +1,122 @@
1
+ # @poodle64/ui — situation → component map
2
+
3
+ <!-- GENERATED by scripts/generate-registry.mjs from scripts/situations.json + package source. DO NOT EDIT. -->
4
+ Generated from `@poodle64/ui@2026.8.9`. 49 components, 12 situations.
5
+
6
+ **Read this before writing a `<div>`.** Find the SITUATION you are in below, then compose the component named for it — do not hand-build it from raw `Card` or utility classes. Import is `import { Name } from '<import path>'`. Props marked `?` are optional. This map is the retrieval step the [`frontend-design` skill] makes mandatory; the [CHI 2026 study] measured composing-from-a-registry at 95% design-system compliance against 71% for writing the CSS from a prose style guide.
7
+
8
+ [`frontend-design` skill]: the household rule that loads before any UI code.
9
+ [CHI 2026 study]: 10.1145/3772363.3798616
10
+
11
+ ## A single labelled figure
12
+
13
+ _One headline metric that earns a tile — a figure with a label, an optional unit, and the state of the thing it measures._
14
+
15
+ | Component | Import | Key props | Reach for it instead of |
16
+ | --- | --- | --- | --- |
17
+ | `StatCard` | `@poodle64/ui/stat-card` | `label, value, unit?, sub?, status?, valueTone?, icon?` | a Card you hand-build with a label over a big number |
18
+
19
+ ## Several bare figures together
20
+
21
+ _A block of plain figures read at a glance — an 'at a glance' strip — where each number is too small to earn its own StatCard._
22
+
23
+ | Component | Import | Key props | Reach for it instead of |
24
+ | --- | --- | --- | --- |
25
+ | `StatList` | `@poodle64/ui/stat-list` | `items, title?, info?` | a hand-built <dl> or a stack of label/value <div>s |
26
+
27
+ ## Comparing magnitudes across rows
28
+
29
+ _A small ranked set of labelled bars — a per-lane usage table, a token-burn chart — where a full StatCard per row would be too heavy._
30
+
31
+ | Component | Import | Key props | Reach for it instead of |
32
+ | --- | --- | --- | --- |
33
+ | `BarRow` | `@poodle64/ui/bar-row` | `label, value, pct, color?, labelWidth?` | a <div> with an inline width:%% bar you style by hand |
34
+
35
+ ## A share of a bounded whole
36
+
37
+ _A part of a fixed total — a percentage, a capacity, a rate-limit window — read as one 0–100 figure._
38
+
39
+ | Component | Import | Key props | Reach for it instead of |
40
+ | --- | --- | --- | --- |
41
+ | `ArcGauge` | `@poodle64/ui/arc-gauge` | `pct, tone?, size?, showLabel?, label?` | a hand-drawn SVG ring, or printing a bare "72%" |
42
+
43
+ ## A compact trend
44
+
45
+ _An inline time series in a row or card — a trend the eye reads at a glance, not a full Tier-1 chart._
46
+
47
+ | Component | Import | Key props | Reach for it instead of |
48
+ | --- | --- | --- | --- |
49
+ | `Sparkline` | `@poodle64/ui/sparkline` | `series, width, height, pad?` | a hand-built SVG polyline, or reaching for a charting library for a thumbnail |
50
+
51
+ ## The state of a thing
52
+
53
+ _The health or status of something. One state reads as a chip; several independent states read as a dot strip._
54
+
55
+ | Component | Import | Key props | Reach for it instead of |
56
+ | --- | --- | --- | --- |
57
+ | `StatusBadge` | `@poodle64/ui/status-badge` | `status, label, pulse?` | a coloured <span> pill you tint by hand |
58
+ | `Scorecard` | `@poodle64/ui/scorecard` | `scores` | a hand-built row of coloured dots for several on/off/warn checks |
59
+
60
+ ## The detail of one entity
61
+
62
+ _The surface for a single selected record — an eyebrow, a title, its status, and a close affordance over the body._
63
+
64
+ | Component | Import | Key props | Reach for it instead of |
65
+ | --- | --- | --- | --- |
66
+ | `DetailPanel` | `@poodle64/ui/detail-panel` | `title, eyebrow?, status?, statusLabel?, onClose?, children` | a Card you hand-build with an eyebrow, a title row and a close button |
67
+
68
+ ## A catalogue of like things
69
+
70
+ _An index of many peer items — a responsive tile wall, or a sortable/filterable table of records._
71
+
72
+ | Component | Import | Key props | Reach for it instead of |
73
+ | --- | --- | --- | --- |
74
+ | `TileGrid` | `@poodle64/ui/tile-grid` | `children, min?, gap?, tag?` | a grid with fixed sm:grid-cols-* breakpoints you maintain per page |
75
+ | `DataTableTanstack` | `@poodle64/ui/data-table-tanstack` | `table, selectable?, selectedId?, onSelect?, emptyMessage?` | a hand-built <table>, or the raw Table primitive wired to TanStack by hand |
76
+ | `DataTableToolbar` | `@poodle64/ui/data-table-toolbar` | `searchValue, onSearch, searchPlaceholder?, chipGroups?, trailing?` | a hand-built search + filter-chip bar above a table |
77
+
78
+ ## A titled section of a route
79
+
80
+ _The generic titled card every route reaches for — an optional header with icon, subtitle and actions over a body. The default answer to 'I need a card here'._
81
+
82
+ | Component | Import | Key props | Reach for it instead of |
83
+ | --- | --- | --- | --- |
84
+ | `Panel` | `@poodle64/ui/panel` | `children, title?, subtitle?, icon?, action?, pad?` | raw Card + CardHeader + CardTitle assembled by hand on every route |
85
+
86
+ ## The frame every route shares
87
+
88
+ _The standing chrome — the app shell and its nav, the one page-title treatment, the persistent right-hand context column._
89
+
90
+ | Component | Import | Key props | Reach for it instead of |
91
+ | --- | --- | --- | --- |
92
+ | `AppShell` | `@poodle64/ui/app-shell` | `nav, currentPath, brandTitle?, actions?, context?, children` | a bespoke nav rail + header layout per app |
93
+ | `PageHeader` | `@poodle64/ui/page-header` | `title?, eyebrow?, breadcrumbs?, subtitle?, info?, actions?` | a hand-written <h1> and title bar (the drift gate fails this) |
94
+ | `ContextColumn` | `@poodle64/ui/context-column` | `stats, statsTitle?, statsInfo?, detail?, ariaLabel?` | a hand-built right-hand <aside> with a stat card and a detail pane |
95
+
96
+ ## A dialog or overlay
97
+
98
+ _A modal task and the pieces around it — the dialog frame, a section within its body, a ⌘K palette, an inline (i) hint._
99
+
100
+ | Component | Import | Key props | Reach for it instead of |
101
+ | --- | --- | --- | --- |
102
+ | `AppDialog` | `@poodle64/ui/app-dialog` | `title, open, onOpenChange?, size?, children, footer?` | raw Dialog.* parts assembled into a frame by hand |
103
+ | `DialogSection` | `@poodle64/ui/dialog-section` | `children, label?` | hand-drawn dividers and section labels inside a dialog body |
104
+ | `CommandPalette` | `@poodle64/ui/command-palette` | `nav, open, onNavigate?` | a bespoke ⌘K command menu wired to raw Command.* |
105
+ | `InfoTip` | `@poodle64/ui/info-tip` | `text, side?, children?` | a standing explainer banner, or a raw Tooltip you wire per use |
106
+
107
+ ## Empty, loading, or failed
108
+
109
+ _A surface with nothing to show yet, still loading, or that failed to load — the three non-happy states every data view needs._
110
+
111
+ | Component | Import | Key props | Reach for it instead of |
112
+ | --- | --- | --- | --- |
113
+ | `EmptyState` | `@poodle64/ui/empty-state` | `title, icon?, description?, action?` | a hand-centred 'Nothing here' message |
114
+ | `LoadingState` | `@poodle64/ui/loading-state` | `message?` | a bare spinner or 'Loading…' you place by hand |
115
+ | `ErrorState` | `@poodle64/ui/error-state` | `message, action?` | a hand-built error message with a retry button |
116
+
117
+ ## Primitives you already know
118
+
119
+ _Standard shadcn-svelte / bits-ui primitives — reach for them the same way; no situation lookup needed. Import each from its path below._
120
+
121
+ `@poodle64/ui/alert` · `@poodle64/ui/alert-dialog` · `@poodle64/ui/avatar` · `@poodle64/ui/badge` · `@poodle64/ui/button` · `@poodle64/ui/card` · `@poodle64/ui/checkbox` · `@poodle64/ui/command` · `@poodle64/ui/data-table` · `@poodle64/ui/dialog` · `@poodle64/ui/dropdown-menu` · `@poodle64/ui/input` · `@poodle64/ui/input-group` · `@poodle64/ui/label` · `@poodle64/ui/password-input` · `@poodle64/ui/popover` · `@poodle64/ui/progress` · `@poodle64/ui/select` · `@poodle64/ui/separator` · `@poodle64/ui/skeleton` · `@poodle64/ui/sonner` · `@poodle64/ui/switch` · `@poodle64/ui/table` · `@poodle64/ui/tabs` · `@poodle64/ui/textarea` · `@poodle64/ui/tooltip`
122
+