@tutti-os/ui-system 0.0.1

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/AGENTS.md +146 -0
  2. package/LICENSE +202 -0
  3. package/README.md +97 -0
  4. package/agent/install-skill.mjs +241 -0
  5. package/agent/nextop-ui-system/SKILL.md +227 -0
  6. package/agent/nextop-ui-system/references/extract-base-component.md +87 -0
  7. package/agent/nextop-ui-system/references/maintain-inventory.md +45 -0
  8. package/agent/nextop-ui-system/references/promote-business-component.md +316 -0
  9. package/agent/nextop-ui-system/references/use-existing-component.md +37 -0
  10. package/agent/nextop-ui-system/scripts/create-business-preview.mjs +658 -0
  11. package/dist/chunk-2AUYRRDG.js +3078 -0
  12. package/dist/chunk-2AUYRRDG.js.map +1 -0
  13. package/dist/chunk-DGPY4WP3.js +11 -0
  14. package/dist/chunk-DGPY4WP3.js.map +1 -0
  15. package/dist/chunk-GX3U3V36.js +70 -0
  16. package/dist/chunk-GX3U3V36.js.map +1 -0
  17. package/dist/chunk-UTUVPSKL.js +873 -0
  18. package/dist/chunk-UTUVPSKL.js.map +1 -0
  19. package/dist/chunk-XHA7R2WC.js +292 -0
  20. package/dist/chunk-XHA7R2WC.js.map +1 -0
  21. package/dist/components/index.d.ts +360 -0
  22. package/dist/components/index.js +221 -0
  23. package/dist/components/index.js.map +1 -0
  24. package/dist/date-format.d.ts +6 -0
  25. package/dist/date-format.js +11 -0
  26. package/dist/date-format.js.map +1 -0
  27. package/dist/dev-vite.d.ts +9 -0
  28. package/dist/dev-vite.js +583 -0
  29. package/dist/dev-vite.js.map +1 -0
  30. package/dist/icons/index.d.ts +112 -0
  31. package/dist/icons/index.js +191 -0
  32. package/dist/icons/index.js.map +1 -0
  33. package/dist/index.d.ts +13 -0
  34. package/dist/index.js +419 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/metadata/components.json +2819 -0
  37. package/dist/metadata/components.schema.json +110 -0
  38. package/dist/metadata/index.d.ts +27 -0
  39. package/dist/metadata/index.js +2827 -0
  40. package/dist/metadata/index.js.map +1 -0
  41. package/dist/styles/base.css +170 -0
  42. package/dist/styles/index.css +4 -0
  43. package/dist/styles/semantic.css +50 -0
  44. package/dist/styles/theme.css +366 -0
  45. package/dist/utils.d.ts +5 -0
  46. package/dist/utils.js +7 -0
  47. package/dist/utils.js.map +1 -0
  48. package/package.json +118 -0
  49. package/ui-system.md +671 -0
