Track HiveGrid Cloud planning reference

This commit is contained in:
david committed 2026-06-26 20:48:01 +02:00
1 parent b3d5ea6764
commit e341c5938a
5784 files changed
+1104929 -3

No files matched your search

@@ -0,0 +1,21 @@
# UI Inventory
This directory is the machine-readable starting point for UI ownership and future hotswap work.
Current scope:
- Identify stable shell, sidebar, navigation and primitive component names.
- Record current file paths and backing libraries.
- Mark adapter candidates for later `packages/ui` migration.
This inventory does not move components. App code should keep its current imports until a category-specific migration introduces real `@toolctrl/ui/*` adapter exports.
Files:
- `schema.json` declares the inventory shape and allowed categories/statuses.
- `components.json` lists current shell, navigation and root UI primitive ownership.
- `surfaces.schema.json` declares the stable UI surface/action/nav map shape.
- `surfaces.json` maps stable `data-ui-id` values to source files and renderers.
- `surface-map.md` is the human-readable companion for prompts and reviews.
The app test suite validates that inventory paths exist, lowercase `apps/web/app/components/ui/*.tsx` primitive files are represented here, and stable registry IDs are represented in the surface map.
@@ -0,0 +1,507 @@
{
"schemaVersion": 1,
"notes": [
"Read-only inventory for the first UI registry phase.",
"Statuses describe current ownership, not desired end state.",
"Headless UI entries are legacy candidates; Radix/shadcn remains the core default for now."
],
"components": [
{
"stableName": "ui.surface.app.sidebar",
"path": "apps/web/app/components/layouts/sidebars/shadcn/app-sidebar.tsx",
"category": "navigation",
"library": "shadcn/radix",
"status": "core"
},
{
"stableName": "AppSidebar",
"path": "apps/web/app/application/sidebar/AppSidebar.tsx",
"category": "navigation",
"library": "application-registry",
"status": "available"
},
{
"stableName": "ui.surface.admin.sidebar",
"path": "apps/web/app/components/layouts/sidebars/shadcn/app-sidebar.tsx",
"category": "navigation",
"library": "shadcn/radix",
"status": "core"
},
{
"stableName": "AdminSidebar",
"path": "apps/web/app/application/sidebar/AdminSidebar.tsx",
"category": "navigation",
"library": "application-registry",
"status": "available"
},
{
"stableName": "ui.surface.docs.sidebar",
"path": "apps/web/app/components/layouts/sidebars/shadcn/app-sidebar.tsx",
"category": "navigation",
"library": "shadcn/radix",
"status": "core"
},
{
"stableName": "DocsSidebar",
"path": "apps/web/app/application/sidebar/DocsSidebar.ts",
"category": "navigation",
"library": "application-registry",
"status": "available"
},
{
"stableName": "ShadcnSidebarLayout",
"path": "apps/web/app/components/layouts/sidebars/shadcn/ShadcnSidebarLayout.tsx",
"category": "layout",
"library": "shadcn/radix",
"status": "available"
},
{
"stableName": "UiRegistry",
"path": "apps/web/app/application/ui/uiRegistry.ts",
"category": "layout",
"library": "agent-registry",
"status": "available"
},
{
"stableName": "NavMain",
"path": "apps/web/app/components/layouts/sidebars/shadcn/nav-main.tsx",
"category": "navigation",
"library": "shadcn/radix",
"status": "available"
},
{
"stableName": "NavSecondary",
"path": "apps/web/app/components/layouts/sidebars/shadcn/nav-secondary.tsx",
"category": "navigation",
"library": "shadcn/radix",
"status": "available"
},
{
"stableName": "NavQuickLinks",
"path": "apps/web/app/components/layouts/sidebars/shadcn/nav-quick-links.tsx",
"category": "navigation",
"library": "shadcn/radix",
"status": "available"
},
{
"stableName": "NavActions",
"path": "apps/web/app/components/layouts/sidebars/shadcn/nav-actions.tsx",
"category": "navigation",
"library": "shadcn/radix/lucide/novu/custom",
"status": "available"
},
{
"stableName": "SidebarPrimitives",
"path": "apps/web/app/components/ui/sidebar.tsx",
"category": "navigation",
"library": "shadcn/radix",
"status": "available"
},
{
"stableName": "Button",
"path": "apps/web/app/components/ui/button.tsx",
"category": "buttons",
"library": "shadcn/radix",
"status": "available"
},
{
"stableName": "Dialog",
"path": "apps/web/app/components/ui/dialog.tsx",
"category": "dialogs",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Modal",
"path": "apps/web/app/components/ui/modals/Modal.tsx",
"category": "dialogs",
"library": "custom",
"status": "candidate-for-adapter"
},
{
"stableName": "ProfileButton",
"path": "apps/web/app/components/layouts/buttons/ProfileButton.tsx",
"category": "buttons",
"library": "headlessui/custom",
"status": "legacy"
},
{
"stableName": "SearchButton",
"path": "apps/web/app/components/layouts/buttons/SearchButton.tsx",
"category": "buttons",
"library": "custom",
"status": "candidate-for-adapter"
},
{
"stableName": "Accordion",
"path": "apps/web/app/components/ui/accordion.tsx",
"category": "dialogs",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "AlertDialog",
"path": "apps/web/app/components/ui/alert-dialog.tsx",
"category": "dialogs",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Alert",
"path": "apps/web/app/components/ui/alert.tsx",
"category": "feedback",
"library": "shadcn/custom",
"status": "candidate-for-adapter"
},
{
"stableName": "AspectRatio",
"path": "apps/web/app/components/ui/aspect-ratio.tsx",
"category": "layout",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Avatar",
"path": "apps/web/app/components/ui/avatar.tsx",
"category": "feedback",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Badge",
"path": "apps/web/app/components/ui/badge.tsx",
"category": "feedback",
"library": "shadcn/custom",
"status": "candidate-for-adapter"
},
{
"stableName": "Breadcrumb",
"path": "apps/web/app/components/ui/breadcrumb.tsx",
"category": "navigation",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Input",
"path": "apps/web/app/components/ui/input.tsx",
"category": "inputs",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "ButtonConfirm",
"path": "apps/web/app/components/ui/button-confirm.tsx",
"category": "buttons",
"library": "custom",
"status": "candidate-for-adapter"
},
{
"stableName": "ButtonGroup",
"path": "apps/web/app/components/ui/button-group.tsx",
"category": "buttons",
"library": "custom",
"status": "candidate-for-adapter"
},
{
"stableName": "Calendar",
"path": "apps/web/app/components/ui/calendar.tsx",
"category": "inputs",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Card",
"path": "apps/web/app/components/ui/card.tsx",
"category": "layout",
"library": "shadcn/custom",
"status": "candidate-for-adapter"
},
{
"stableName": "Carousel",
"path": "apps/web/app/components/ui/carousel.tsx",
"category": "layout",
"library": "shadcn/embla",
"status": "candidate-for-adapter"
},
{
"stableName": "Chart",
"path": "apps/web/app/components/ui/chart.tsx",
"category": "feedback",
"library": "shadcn/recharts",
"status": "candidate-for-adapter"
},
{
"stableName": "Checkbox",
"path": "apps/web/app/components/ui/checkbox.tsx",
"category": "inputs",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Choicebox",
"path": "apps/web/app/components/ui/choicebox.tsx",
"category": "inputs",
"library": "custom",
"status": "candidate-for-adapter"
},
{
"stableName": "Collapsible",
"path": "apps/web/app/components/ui/collapsible.tsx",
"category": "layout",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Command",
"path": "apps/web/app/components/ui/command.tsx",
"category": "navigation",
"library": "shadcn/cmdk",
"status": "candidate-for-adapter"
},
{
"stableName": "ContextMenu",
"path": "apps/web/app/components/ui/context-menu.tsx",
"category": "navigation",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Drawer",
"path": "apps/web/app/components/ui/drawer.tsx",
"category": "dialogs",
"library": "shadcn/vaul",
"status": "candidate-for-adapter"
},
{
"stableName": "DropdownMenu",
"path": "apps/web/app/components/ui/dropdown-menu.tsx",
"category": "navigation",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Empty",
"path": "apps/web/app/components/ui/empty.tsx",
"category": "feedback",
"library": "custom",
"status": "candidate-for-adapter"
},
{
"stableName": "Form",
"path": "apps/web/app/components/ui/form.tsx",
"category": "inputs",
"library": "shadcn/radix/react-hook-form",
"status": "candidate-for-adapter"
},
{
"stableName": "HoverCard",
"path": "apps/web/app/components/ui/hover-card.tsx",
"category": "feedback",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "InputGroup",
"path": "apps/web/app/components/ui/input-group.tsx",
"category": "inputs",
"library": "custom",
"status": "candidate-for-adapter"
},
{
"stableName": "InputOtp",
"path": "apps/web/app/components/ui/input-otp.tsx",
"category": "inputs",
"library": "shadcn/input-otp",
"status": "candidate-for-adapter"
},
{
"stableName": "Label",
"path": "apps/web/app/components/ui/label.tsx",
"category": "inputs",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "MultiSelect",
"path": "apps/web/app/components/ui/multi-select.tsx",
"category": "inputs",
"library": "custom",
"status": "candidate-for-adapter"
},
{
"stableName": "Pagination",
"path": "apps/web/app/components/ui/pagination.tsx",
"category": "navigation",
"library": "shadcn/custom",
"status": "candidate-for-adapter"
},
{
"stableName": "Popover",
"path": "apps/web/app/components/ui/popover.tsx",
"category": "dialogs",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Progress",
"path": "apps/web/app/components/ui/progress.tsx",
"category": "feedback",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "RadioGroup",
"path": "apps/web/app/components/ui/radio-group.tsx",
"category": "inputs",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Resizable",
"path": "apps/web/app/components/ui/resizable.tsx",
"category": "layout",
"library": "shadcn/react-resizable-panels",
"status": "candidate-for-adapter"
},
{
"stableName": "ScrollArea",
"path": "apps/web/app/components/ui/scroll-area.tsx",
"category": "layout",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Select",
"path": "apps/web/app/components/ui/select.tsx",
"category": "inputs",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Separator",
"path": "apps/web/app/components/ui/separator.tsx",
"category": "layout",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Sheet",
"path": "apps/web/app/components/ui/sheet.tsx",
"category": "dialogs",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Skeleton",
"path": "apps/web/app/components/ui/skeleton.tsx",
"category": "feedback",
"library": "shadcn/custom",
"status": "candidate-for-adapter"
},
{
"stableName": "Slider",
"path": "apps/web/app/components/ui/slider.tsx",
"category": "inputs",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Sonner",
"path": "apps/web/app/components/ui/sonner.tsx",
"category": "feedback",
"library": "shadcn/sonner",
"status": "candidate-for-adapter"
},
{
"stableName": "SplitButton",
"path": "apps/web/app/components/ui/split-button.tsx",
"category": "buttons",
"library": "custom",
"status": "candidate-for-adapter"
},
{
"stableName": "Stepper",
"path": "apps/web/app/components/ui/stepper.tsx",
"category": "inputs",
"library": "custom",
"status": "candidate-for-adapter"
},
{
"stableName": "Switch",
"path": "apps/web/app/components/ui/switch.tsx",
"category": "inputs",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Table",
"path": "apps/web/app/components/ui/table.tsx",
"category": "tables",
"library": "shadcn/custom",
"status": "candidate-for-adapter"
},
{
"stableName": "Tabs",
"path": "apps/web/app/components/ui/tabs.tsx",
"category": "tables",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Textarea",
"path": "apps/web/app/components/ui/textarea.tsx",
"category": "inputs",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "ToastPrimitive",
"path": "apps/web/app/components/ui/toast.tsx",
"category": "feedback",
"library": "shadcn/radix",
"status": "legacy"
},
{
"stableName": "Toaster",
"path": "apps/web/app/components/ui/toaster.tsx",
"category": "feedback",
"library": "shadcn/radix",
"status": "legacy"
},
{
"stableName": "ToggleGroup",
"path": "apps/web/app/components/ui/toggle-group.tsx",
"category": "buttons",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Toggle",
"path": "apps/web/app/components/ui/toggle.tsx",
"category": "buttons",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Tooltip",
"path": "apps/web/app/components/ui/tooltip.tsx",
"category": "feedback",
"library": "shadcn/radix",
"status": "candidate-for-adapter"
},
{
"stableName": "Toast",
"path": "apps/web/app/root.tsx",
"category": "feedback",
"library": "sonner",
"status": "candidate-for-adapter"
},
{
"stableName": "Lucide icons",
"path": "apps/web/app/components/layouts/icons",
"category": "feedback",
"library": "lucide/custom",
"status": "plugin"
}
]
}
@@ -0,0 +1,65 @@
# UI Hot-Swap Status
## What's wired
The boilerplate ships three UI adapter packages under `packages/`:
| Package | Purpose | Current state |
| --- | --- | --- |
| `@toolctrl/ui-app` | App-shell registries (sidebar, breadcrumb, mobile shell, portals) | Skeleton + registry types, no concrete component bindings. |
| `@toolctrl/ui-subframe` | Subframe (design-tool) component placeholder bridge | Placeholder factory only — `createSubframePlaceholder()` returns `{ status: "empty" }`. |
| `@toolctrl/ui-mock` | Fixture-driven mock surface for visual/hot-swap testing | Has `createHotSwapDrillPage()` and is consumed by `scripts/verify-ui-hot-swap.ts`. |
`packages/foundation` and `packages/view-models` provide the contract these adapters compose against.
## What is NOT wired
- `@toolctrl/ui-subframe` is still a placeholder bridge — no real design-source binding.
- Outside the activation reference route (see below), `apps/web/app/routes/**` does not render through any `@toolctrl/ui-*` registry — concrete components still live in `apps/web/app/components/`.
- The majority of consumer surfaces (admin shell, tenant dashboard, marketing) have not been refactored to consume from `@toolctrl/ui-app`'s page registry.
## What IS wired (activation reference)
A minimal hot-swap activation now lives in `apps/web`:
- `apps/web/package.json` depends on `@toolctrl/ui-app`, `@toolctrl/ui-mock`, `@toolctrl/foundation`, `@toolctrl/view-models`.
- `apps/web/app/components/ui/hot-swap/HotSwapPageRenderer.tsx` bridges the descriptor shape from `@toolctrl/ui-mock` to RR7 JSX using `createShellRegistries()` from `@toolctrl/ui-app`.
- `apps/web/app/routes/hot-swap-demo.tsx` renders `createHotSwapDrillPage()` through the renderer. The route is gated to `NODE_ENV !== "production"` so production surface area is unchanged.
- `apps/web/app/components/ui/hot-swap/HotSwapPageRenderer.test.tsx` asserts the descriptor → DOM mapping (sidebar, breadcrumbs, header, cards, activity).
This is the first activation milestone: the registry path is alive end-to-end against a real consumer route, but is still fixture-driven (no Subframe binding yet).
## Why this is intentional (Settings API Onboarding-era)
The adapter packages were introduced in the Phase-C boundary work to lock down what a **whitelabel-friendly** UI swap can look like without forcing a big-bang component rewrite. Boundaries are enforced by `scripts/verify-phase-c-boundaries.mjs` so any future activation cannot accidentally cross a runtime line.
Until an activation milestone explicitly enables Subframe (or another design source), the registries stay as a structural placeholder. The cost of registering them in `apps/web` without a real binding would be:
- pulling `@toolctrl/foundation` + `@toolctrl/view-models` into the web bundle for no consumer,
- adding registry warm-up code paths to SSR + hot-swap tests with no real fixture to exercise,
- noise in the hot-swap verifier (`verify:ui-hot-swap`) that would have to allowlist “empty registry” permanently.
## When to activate
Activate the hot-swap path when **at least one** is true:
- A real Subframe/Figma source is being integrated and `ui-subframe` becomes a non-empty bridge.
- A second consumer app (admin shell, partner portal) needs to share `apps/web` components through a registry instead of importing relative paths.
- A whitelabel deployment needs to swap the dashboard surface per-tenant without forking the codebase.
## Activation checklist
1. ✓ Add `@toolctrl/ui-app`, `@toolctrl/ui-mock`, `@toolctrl/foundation`, `@toolctrl/view-models` to `apps/web/package.json` `dependencies`. — done.
2. ✓ Pick one low-risk route and refactor it to consume from `@toolctrl/ui-app`'s page registry. — done at `apps/web/app/routes/hot-swap-demo.tsx`, dev-only.
3. ☐ Add `@toolctrl/ui-subframe` to `apps/web/package.json` once it becomes a non-empty bridge.
4. ☐ Promote the mock fixture in `packages/ui-mock/src/index.ts` (`createHotSwapDrillPage`) to a non-mock descriptor source once a design tool is wired.
5. ☐ Refactor a second real consumer surface (admin shell or tenant dashboard) through the renderer to prove cross-surface reuse.
6. ☐ Update `docs/ui-inventory/surface-map.md` to claim the new surfaces as hot-swappable.
## Related verification
```sh
pnpm run verify:phase-c-boundaries # boundaries that future activation must not cross
pnpm run verify:ui-visual # current mock visual baseline
pnpm run test:hot-swap # mock-driven hot-swap drill
```
@@ -0,0 +1,42 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://toolctrl.local/ui-inventory.schema.json",
"title": "Toolctrl UI Inventory",
"type": "object",
"additionalProperties": false,
"required": ["schemaVersion", "notes", "components"],
"properties": {
"schemaVersion": {
"type": "integer",
"const": 1
},
"notes": {
"type": "array",
"items": { "type": "string" }
},
"components": {
"type": "array",
"items": { "$ref": "#/$defs/component" }
}
},
"$defs": {
"component": {
"type": "object",
"additionalProperties": false,
"required": ["stableName", "path", "category", "library", "status"],
"properties": {
"stableName": { "type": "string", "minLength": 1 },
"path": { "type": "string", "minLength": 1 },
"category": {
"type": "string",
"enum": ["navigation", "layout", "buttons", "dialogs", "tables", "inputs", "feedback"]
},
"library": { "type": "string", "minLength": 1 },
"status": {
"type": "string",
"enum": ["core", "available", "legacy", "plugin", "candidate-for-adapter"]
}
}
}
}
}
@@ -0,0 +1,82 @@
# UI Surface Map
This file maps stable UI IDs to the source files an agent should inspect before changing a surface.
Machine-readable map: `docs/ui-inventory/surfaces.json`.
## Shell Surfaces
| UI ID | Source | Purpose |
| --- | --- | --- |
| `ui.surface.shell.header` | `apps/web/app/components/layouts/sidebars/shadcn/ShadcnSidebarLayout.tsx` | Authenticated shell header. |
| `ui.surface.shell.breadcrumb` | `apps/web/app/components/layouts/sidebars/shadcn/ShadcnSidebarLayout.tsx` | Header breadcrumb container. |
| `ui.surface.shell.breadcrumb.current` | `apps/web/app/components/layouts/sidebars/shadcn/ShadcnSidebarLayout.tsx` | Current page title. |
| `ui.surface.shell.navActions` | `apps/web/app/components/layouts/sidebars/shadcn/nav-actions.tsx` | Header right action area. |
| `ui.surface.shell.main` | `apps/web/app/components/layouts/sidebars/shadcn/ShadcnSidebarLayout.tsx` | Authenticated page content. |
## Sidebar Surfaces
| UI ID | Source | Purpose |
| --- | --- | --- |
| `ui.surface.app.sidebar` | `apps/web/app/components/layouts/sidebars/shadcn/app-sidebar.tsx`, `apps/web/app/application/sidebar/AppSidebar.tsx` | Tenant app sidebar. |
| `ui.surface.admin.sidebar` | `apps/web/app/components/layouts/sidebars/shadcn/app-sidebar.tsx`, `apps/web/app/application/sidebar/AdminSidebar.tsx` | Admin sidebar. |
| `ui.surface.docs.sidebar` | `apps/web/app/components/layouts/sidebars/shadcn/app-sidebar.tsx`, `apps/web/app/application/sidebar/DocsSidebar.ts` | Docs sidebar. |
## Header Actions
| UI ID | Source | Purpose |
| --- | --- | --- |
| `ui.action.shell.sidebarTrigger` | `ShadcnSidebarLayout.tsx`, `apps/web/app/components/ui/sidebar.tsx` | Open/collapse sidebar. |
| `ui.action.shell.search` | `nav-actions.tsx`, `SearchButton.tsx` | Open command palette/search. |
| `ui.action.shell.aiAssistant` | `nav-actions.tsx` | Open AI assistant. |
| `ui.action.shell.notifications` | `nav-actions.tsx` | Notifications. |
| `ui.action.shell.onboarding` | `nav-actions.tsx` | Onboarding. |
| `ui.action.shell.feedback` | `nav-actions.tsx` | Feedback dialog. |
| `ui.action.shell.chatSupport` | `nav-actions.tsx` | External support chat. |
| `ui.action.shell.more` | `nav-actions.tsx` | More menu. |
| `ui.action.shell.profile` | `ProfileButton.tsx`, `nav-actions.tsx` | Profile action. |
| `ui.action.shell.subscription` | `nav-actions.tsx` | Subscription action. |
| `ui.action.shell.signOut` | `nav-actions.tsx` | Sign out. |
## Core Nav IDs
| UI ID | Source | Purpose |
| --- | --- | --- |
| `ui.nav.app.dashboard` | `AppSidebar.tsx` | Tenant dashboard. |
| `ui.nav.app.settings` | `AppSidebar.tsx` | Tenant settings. |
| `ui.nav.app.switchToAdmin` | `AppSidebar.tsx` | Tenant-to-admin switch. |
| `ui.nav.admin.dashboard` | `AdminSidebar.tsx` | Admin dashboard. |
| `ui.nav.admin.accounts` | `AdminSidebar.tsx` | Accounts and users. |
| `ui.nav.admin.analytics` | `AdminSidebar.tsx` | Analytics. |
| `ui.nav.admin.entities` | `AdminSidebar.tsx` | Entity builder/runtime. |
| `ui.nav.admin.workflows` | `AdminSidebar.tsx` | Workflow plugin candidate. |
| `ui.nav.admin.api` | `AdminSidebar.tsx` | API key area. |
| `ui.nav.admin.events` | `AdminSidebar.tsx` | Events. |
| `ui.nav.admin.auditTrails` | `AdminSidebar.tsx` | Audit trails. |
| `ui.nav.admin.notifications` | `AdminSidebar.tsx` | Notifications. |
| `ui.nav.admin.featureFlags` | `AdminSidebar.tsx` | Feature flags. |
| `ui.nav.admin.navigation` | `AdminSidebar.tsx` | Navigation configuration. |
| `ui.nav.admin.portals` | `AdminSidebar.tsx` | Portals plugin candidate. |
| `ui.nav.admin.prompts` | `AdminSidebar.tsx` | Prompt builder plugin candidate. |
| `ui.nav.admin.emailMarketing` | `AdminSidebar.tsx` | Email marketing plugin candidate. |
| `ui.nav.admin.knowledgeBase` | `AdminSidebar.tsx` | Knowledge base plugin candidate. |
| `ui.nav.admin.helpDesk` | `AdminSidebar.tsx` | Help desk plugin candidate. |
| `ui.nav.admin.onboarding` | `AdminSidebar.tsx` | Onboarding. |
| `ui.nav.admin.metrics` | `AdminSidebar.tsx` | Metrics. |
| `ui.nav.admin.settings` | `AdminSidebar.tsx` | Admin settings. |
| `ui.nav.admin.switchToApp` | `AdminSidebar.tsx` | Admin-to-app switch. |
| `ui.nav.docs.introduction` | `DocsSidebar.ts` | Docs introduction. |
## Dynamic Patterns
| Pattern | Source | Notes |
| --- | --- | --- |
| `ui.nav.app.entity.{entityName}` | `AppSidebar.tsx` | Tenant entity nav entries. |
| `ui.nav.app.entityGroup.{groupSlug}` | `AppSidebar.tsx` | Tenant entity group nav entries. |
| `ui.nav.app.entityGroup.{groupSlug}.entity.{entityName}` | `AppSidebar.tsx` | Nested entity group entries. |
| `ui.nav.app.section.{sectionName}` | `AppSidebar.tsx` | Tenant sidebar sections. |
| `ui.nav.docs.route.{routeSlug}` | `DocsSidebar.ts` | Docs route entries. |
## Agent Guidance
When a prompt says "header right", "search", "sidebar", or "settings nav", resolve it to a UI ID first, then inspect the mapped source file. If no stable UI ID exists for a new surface, add it to `apps/web/app/application/ui/uiRegistry.ts`, render it as `data-ui-id`, update `surfaces.json`, and add a DOM test.
@@ -0,0 +1,485 @@
{
"schemaVersion": 1,
"surfaces": [
{
"id": "ui.surface.shell",
"kind": "surface",
"label": "Authenticated shell",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/ShadcnSidebarLayout.tsx", "apps/web/app/application/ui/uiRegistry.ts"],
"renderer": "SidebarProvider",
"notes": ["Root authenticated shell surface for app, admin and docs layouts."]
},
{
"id": "ui.surface.shell.inset",
"kind": "surface",
"label": "Authenticated shell inset",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/ShadcnSidebarLayout.tsx", "apps/web/app/application/ui/uiRegistry.ts"],
"renderer": "SidebarInset",
"notes": ["Inset area that contains the shell header and main content."]
},
{
"id": "ui.surface.app.sidebar",
"kind": "surface",
"label": "Tenant app sidebar",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/app-sidebar.tsx", "apps/web/app/application/sidebar/AppSidebar.tsx"],
"renderer": "ShadcnAppSidebar",
"notes": ["Use for prompts that target the tenant sidebar container."]
},
{
"id": "ui.surface.admin.sidebar",
"kind": "surface",
"label": "Admin sidebar",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/app-sidebar.tsx", "apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "ShadcnAppSidebar",
"notes": ["Use for prompts that target the admin sidebar container."]
},
{
"id": "ui.surface.docs.sidebar",
"kind": "surface",
"label": "Docs sidebar",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/app-sidebar.tsx", "apps/web/app/application/sidebar/DocsSidebar.ts"],
"renderer": "ShadcnAppSidebar",
"notes": ["Use for prompts that target documentation navigation."]
},
{
"id": "ui.surface.shell.header",
"kind": "surface",
"label": "Shell header",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/ShadcnSidebarLayout.tsx"],
"renderer": "ShadcnSidebarLayout",
"notes": ["Top authenticated shell bar containing sidebar trigger, breadcrumb and right actions."]
},
{
"id": "ui.surface.shell.breadcrumb",
"kind": "surface",
"label": "Shell breadcrumb area",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/ShadcnSidebarLayout.tsx"],
"renderer": "ShadcnSidebarLayout",
"notes": ["Left header area after the sidebar trigger."]
},
{
"id": "ui.surface.shell.breadcrumb.current",
"kind": "surface",
"label": "Current breadcrumb page",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/ShadcnSidebarLayout.tsx"],
"renderer": "BreadcrumbPage",
"notes": ["Current page title inside the shell breadcrumb."]
},
{
"id": "ui.surface.shell.main",
"kind": "surface",
"label": "Shell main content",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/ShadcnSidebarLayout.tsx"],
"renderer": "main",
"notes": ["Authenticated page content host."]
},
{
"id": "ui.surface.shell.navActions",
"kind": "surface",
"label": "Header right action area",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/ShadcnSidebarLayout.tsx", "apps/web/app/components/layouts/sidebars/shadcn/nav-actions.tsx"],
"renderer": "NavActions",
"notes": ["Right side of header with search, notifications, AI, profile/more actions."]
},
{
"id": "ui.surface.shell.aiAssistant.dialog",
"kind": "surface",
"label": "AI assistant dialog",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/nav-actions.tsx"],
"renderer": "DialogContent",
"notes": ["Dialog surface for AI assistant chat."]
},
{
"id": "ui.surface.shell.feedback.dialog",
"kind": "surface",
"label": "Feedback dialog",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/nav-actions.tsx", "apps/web/app/components/ui/modals/Modal.tsx"],
"renderer": "Modal",
"notes": ["Dialog surface for tenant feedback."]
},
{
"id": "ui.surface.commandPalette.portal",
"kind": "surface",
"label": "Command palette portal",
"sourceFiles": ["apps/web/app/components/ui/commandPalettes/CommandPalette.tsx", "apps/web/app/application/ui/uiRegistry.ts"],
"renderer": "KBarPortal",
"notes": ["Portal host for global command palette overlays."]
},
{
"id": "ui.surface.commandPalette.search",
"kind": "surface",
"label": "Command palette search",
"sourceFiles": ["apps/web/app/components/ui/commandPalettes/CommandPalette.tsx", "apps/web/app/application/ui/uiRegistry.ts"],
"renderer": "KBarSearch",
"notes": ["Search input inside the global command palette."]
},
{
"id": "ui.surface.commandPalette.results",
"kind": "surface",
"label": "Command palette results",
"sourceFiles": ["apps/web/app/components/ui/commandPalettes/CommandPalette.tsx", "apps/web/app/application/ui/uiRegistry.ts"],
"renderer": "KBarResults",
"notes": ["Scrollable result list inside the global command palette."]
},
{
"id": "ui.surface.commandPalette.row",
"kind": "surface",
"label": "Command palette row",
"sourceFiles": ["apps/web/app/components/ui/commandPalettes/CommandPalette.tsx", "apps/web/app/application/ui/uiRegistry.ts"],
"renderer": "CommandPaletteResult",
"notes": ["Individual selectable command palette result row."]
},
{
"id": "ui.action.shell.sidebarTrigger",
"kind": "action",
"label": "Sidebar trigger",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/ShadcnSidebarLayout.tsx", "apps/web/app/components/ui/sidebar.tsx"],
"renderer": "SidebarTrigger",
"notes": ["Collapses or opens the shell sidebar."]
},
{
"id": "ui.action.shell.search",
"kind": "action",
"label": "Header search",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/nav-actions.tsx", "apps/web/app/components/layouts/buttons/SearchButton.tsx"],
"renderer": "NavActions/SearchButton",
"notes": ["Opens command palette/search."]
},
{
"id": "ui.action.shell.aiAssistant",
"kind": "action",
"label": "AI assistant button",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/nav-actions.tsx"],
"renderer": "NavActions",
"notes": ["Opens AI assistant dialog when available."]
},
{
"id": "ui.action.shell.aiAssistant.expand",
"kind": "action",
"label": "AI assistant expand",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/nav-actions.tsx"],
"renderer": "NavActions",
"notes": ["Expands dialog chat into full chat route."]
},
{
"id": "ui.action.shell.aiAssistant.close",
"kind": "action",
"label": "AI assistant close",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/nav-actions.tsx"],
"renderer": "NavActions",
"notes": ["Closes AI assistant dialog."]
},
{
"id": "ui.action.shell.notifications",
"kind": "action",
"label": "Notifications",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/nav-actions.tsx"],
"renderer": "NavActions",
"notes": ["Novu-backed notifications trigger."]
},
{
"id": "ui.action.shell.onboarding",
"kind": "action",
"label": "Onboarding",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/nav-actions.tsx"],
"renderer": "OnboardingButton",
"notes": ["Opens onboarding flow when enabled."]
},
{
"id": "ui.action.shell.feedback",
"kind": "action",
"label": "Feedback",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/nav-actions.tsx"],
"renderer": "AddFeedbackButton",
"notes": ["Opens tenant feedback dialog."]
},
{
"id": "ui.action.shell.chatSupport",
"kind": "action",
"label": "Chat support",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/nav-actions.tsx"],
"renderer": "NavActions",
"notes": ["Opens external support chat when configured."]
},
{
"id": "ui.action.shell.more",
"kind": "action",
"label": "More actions",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/nav-actions.tsx", "apps/web/app/components/layouts/sidebars/shadcn/nav-quick-links.tsx"],
"renderer": "PopoverTrigger/SidebarMenuButton",
"notes": ["Header or quick-link overflow actions."]
},
{
"id": "ui.action.shell.profile",
"kind": "action",
"label": "Profile",
"sourceFiles": ["apps/web/app/components/layouts/buttons/ProfileButton.tsx", "apps/web/app/components/layouts/sidebars/shadcn/nav-actions.tsx"],
"renderer": "ProfileButton/NavActions",
"notes": ["User profile or profile menu action."]
},
{
"id": "ui.action.shell.subscription",
"kind": "action",
"label": "Subscription",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/nav-actions.tsx"],
"renderer": "NavActions",
"notes": ["Subscription CTA or menu action."]
},
{
"id": "ui.action.shell.signOut",
"kind": "action",
"label": "Sign out",
"sourceFiles": ["apps/web/app/components/layouts/sidebars/shadcn/nav-actions.tsx"],
"renderer": "NavActions",
"notes": ["Sign-out action in overflow menu."]
},
{
"id": "ui.nav.app.dashboard",
"kind": "nav",
"label": "Tenant dashboard nav",
"sourceFiles": ["apps/web/app/application/sidebar/AppSidebar.tsx"],
"renderer": "AppSidebar/NavMain",
"notes": ["Tenant dashboard sidebar item."]
},
{
"id": "ui.nav.app.notifications",
"kind": "nav",
"label": "Tenant notifications nav",
"sourceFiles": ["apps/web/app/application/sidebar/AppSidebar.tsx", "apps/web/app/application/ui/uiRegistry.ts"],
"renderer": "AppSidebar/NavMain",
"notes": ["Tenant notification center sidebar item."]
},
{
"id": "ui.nav.app.crm",
"kind": "nav",
"label": "Tenant CRM nav",
"sourceFiles": ["apps/web/app/application/sidebar/AppSidebar.tsx", "apps/web/app/application/ui/uiRegistry.ts"],
"renderer": "AppSidebar/NavMain",
"notes": ["Tenant CRM sidebar item."]
},
{
"id": "ui.nav.app.emailMarketing",
"kind": "nav",
"label": "Tenant email marketing nav",
"sourceFiles": ["apps/web/app/application/sidebar/AppSidebar.tsx", "apps/web/app/application/ui/uiRegistry.ts"],
"renderer": "AppSidebar/NavMain",
"notes": ["Tenant email marketing sidebar item."]
},
{
"id": "ui.nav.app.api",
"kind": "nav",
"label": "Tenant API nav",
"sourceFiles": ["apps/web/app/application/sidebar/AppSidebar.tsx", "apps/web/app/application/ui/uiRegistry.ts"],
"renderer": "AppSidebar/NavMain",
"notes": ["Tenant API keys sidebar item."]
},
{
"id": "ui.nav.app.workflowEngine",
"kind": "nav",
"label": "Tenant workflow engine nav",
"sourceFiles": ["apps/web/app/application/sidebar/AppSidebar.tsx", "apps/web/app/application/ui/uiRegistry.ts"],
"renderer": "AppSidebar/NavMain",
"notes": ["Tenant workflow engine sidebar item."]
},
{
"id": "ui.nav.app.credits",
"kind": "nav",
"label": "Tenant credits nav",
"sourceFiles": ["apps/web/app/application/sidebar/AppSidebar.tsx", "apps/web/app/application/ui/uiRegistry.ts"],
"renderer": "AppSidebar/NavMain",
"notes": ["Tenant credits sidebar item."]
},
{
"id": "ui.nav.app.settings",
"kind": "nav",
"label": "Tenant settings nav",
"sourceFiles": ["apps/web/app/application/sidebar/AppSidebar.tsx"],
"renderer": "AppSidebar/NavSecondary",
"notes": ["Tenant settings sidebar item."]
},
{
"id": "ui.nav.app.switchToAdmin",
"kind": "nav",
"label": "Switch to admin nav",
"sourceFiles": ["apps/web/app/application/sidebar/AppSidebar.tsx"],
"renderer": "AppSidebar/NavSecondary",
"notes": ["Tenant sidebar admin switch item."]
},
{
"id": "ui.nav.admin.dashboard",
"kind": "nav",
"label": "Admin dashboard nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Admin dashboard sidebar item."]
},
{
"id": "ui.nav.admin.accounts",
"kind": "nav",
"label": "Admin accounts nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Admin accounts and users sidebar item."]
},
{
"id": "ui.nav.admin.analytics",
"kind": "nav",
"label": "Admin analytics nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Admin analytics sidebar item."]
},
{
"id": "ui.nav.admin.entities",
"kind": "nav",
"label": "Admin entities nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Admin entities sidebar item."]
},
{
"id": "ui.nav.admin.workflows",
"kind": "nav",
"label": "Admin workflows nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Disabled plugin-candidate workflow sidebar item."]
},
{
"id": "ui.nav.admin.api",
"kind": "nav",
"label": "Admin API nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Admin API keys sidebar item."]
},
{
"id": "ui.nav.admin.events",
"kind": "nav",
"label": "Admin events nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Admin events sidebar item."]
},
{
"id": "ui.nav.admin.auditTrails",
"kind": "nav",
"label": "Admin audit trails nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Admin audit trail sidebar item."]
},
{
"id": "ui.nav.admin.notifications",
"kind": "nav",
"label": "Admin notifications nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Admin notifications sidebar item."]
},
{
"id": "ui.nav.admin.featureFlags",
"kind": "nav",
"label": "Admin feature flags nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Admin feature flags sidebar item."]
},
{
"id": "ui.nav.admin.navigation",
"kind": "nav",
"label": "Admin navigation nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Admin navigation/sidebar configuration item."]
},
{
"id": "ui.nav.admin.portals",
"kind": "nav",
"label": "Admin portals nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Disabled plugin-candidate portals sidebar item."]
},
{
"id": "ui.nav.admin.prompts",
"kind": "nav",
"label": "Admin prompts nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Disabled plugin-candidate prompts sidebar item."]
},
{
"id": "ui.nav.admin.emailMarketing",
"kind": "nav",
"label": "Admin email marketing nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Disabled plugin-candidate email marketing sidebar item."]
},
{
"id": "ui.nav.admin.marketing",
"kind": "nav",
"label": "Admin marketing nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Disabled plugin-candidate marketing sidebar item."]
},
{
"id": "ui.nav.admin.knowledgeBase",
"kind": "nav",
"label": "Admin knowledge base nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Disabled plugin-candidate knowledge base sidebar item."]
},
{
"id": "ui.nav.admin.helpDesk",
"kind": "nav",
"label": "Admin help desk nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Disabled plugin-candidate help desk sidebar item."]
},
{
"id": "ui.nav.admin.onboarding",
"kind": "nav",
"label": "Admin onboarding nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Admin onboarding sidebar item."]
},
{
"id": "ui.nav.admin.metrics",
"kind": "nav",
"label": "Admin metrics nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Admin metrics sidebar item."]
},
{
"id": "ui.nav.admin.settings",
"kind": "nav",
"label": "Admin settings nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Admin settings sidebar item."]
},
{
"id": "ui.nav.admin.switchToApp",
"kind": "nav",
"label": "Switch to app nav",
"sourceFiles": ["apps/web/app/application/sidebar/AdminSidebar.tsx"],
"renderer": "AdminSidebar/NavMain",
"notes": ["Admin sidebar app switch item."]
},
{
"id": "ui.nav.docs.introduction",
"kind": "nav",
"label": "Docs introduction nav",
"sourceFiles": ["apps/web/app/application/sidebar/DocsSidebar.ts"],
"renderer": "DocsSidebar/NavMain",
"notes": ["Docs introduction navigation item."]
}
]
}
@@ -0,0 +1,51 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://toolctrl.local/ui-surfaces.schema.json",
"title": "Toolctrl UI Surface Map",
"type": "object",
"additionalProperties": false,
"required": ["schemaVersion", "surfaces"],
"properties": {
"schemaVersion": {
"type": "integer",
"const": 1
},
"surfaces": {
"type": "array",
"items": { "$ref": "#/$defs/surface" }
}
},
"$defs": {
"surface": {
"type": "object",
"additionalProperties": false,
"required": ["id", "kind", "label", "sourceFiles", "renderer", "notes"],
"properties": {
"id": {
"type": "string",
"pattern": "^ui\\.(surface|action|nav)\\."
},
"kind": {
"type": "string",
"enum": ["surface", "action", "nav"]
},
"label": {
"type": "string",
"minLength": 1
},
"sourceFiles": {
"type": "array",
"items": { "type": "string" }
},
"renderer": {
"type": "string",
"minLength": 1
},
"notes": {
"type": "array",
"items": { "type": "string" }
}
}
}
}
}