Component Layering and UI Encapsulation
Define what belongs in packages/os-ui, what belongs in apps/shell orchestration, and how headless primitives should be sealed. Visual tokens are documented in [
Purpose
Define what belongs in packages/os-ui, what belongs in apps/shell orchestration, and how headless primitives should be sealed. Visual tokens are documented in tokens.md; shell chrome rules in os-shell-design-language.md.
Current Status
@prox-os/os-ui exports shell-oriented primitives and layout pieces (see packages/os-ui/src/index.ts). Radix/Ariakit-style dependencies, if present, should remain implementation details inside os-ui — not re-exported as the public app API.
Layer Model
┌─────────────────────────────────────────────┐
│ apps/os-shell, packages/os-apps │ Product logic, registry, Zustand, Query
├─────────────────────────────────────────────┤
│ packages/os-ui │ Presentational shell components
├─────────────────────────────────────────────┤
│ @prox-os/design-tokens │ --os-* CSS, Tailwind bridge
├─────────────────────────────────────────────┤
│ Headless primitives (Radix, etc.) │ Only inside os-ui when needed
└─────────────────────────────────────────────┘What Belongs in os-ui
- Pure presentational components:
Panel,Surface,WindowFrame,CommandSurface,Dock,ShellBar, … - Accessible wrappers and keyboard/focus behavior for those primitives.
- Components that read CSS variables (
var(--os-color-*)) and density/theme attributes on ancestors. - Shared hooks that are UI-only (e.g. dialog escape handling) without business data.
Exports today (observation): packages/os-ui/src/index.ts — primitives under primitives/, shell under shell/.
What Does Not Belong in os-ui
- App registry, manifest resolution, or route group logic.
- Zustand stores or window manager actions.
- TanStack Query hooks or API clients.
- App i18n bundles (
apps/os-shell/src/i18n/**is shell-owned). - Per-app business tables, forms tied to domain entities, or App Store catalog data.
Enforced by dependency-cruiser rules (package-graph.md).
Shell vs App Components
| Location | Examples | May use |
|---|---|---|
packages/os-ui | WindowControls, CommandSurface | tokens, local UI state |
apps/os-shell/src/shell/** | DesktopScene, GlobalShortcuts, MissionControlOverlay | os-ui, Zustand, router |
packages/os-apps | ProjectsApp, AppStoreApp | os-ui, app-contract runtime props |
apps/os-shell/src/apps/** | DisplayOptionsApp, Architecture local module | contract adapters, os-ui |
Apps should compose os-ui + tokens; they should not fork window chrome.
Headless Primitive Policy
Decision: Business code imports @prox-os/os-ui, not @radix-ui/* (or similar) directly.
Why:
- Centralizes accessibility and focus traps.
- Allows primitive swaps without rewriting every app.
- Prevents visual drift (spacing, radius, motion).
Exception process: Document in PR + add wrapper in os-ui first.
Storybook / UI Workshop
apps/ui-workshop is the visual acceptance surface for Próx OS: it loads @prox-os/design-tokens and @prox-os/os-ui/tokens.css plus Tailwind (same bridge as the shell). Stories live under apps/ui-workshop/src/stories/** and must stay decoupled from shell Zustand, registry, and routing.
Foundation components documented in Storybook (round 1): WindowFrame, Dock, ShellBar, AppTile, EmptyState (plus existing StatusPill variants). Shell code in apps/os-shell keeps state ownership (window manager, active app, workspaces); only presentational pieces belong in os-ui.
Use the workshop when adding primitives or changing chrome density — not as a second design system.
Anti-Patterns
- Importing shell Zustand from
os-ui. - Passing entire
appRegistryinto a primitive for convenience. - Hard-coded hex in
os-uiinstead of--os-*variables. - Duplicating
WindowFrameinside an app to “customize” one screen — extend via tokens or shell-approved slots.
Related Files
packages/os-ui/src/index.tspackages/os-ui/src/shell/*apps/os-shell/src/shell/DesktopScene.tsx(orchestration).dependency-cruiser.js