package/ui-system.md ADDED
@@ -0,0 +1,671 @@
1
+ # UI System
2
+
3
+ This document defines the responsibility boundary for the shared visual-system package under `packages/ui/*`.
4
+
5
+ ## Purpose
6
+
7
+ The shared UI system package exists to hold product-facing visual foundations that are reused across renderer surfaces.
8
+
9
+ The current package is:
10
+
11
+ - `packages/ui/system`
12
+
13
+ It is organized into two public component layers:
14
+
15
+ - `base`
16
+ Basic visual primitives and foundations such as tokens, icons, Button,
17
+ Input, Dialog, Select, Card, Badge, and Toast.
18
+ - `business`
19
+ Multi-end reusable Nextop business display components. These components may
20
+ expose domain display props such as workspace, file, task, or agent state, and
21
+ should compose `base` primitives instead of recreating them.
22
+
23
+ The package owns:
24
+
25
+ - design tokens
26
+ - shared theme styles
27
+ - icon exports
28
+ - presentation primitives
29
+ - reusable business display components
30
+
31
+ It does not own:
32
+
33
+ - business logic or host-side side effects
34
+ - app-specific workflows
35
+ - daemon, Electron, router, or host adapter calls
36
+
37
+ ## Current Package Role
38
+
39
+ `@tutti-os/ui-system` is the single source of truth for:
40
+
41
+ - CSS token definitions
42
+ - structural shared workbench CSS in `packages/workbench/surface/src/styles/workbench.css`
43
+ - structural shared terminal CSS in `packages/workspace/terminal/src/styles/terminal.css`
44
+ - Tailwind-facing semantic theme variables
45
+ - shared SVG and icon APIs
46
+ - shadcn-derived React primitives
47
+
48
+ Desktop renderer code should consume this package instead of defining a second token or primitive layer in `apps/desktop`.
49
+
50
+ The visual language that this package should serve is defined in [Desktop Visual Language](../../../docs/conventions/desktop-visual-language.md).
51
+
52
+ ## Public API
53
+
54
+ `@tutti-os/ui-system` should expose a small, stable surface:
55
+
56
+ - `@tutti-os/ui-system`
57
+ Root runtime entry for shared primitives, icon components, and the small set of utility exports that primitives depend on
58
+ - `@tutti-os/ui-system/styles.css`
59
+ Shared stylesheet entry loaded by renderer shells
60
+ - `@tutti-os/ui-system/components`
61
+ Stable component barrel for tooling and rare category-focused imports
62
+ - `@tutti-os/ui-system/icons`
63
+ Stable icon barrel for tooling and rare category-focused imports
64
+ - `@tutti-os/ui-system/utils`
65
+ Stable utility entry for shadcn CLI integration and primitive support code
66
+ - `@tutti-os/ui-system/metadata`
67
+ Tooling entry for component metadata used by storyboard, dev server, and
68
+ agent skills
69
+ - `@tutti-os/ui-system/dev-vite`
70
+ Development-tooling entry for external Vite apps that opt into local source
71
+ sync; this is not a runtime component API
72
+
73
+ Default consumption rules:
74
+
75
+ - application code should prefer importing primitives and icons from `@tutti-os/ui-system`
76
+ - renderer entrypoints should import `@tutti-os/ui-system/styles.css` once
77
+ - shadcn monorepo aliases may target `@tutti-os/ui-system/components` and `@tutti-os/ui-system/utils`
78
+ - application runtime code must not import `@tutti-os/ui-system/dev-vite`
79
+ - consumers must not deep import `@tutti-os/ui-system/src/*` or component file
80
+ paths; use the root package and stable subpaths instead
81
+
82
+ ## Automated Enforcement
83
+
84
+ The repository enforces the shared UI boundary with:
85
+
86
+ - `pnpm check:ui-boundaries`
87
+ - `pnpm check:ui-boundaries:staged`
88
+
89
+ Use the script output as the source of truth for the mechanically-checkable rules.
90
+
91
+ - `check:ui-boundaries:staged` is the fast local hook variant for staged files
92
+ - `check:ui-boundaries` is the full-repository variant for `pre-push` and CI
93
+ - `@tutti-os/workbench-surface/styles.css`, `@tutti-os/workspace-terminal/styles.css`, and `@tutti-os/agent-gui/styles.css` are the only non-UI-system package stylesheets allowed by the boundary check
94
+ - `@tutti-os/workbench-surface/styles.css` and `@tutti-os/workspace-terminal/styles.css` should remain structural and variable-driven, not product-branded
95
+ - `@tutti-os/agent-gui/styles.css` is a deliberate package contract for the carried agent GUI and workspace-agent panel selectors that still rely on package-owned class names
96
+
97
+ Keep this check aligned with the package exports and file boundary rules:
98
+
99
+ - if a new stable public subpath is intentionally added, update both `packages/ui/system/package.json` and the import-check script
100
+ - do not "fix" the script by broadening the allowed list unless the package boundary itself is intentionally changing
101
+
102
+ ## Metadata Rules
103
+
104
+ Every public UI-system metadata entry must have a stable `id`.
105
+
106
+ - `id` is the user-facing tooling identifier for storyboard anchors, dev server
107
+ metadata, and agent skill lookups
108
+ - `id` must be globally unique and use readable kebab-case, for example
109
+ `button`, `dialog-content`, `button-variants`, or `styles-css`
110
+ - `id` should not be derived at runtime by consumers; read it from
111
+ `@tutti-os/ui-system/metadata`
112
+ - `name` and `export` remain the TypeScript API identity, while `id` is the
113
+ stable human-readable inventory identity
114
+ - `layer` must be either `base` or `business`
115
+ - `business` components may use business display nouns in their props, but
116
+ should still receive data, labels, status, and callbacks from the consuming
117
+ host
118
+
119
+ ## Component Promotion Protocol
120
+
121
+ Use this protocol when moving UI from an application or business package into
122
+ `@tutti-os/ui-system`.
123
+
124
+ The UI system follows a shadcn-like self-owned source model:
125
+
126
+ - Radix or equivalent headless primitives provide accessible interaction
127
+ behavior where a proven primitive exists
128
+ - CVA-style variant definitions keep component variants explicit and typed
129
+ - Tailwind classes consume shared semantic tokens instead of local palettes
130
+ - Nextop owns the checked-in source, public exports, metadata, storyboard
131
+ examples, and boundary validation
132
+
133
+ Before extracting a component, decide whether the target is `base`, `business`,
134
+ or not suitable for UI-system promotion.
135
+
136
+ Promote to `base` when the component is a foundation primitive:
137
+
138
+ - it has no product workflow or domain noun in its public contract
139
+ - props describe presentation, interaction, accessibility, variants, refs,
140
+ slots, class names, or children
141
+ - the component can be reused by unrelated surfaces without explaining a
142
+ business concept
143
+ - a known shadcn or Radix primitive can provide the starting point, or the
144
+ exception is documented in review
145
+
146
+ Promote to `business` when the component is a reusable business display unit:
147
+
148
+ - it represents a cross-surface Nextop concept such as workspace, file, task,
149
+ agent, run, project, or account display state
150
+ - it receives all business data, labels, statuses, permissions, and callbacks
151
+ from the host through props
152
+ - it receives user-visible copy through props, labels, or children instead of
153
+ introducing new hardcoded product strings inside the shared component body
154
+ - it composes `base` components for buttons, fields, dialogs, cards, icons, and
155
+ overlays instead of rebuilding those primitives locally
156
+ - it can be rendered in storyboard with controlled sample data and no daemon,
157
+ router, store, or host adapter
158
+
159
+ Business promotion uses a copy-first workflow. Move the existing application
160
+ component structure into `@tutti-os/ui-system` as intact as possible, preserve
161
+ the source DOM hierarchy, visual states, and interaction layout, then remove
162
+ host-owned dependencies and standardize the API incrementally. Do not start by
163
+ inventing a cleaner abstraction or new visual treatment. Storyboard should first
164
+ prove source/screenshot parity; only then should props be generalized into
165
+ stable data, labels, actions, variants, and slots.
166
+
167
+ Promotion is not a redesign. The promoted UI must preserve the original design
168
+ unless the user explicitly approves a visual change. Do not add decoration,
169
+ layout chrome, controls, icons, copy, motion, states, spacing, or hierarchy that
170
+ cannot be traced to the source component or source screenshot. UI-system tokens
171
+ and primitives should replace local styling only when they keep the same
172
+ observed design and interaction path.
173
+
174
+ Copy-first promotion includes the candidate's dependent display subcomponents
175
+ and third-party-library wrappers. Pure presentational helpers should migrate
176
+ with the business component. Reusable wrappers around Radix, floating UI,
177
+ resizable panels, drag/drop, virtualization, editor shells, or similar libraries
178
+ should become or reuse `base` primitives before the business component composes
179
+ them. Host-coupled children must be split into caller-owned data, labels,
180
+ callbacks, or slots rather than rewritten from memory inside the promoted
181
+ component.
182
+
183
+ Do not promote a component when it owns:
184
+
185
+ - daemon, Electron, filesystem, router, or host-adapter calls
186
+ - data fetching, cache mutation, persistence, or polling
187
+ - global app store ownership
188
+ - workspace registration, navigation, onboarding, or other app workflow
189
+ orchestration
190
+ - product copy that cannot be supplied by props or children
191
+
192
+ For every promoted component, define this contract before editing code:
193
+
194
+ - component `id` and `layer`
195
+ - source usage being replaced
196
+ - intended reuse surfaces
197
+ - public props and callbacks
198
+ - host-owned state and side effects that remain outside the component
199
+ - stable export path
200
+ - metadata entry
201
+ - storyboard states and examples
202
+ - validation commands
203
+
204
+ The bundled `packages/ui/system/agent/nextop-ui-system/SKILL.md` skill is the
205
+ standard prompt-level workflow for this judgment-heavy promotion step. Boundary
206
+ scripts are still required, but they only catch mechanical violations.
207
+
208
+ ## API Shape And Composition
209
+
210
+ The public API must be derived from the source state matrix and intended reuse
211
+ surfaces, not from every conditional branch in the original component.
212
+
213
+ - Avoid boolean prop proliferation for rendering modes. Standard UI booleans
214
+ such as `disabled`, `loading`, `selected`, `open`, `required`, or `invalid`
215
+ are acceptable when they represent real state. Mode switches such as `isFoo`,
216
+ `showBar`, and `withBaz` should usually become a finite variant,
217
+ discriminated union, explicit component variant, slot, or composed child.
218
+ - Use explicit variants when combinations would otherwise create impossible
219
+ states. Prefer a narrow discriminated union or named component variant over a
220
+ component that accepts unrelated mode booleans.
221
+ - Prefer `children` and named slots for caller-owned visual regions. Use render
222
+ props only when the shared component must pass data back to the caller.
223
+ - User-visible copy must stay caller-owned by default. Do not add hardcoded
224
+ product strings inside `@tutti-os/ui-system` components when the text can be
225
+ supplied by `labels`, `title`, `description`, `children`, or other explicit
226
+ props. If a legacy-compatible fallback string must exist temporarily, treat it
227
+ as compatibility debt rather than the preferred API pattern.
228
+ - Use compound components and context only when the component is complex enough
229
+ that consumers need to assemble subparts while sharing state. Do not add a
230
+ provider for a simple card, row, badge, or button wrapper.
231
+ - If shared state is necessary, make the context contract narrow and
232
+ injectable: `state`, `actions`, and `meta`. State implementation remains
233
+ outside the visual subcomponents, and host-owned daemon, Electron, router,
234
+ store, query, persistence, filesystem, or workflow behavior remains outside
235
+ the UI-system package.
236
+ - For new components, follow the repository's React 19 baseline, including
237
+ `ref` as a prop where a ref is part of the public contract. Do not rewrite
238
+ shadcn or Radix-acquired components only to chase API style parity.
239
+
240
+ Document the API shape decision before promotion: which state axes became
241
+ variants, props, slots, children, compound subcomponents, provider state, or
242
+ host-owned caller logic.
243
+
244
+ ## Token Rules
245
+
246
+ - CSS variables are the source of truth for theme values
247
+ - Tailwind utilities should consume the same token layer rather than defining a parallel color system
248
+ - prefer semantic token names such as `background`, `foreground`, `primary`, `muted`, and `destructive` over raw palette leakage in public APIs
249
+ - keep nextop-specific token extensions additive and minimal
250
+ - Build primitives for a calm workbench shell, not for marketing-card
251
+ theatrics.
252
+
253
+ For cross-surface stacking, use shared semantic `z-index` tokens instead of
254
+ local magic numbers. Current global layer tokens live in
255
+ `packages/ui/system/src/styles/theme.css` and should be the default source of
256
+ truth for:
257
+
258
+ - workbench chrome overlays
259
+ - popovers and menus
260
+ - toasts
261
+ - full-panel overlays
262
+ - dialogs and their backdrops
263
+
264
+ When a surface needs a new global layer, add a responsibility-named token to the
265
+ shared theme rather than introducing another raw `z-[12345]` value in app code.
266
+ Small component-internal stacking such as `z-1` on a pseudo-element can stay
267
+ local when it does not participate in global overlay ordering.
268
+
269
+ ### Z-Index Design Rules
270
+
271
+ Treat `z-index` as an ordering system, not as a per-component escape hatch.
272
+
273
+ Use these rules:
274
+
275
+ - use shared global tokens when a layer can overlap content owned by another component, feature, portal, or renderer surface
276
+ - use package-local or component-local variables when the layer only needs to order parts inside one isolated surface
277
+ - keep local decorative layering simple; values such as `0`, `1`, `2`, or `3` are acceptable when they never compete with global overlays
278
+ - do not introduce new raw high values such as `9999`, `10000`, or `z-[12345]` in app code
279
+ - prefer a new responsibility-named token over “one bigger number” when an existing global layer is not sufficient
280
+
281
+ Questions to ask before adding or changing a layer:
282
+
283
+ 1. Can this element ever overlap a portal, popover, toast, dialog, or another feature-owned overlay?
284
+ 2. Is this a global interaction layer or only internal ordering inside one component?
285
+ 3. Would another engineer understand the layer’s purpose from its token name alone?
286
+
287
+ If the answer to the first question is yes, the layer should almost always use a
288
+ shared global token.
289
+
290
+ ### Current Global Layers
291
+
292
+ The current shared global `z-index` tokens are:
293
+
294
+ - `--z-workbench-chrome`
295
+ Top or bottom workbench chrome rendered above window content but below global popovers and dialogs.
296
+ - `--z-workbench-genie`
297
+ The genie animation layer that must stay above ordinary workbench chrome.
298
+ - `--z-popover`
299
+ Cross-feature floating UI such as menus, switchers, and layout popovers.
300
+ - `--z-toast`
301
+ Toast notifications that should stay above popovers.
302
+ - `--z-panel`
303
+ Full-panel overlays such as workspace settings surfaces.
304
+ - `--z-panel-popover`
305
+ Popovers or menus that are portaled from within a full-panel overlay and must stay above the panel but below dialog backdrops.
306
+ - `--z-dialog-overlay`
307
+ Dialog backdrops that should dim or block panel surfaces beneath them.
308
+ - `--z-dialog`
309
+ Dialog content rendered above dialog backdrops.
310
+ - `--z-tooltip`
311
+ Short hover/focus guidance that should stay above panels, drawers, and their popovers so clipped text can be inspected across overlay boundaries.
312
+
313
+ These tokens are intentionally semantic and ordered by responsibility, not by
314
+ visual implementation detail.
315
+
316
+ ### Local Layering Rules
317
+
318
+ Local layers should stay local when they only solve ordering inside one bounded
319
+ surface. Good examples:
320
+
321
+ - a selected tile border inside one settings grid
322
+ - a resize handle above adjacent pane content inside one file manager surface
323
+ - a tooltip above its own dock icon inside one workbench dock
324
+ - background decorations behind launcher content
325
+
326
+ For these cases:
327
+
328
+ - prefer package-local variables such as `--workbench-z-dock-tooltip` or `--workspace-file-manager-dialog-overlay-z-index`
329
+ - keep the numeric scale tight and relative to the owning surface
330
+ - do not promote a local layer into the global theme unless another surface needs to reason about it
331
+
332
+ ### Migration Rules
333
+
334
+ When touching existing code:
335
+
336
+ - replace raw high `z-index` values with the nearest existing semantic token when the layer is globally meaningful
337
+ - if no existing token matches, add a new one in `packages/ui/system/src/styles/theme.css` and document it here
338
+ - if the layer is local-only, prefer a package or component variable instead of a new global token
339
+ - remove transitional duplicate declarations such as a Tailwind `z-*` class plus an overriding inline `style.zIndex`
340
+
341
+ ## Reusable Package Styling Rules
342
+
343
+ Reusable packages outside `packages/ui/*` should not create their own visual
344
+ systems. They should consume `@tutti-os/ui-system` primitives, icons, tokens, and
345
+ token-backed Tailwind utilities as their default styling model.
346
+
347
+ For reusable packages that render Tailwind utility classes, consumers are
348
+ responsible for including the published package output in Tailwind source
349
+ scanning. For example, a consumer of `@tutti-os/workbench-surface` should include
350
+ that package's built output in its Tailwind entrypoint or equivalent build
351
+ configuration:
352
+
353
+ ```css
354
+ @source "../node_modules/@tutti-os/workbench-surface/dist";
355
+ ```
356
+
357
+ Within this monorepo, reusable packages that require consumer Tailwind scanning
358
+ should declare that requirement in their `package.json`:
359
+
360
+ ```json
361
+ {
362
+ "nextop": {
363
+ "tailwindSourceRoot": "src"
364
+ }
365
+ }
366
+ ```
367
+
368
+ `pnpm check:ui-boundaries` validates that the desktop renderer entrypoint
369
+ `apps/desktop/src/renderer/src/style.css` includes matching `@source` directives
370
+ for any imported workspace package that declares `nextop.tailwindSourceRoot`.
371
+ It also validates that the path matches the declared source root and reports the
372
+ exact `@source` line to add or replace.
373
+
374
+ Tailwind source troubleshooting checklist:
375
+
376
+ - if a reusable package introduces new utility classes but the desktop UI does not change at runtime, confirm the package declares `nextop.tailwindSourceRoot` when it renders runtime Tailwind classes
377
+ - confirm the desktop renderer Tailwind entrypoint includes the package source path through `@source`
378
+ - re-run `pnpm check:ui-boundaries` before assuming the issue is a hot-reload or build-cache problem
379
+
380
+ Package-local CSS in reusable packages is an exception, not the default. Add it
381
+ only when the package needs selectors, keyframes, or structural behavior that is
382
+ awkward to express through UI system primitives and Tailwind utilities.
383
+
384
+ When package-local CSS is necessary:
385
+
386
+ - use UI system CSS variables or Tailwind-facing semantic tokens
387
+ - keep the CSS structural and package-responsibility-specific
388
+ - do not define raw palette values, a second token layer, or app-specific visual roles
389
+ - do not include product styling, product copy, or app-specific state concepts
390
+ - document the public stylesheet entrypoint in the package README and package release docs
391
+
392
+ If a reusable package repeatedly needs visual primitives or new semantic tokens,
393
+ prefer moving that foundation into `@tutti-os/ui-system` before adding more
394
+ package-local CSS.
395
+
396
+ ## Design Foundation Compliance
397
+
398
+ All components promoted into `@tutti-os/ui-system` must fully follow the
399
+ existing design foundations owned by this package. Promotion must align with the
400
+ shared token model, theme variables, spacing rhythm, radius scale, typography,
401
+ surface language, focus states, disabled states, and the existing `base`
402
+ primitive vocabulary.
403
+
404
+ Do not introduce a second visual language during promotion. Avoid raw palette
405
+ values, ad hoc spacing scales, local radius conventions, duplicate button or
406
+ field treatments, or component-specific CSS that should be expressed through
407
+ existing tokens or primitives.
408
+
409
+ After a component is promoted, start an independent design-foundation review
410
+ subagent before reporting completion. Give the subagent the promoted component
411
+ files, source usage, selected states, storyboard entry, metadata entry, and this
412
+ document. The subagent should verify that the component follows the design
413
+ foundation and report any drift. If a subagent cannot be started in the current
414
+ environment, report the verification as blocked instead of claiming full
415
+ design-foundation compliance.
416
+
417
+ ## Component Rules
418
+
419
+ - `base` primitives should stay low-level and presentation-focused
420
+ - `business` components may include reusable business display semantics, but
421
+ must stay host-agnostic and side-effect-free
422
+ - `packages/ui/system` is the repository's shared Radix and shadcn host package
423
+ - for primitives that exist in the shadcn registry, start from shadcn CLI output targeted at `packages/ui/system`; do not hand-author a fresh component body when the upstream primitive can be downloaded
424
+ - keep `packages/ui/system/components.json` healthy enough that `pnpm dlx shadcn@latest add <component> -c packages/ui/system` remains the default acquisition path
425
+ - treat CLI-generated source as the canonical starting point; repository-specific edits should stay narrow and mechanical, such as package import aliases, stable barrel exports, icon-layer routing, and token-backed class adjustments required by boundary checks
426
+ - if a desired primitive is not available from shadcn, prefer composing directly from `radix-ui` inside `packages/ui/system` and document that exception in the change review or follow-up docs
427
+ - if the current package structure makes CLI acquisition awkward, fix the host package structure or configuration first instead of silently replacing the workflow with a handwritten primitive
428
+ - keep primitive APIs close to upstream shadcn patterns unless product-specific constraints require a deviation
429
+ - do not place app-specific workflows such as launcher flows, workspace
430
+ registration, or route-owned panels in the shared package
431
+ - do not add new hardcoded user-visible copy inside `@tutti-os/ui-system`
432
+ components when the text can be supplied by props, `labels`, `title`,
433
+ `description`, or `children`; keep translation lookup and copy selection in
434
+ the caller
435
+ - export UI-system components through stable package barrels instead of exposing
436
+ per-file component paths
437
+ - only move a component into `@tutti-os/ui-system` when it is a real
438
+ visual-system primitive or reusable business display component with more than
439
+ one plausible consumer
440
+ - every public component, icon, utility, style entry, or tooling-visible UI
441
+ export must have metadata in
442
+ `packages/ui/system/src/metadata/components.json`
443
+ - metadata `source` paths must point at existing files under `packages/ui/system/src`
444
+ and `from` must use a stable public entrypoint
445
+ - run `node tools/scripts/check-ui-metadata.mjs` or
446
+ `pnpm check:ui-boundaries` after adding, removing, or renaming UI-system
447
+ public exports
448
+
449
+ ### Primitive Sourcing Workflow
450
+
451
+ Use this workflow when adding or replacing a shared primitive:
452
+
453
+ 1. Run shadcn CLI against `packages/ui/system` when the primitive exists in the registry.
454
+ 2. Keep the downloaded component body as the baseline implementation.
455
+ 3. Apply only the minimum package-specific adaptation required to satisfy repository rules.
456
+ 4. Export the primitive through the stable `@tutti-os/ui-system` barrels.
457
+ 5. Re-run `pnpm check:ui-boundaries` after the adaptation pass.
458
+
459
+ Repository-specific adaptation is allowed for:
460
+
461
+ - replacing direct third-party icon imports with `@tutti-os/ui-system` icon exports when the UI boundary check requires it
462
+ - switching import aliases to package-local `#components`, `#icons`, or `#lib` paths
463
+ - aligning classes with shared CSS tokens or other repository-owned boundary rules
464
+
465
+ Repository-specific adaptation is not a reason to skip CLI acquisition. The rule of thumb is:
466
+
467
+ - download first
468
+ - adapt second
469
+ - do not handwrite the primitive body from scratch unless there is no upstream shadcn primitive to start from
470
+
471
+ ### Business Component Workflow
472
+
473
+ Use this workflow when promoting reusable business UI:
474
+
475
+ 1. Read existing `@tutti-os/ui-system/metadata` and storyboard entries first.
476
+ 2. Reuse existing `base` and `business` components when they cover the need.
477
+ 3. Keep host state, side effects, data loading, routing, and daemon calls in the
478
+ original app or package.
479
+ 4. Extract only the reusable display surface and typed callback contract.
480
+ 5. Compose `base` primitives for controls, overlays, cards, icons, and layout
481
+ affordances.
482
+ 6. Add metadata with `layer: "business"` and a readable stable `id`.
483
+ 7. Add storyboard examples that cover empty, loading, normal, disabled, and
484
+ error-like display states when those states are part of the public contract.
485
+ 8. Replace the original duplicated UI with a stable public import from
486
+ `@tutti-os/ui-system`.
487
+
488
+ Business components should look like controlled React components:
489
+
490
+ ```tsx
491
+ <WorkspaceSummaryCard
492
+ workspace={workspace}
493
+ status={workspaceStatus}
494
+ disabled={!canOpenWorkspace}
495
+ onOpen={handleOpenWorkspace}
496
+ />
497
+ ```
498
+
499
+ They should not reach back into the host:
500
+
501
+ ```tsx
502
+ useWorkspaceStore();
503
+ useNavigate();
504
+ invokeDaemon();
505
+ fetch("/api/workspaces");
506
+ localStorage.setItem("workspace", id);
507
+ ```
508
+
509
+ ## Promotion Review Gate
510
+
511
+ Use this gate for every base or business component promotion. It adapts
512
+ frontend design review practice to Nextop's product standard; do not import
513
+ general marketing, portfolio, or decorative frontend heuristics into the shared
514
+ workbench system.
515
+
516
+ ### Frictionless
517
+
518
+ - The migrated consumer preserves the original task path and interaction count
519
+ unless the user approved a behavior change.
520
+ - Primary, secondary, destructive, cancel, and recovery actions keep their
521
+ visual hierarchy and remain reachable by keyboard.
522
+ - The shared component does not introduce dead ends, hidden required steps, or
523
+ new caller-owned state requirements.
524
+
525
+ ### Quality Craft
526
+
527
+ - Selected states have before/after visual parity evidence from the source
528
+ route, storyboard, or smallest reproducible view.
529
+ - The implementation uses existing `@tutti-os/ui-system` primitives and
530
+ canonical tokens before adding component-local CSS.
531
+ - Light, dark, focus-visible, hover, disabled, invalid, selected, loading, and
532
+ reduced-motion states are covered when they exist in the public contract.
533
+ - Layout, density, spacing, radius, typography, icon sizing, border, color,
534
+ opacity, shadow, and responsive behavior have no unapproved drift.
535
+ - Any intentional delta is named, justified, and reported as approved rather
536
+ than hidden inside the promotion.
537
+
538
+ ### Trustworthy
539
+
540
+ - Loading, empty, disabled, error-like, and permission-limited states keep clear
541
+ labels and actionable recovery where the source had them.
542
+ - AI-generated or inferred content keeps provenance, confidence, or disclaimer
543
+ treatment host-owned and visible when applicable.
544
+ - Error copy and status labels are supplied by the caller or labels props; the
545
+ shared component must not invent product policy or workflow meaning.
546
+ - New user-visible copy should enter the component boundary through props,
547
+ labels, or children rather than new hardcoded strings inside the shared
548
+ component.
549
+ - The component boundary leaves daemon, Electron, router, persistence, query,
550
+ filesystem, polling, i18n lookup, and workflow orchestration in the host.
551
+
552
+ Report the promotion review in this structure:
553
+
554
+ - context: component id/layer, source usage, user task, selected states, and
555
+ intended reuse surfaces
556
+ - summary: pass, needs work, or blocked
557
+ - pillar assessment: Frictionless, Quality craft, Trustworthy
558
+ - design-system compliance: tokens, primitives, metadata, storyboard, stable
559
+ exports, and public imports
560
+ - issues: blocking, major, and minor, with concrete file references
561
+ - validation: exact commands and results
562
+ - risks: uncovered states, unavailable visual evidence, unresolved subagent
563
+ review, or approved visual deltas.
564
+
565
+ ## Agent Skill Rules
566
+
567
+ Use the bundled `nextop-ui-system` skill for prompt-level work involving
568
+ `@tutti-os/ui-system`. The source lives under
569
+ `packages/ui/system/agent/nextop-ui-system/SKILL.md` so it can ship with the UI
570
+ system package.
571
+
572
+ The skill should route internally across these scenarios:
573
+
574
+ - use an existing UI-system component
575
+ - extract a new `base` component
576
+ - extract a new `business` component
577
+ - maintain metadata, ids, exports, or storyboard coverage
578
+
579
+ The skill must treat this document, `packages/ui/AGENTS.md`, metadata, and
580
+ boundary scripts as the source of truth. It should not duplicate long copies of
581
+ the rules; it should point the coding agent to the right files, force the
582
+ base/business decision, and require validation.
583
+
584
+ External business repositories that promote UI into the shared system should add
585
+ a short agent instruction such as:
586
+
587
+ ```md
588
+ When promoting business UI into @tutti-os/ui-system, use the
589
+ nextop-ui-system skill and follow packages/ui/system/ui-system.md.
590
+ ```
591
+
592
+ After installing `@tutti-os/ui-system`, external repositories can configure the
593
+ bundled skill with:
594
+
595
+ ```bash
596
+ pnpm exec nextop-ui-system-install-skill
597
+ ```
598
+
599
+ The command copies the bundled skill into `.codex/skills/nextop-ui-system` in
600
+ the current repository. When `.nextop-ui-system-dev/` is present, the installer
601
+ prefers the synced source checkout so the skill and bundled UI-system rules stay
602
+ aligned with the current local UI-system source. It refuses to overwrite local
603
+ changes unless run with `--force`.
604
+
605
+ ## Storyboard Rules
606
+
607
+ `apps/ui-storyboard` is the local component inventory and example surface for
608
+ `@tutti-os/ui-system`.
609
+
610
+ - the component list, categories, statuses, and inventory counts must come from
611
+ `@tutti-os/ui-system/metadata`
612
+ - storyboard navigation should group component stories by `layer`
613
+ - examples may stay hand-written so they can show realistic composition and
614
+ edge states
615
+ - visible component stories should display the component `id` prominently and
616
+ support copying it from the UI
617
+ - the storyboard should import public UI-system entrypoints and metadata, not
618
+ private component file paths
619
+ - keep the storyboard as a development surface; it should not become a product
620
+ shell or marketing page
621
+
622
+ ## External Dev Server Rules
623
+
624
+ The UI-system dev server exists only for local external development. It lets an
625
+ external app keep normal `@tutti-os/ui-system` imports while temporarily resolving
626
+ the stable entrypoints to a generated local cache.
627
+
628
+ - start it with `pnpm --filter @tutti-os/ui-system dev:server`
629
+ - external Vite apps opt in with `nextopUISystemDev` from
630
+ `@tutti-os/ui-system/dev-vite`
631
+ - when the server is unavailable, external apps must fall back to their
632
+ installed package in `node_modules`
633
+ - the generated `.nextop-ui-system-dev/` cache belongs in the external app's
634
+ `.gitignore`
635
+ - Tailwind consumers must include both the installed package output and the
636
+ generated dev cache in source scanning, for example
637
+ `@source "../node_modules/@tutti-os/ui-system/dist";` and
638
+ `@source "../.nextop-ui-system-dev";`
639
+ - do not make CI, production builds, or package publishing depend on the dev
640
+ server
641
+ - `@tutti-os/ui-system/dev-vite` may be imported only from bundler config or
642
+ tooling files
643
+
644
+ ## Icon Rules
645
+
646
+ - renderer-visible icons should be exported through the root package or the stable `@tutti-os/ui-system/icons` barrel
647
+ - promoted components and storyboard examples must consume icons from
648
+ `@tutti-os/ui-system/icons`; do not leave inline SVG/data URI icons,
649
+ app-local icon assets, or direct third-party icon imports in promoted UI
650
+ - generic system icons may wrap a third-party icon set
651
+ - product marks and custom status glyphs should live as local SVG components in the package
652
+ - icons should default to `currentColor` unless a specific token-driven treatment is required
653
+
654
+ ## Review Heuristics
655
+
656
+ When reviewing a change under `packages/ui/*`, prefer these checks:
657
+
658
+ - is the new export responsibility-named and stable enough to support for a while
659
+ - could a consumer do the same work through the root package instead of a new subpath
660
+ - is a proposed helper really part of primitive support, or should it stay local to one app
661
+ - does the change make the package easier to consume without exposing its folder layout
662
+
663
+ ## Review Questions
664
+
665
+ When reviewing a change under `packages/ui/*`, ask:
666
+
667
+ 1. Is this a visual-system concern or an app-specific component?
668
+ 2. Does this change preserve CSS tokens as the source of truth?
669
+ 3. Is the package API still narrow, stable, and responsibility-named?
670
+ 4. Would this be clearer if it stayed local to one app instead?
671
+ 5. Are consumers being nudged toward the root package and stable barrels instead of internal paths?