# COMPONENT-INVENTORY.md — Component Inventory (Phase 1) Run scope: `ui/src/components/` and `ui/src/pages/` on branch `design/token-extraction`. Read-only audit — no source files modified. **All consolidation, merge, and shadcn-swap recommendations in this document are RECOMMENDATIONS ONLY.** Per `GOAL-PROMPT.md` and `DESIGN.md`, no component merges, deletions, or swaps happen in this run. They become human-approved follow-up runs ("Run 2"/"Run 3"). ## Counts Execution recovery reuses the existing transcript header and task status. Routine phases add no list badges, status cards, or reconciliation dialogs. Only a transient reconnection changes the header text. Automatic recovery decisions remain in the local run log. Storybook **Tasks / Execution recovery** demonstrates quiet task lists, native and legacy transcript headers, and dashboard composition. | Area | Count | |---|---:| | Shared primitives (`ui/src/components/ui/`) | 24 | | Feature components, flat (`ui/src/components/*.tsx`) | 178 | | Feature components, nested subdirs (`access/`, `artifacts/`, `environment-variables-editor/`, `interrupt-handoff/`, `issue-output/`, `issue-properties/`, `routine-sections/`, `search/`, `timeline/`, `transcript/`) | 28 | | **Feature components total** | **206** | | Pages (`ui/src/pages/`, incl. `pages/secrets/`) | 73 | | **Grand total** | **303** (roughly matches DESIGN.md's "24 + ~277") | --- ## 1. Shared primitives — `ui/src/components/ui/` (24) All 24 checked against the live shadcn registry via `npx shadcn@latest diff` (network-available in this environment). Aggregate `diff` and per-component spot checks (`button`, `dialog`) both returned **"No updates found"** — these are currently in sync with the upstream registry source. | Component | File | Registry name | Variants (props) | Purpose | |---|---|---|---|---| | Alert Dialog | `alert-dialog.tsx` | `alert-dialog` | (Radix primitive passthrough) | Confirm/destructive-action modal | | Avatar | `avatar.tsx` | `avatar` | `size` (sm/default), `shape` (circle/square via `data-shape`) | User/agent avatar with fallback initials | | Badge | `badge.tsx` | `badge` | `variant` (default/secondary/destructive/outline) | Generic pill label | | Breadcrumb | `breadcrumb.tsx` | `breadcrumb` | (Radix/plain nav passthrough) | Page breadcrumb trail | | Button | `button.tsx` | `button` | `variant` (default/destructive/outline/secondary/ghost/link), `size` (default/sm/lg/icon/icon-sm/icon-xs) | CTA / action button, all tiers | | Card | `card.tsx` | `card` | Header/Title/Description/Content/Footer subparts | Bordered content container | | Checkbox | `checkbox.tsx` | `checkbox` | (Radix passthrough) | Boolean input | | Collapsible | `collapsible.tsx` | `collapsible` | (Radix passthrough) | Expand/collapse section | | Command | `command.tsx` | `command` | Dialog/Input/List/Item/Group/Separator/Shortcut | ⌘K palette primitive (backs `CommandPalette`) | | Dialog | `dialog.tsx` | `dialog` | (Radix passthrough), expandable max-width transition | Modal dialog | | Dropdown Menu | `dropdown-menu.tsx` | `dropdown-menu` | Item/CheckboxItem/RadioItem/Sub/Separator/Shortcut | Context/action menu | | Input | `input.tsx` | `input` | (native input passthrough) | Text input | | Label | `label.tsx` | `label` | (Radix passthrough) | Form field label | | Popover | `popover.tsx` | `popover` | (Radix passthrough) | Floating panel | | Radio Card | `radio-card.tsx` | **not a standard registry name** | (custom, card-shaped radio option) | Large selectable option card (onboarding/settings pickers) | | Scroll Area | `scroll-area.tsx` | `scroll-area` | (Radix passthrough) | Styled scroll container | | Select | `select.tsx` | `select` | (Radix passthrough) | Dropdown select | | Separator | `separator.tsx` | `separator` | `orientation` | Divider line | | Sheet | `sheet.tsx` | `sheet` | `side` (top/right/bottom/left) | Slide-in drawer | | Skeleton | `skeleton.tsx` | `skeleton` | (plain div passthrough) | Loading placeholder | | Tabs | `tabs.tsx` | `tabs` | (Radix passthrough) | Tab navigation | | Textarea | `textarea.tsx` | `textarea` | (native passthrough) | Multi-line text input | | Toggle Switch | `toggle-switch.tsx` | **not a standard registry name** (registry has `switch`) | (custom on/off toggle) | Boolean toggle control | | Tooltip | `tooltip.tsx` | `tooltip` | (Radix passthrough) | Hover/focus hint | **Note:** `radio-card` and `toggle-switch` are not standard shadcn registry component names (the registry ships `radio-group` and `switch` respectively) — these were custom-built or heavily renamed/adapted rather than installed from the registry, so `shadcn diff` cannot check them against an upstream source. See shadcn-candidates section 4c. **Stock-value note:** several "arbitrary Tailwind bracket values" flagged in `TOKEN-AUDIT.md` inside these primitives (`checkbox.tsx` `rounded-[4px]`, `tooltip.tsx` `rounded-[2px]`, `command.tsx` `max-h-[300px]`, `avatar.tsx` `text-[10px]`) were verified to match the **current shadcn/ui registry source verbatim** — they are not local drift, just registry boilerplate that itself doesn't route through a token layer. Phase 2 will still need to touch them (DESIGN.md's gate is "zero arbitrary values in `ui/src/components/**`" with no carve-out for `components/ui/`), but they are not evidence of local customization. --- ## 2. Feature components — `ui/src/components/` (206) Grouped by rough domain area. One line each; variants column is props-based where notable, blank where the component is largely propless/single-purpose. ### 2.1 Issue / task surfaces (largest cluster) | Component | Purpose | |---|---| | `IssueRow.tsx` | Single row in a task list (status glyph, title, chips) | | `IssuesList.tsx` | List/board container rendering many `IssueRow`s, sort/filter/group | | `IssueColumns.tsx` | Column-layout config for the issues list/board | | `IssueGroupHeader.tsx` | Section header when list is grouped (by status/project/assignee) | | `IssueProperties.tsx` | 1-line re-export barrel → `issue-properties/IssueProperties.tsx` | | `issue-properties/IssueProperties.tsx` | Full task detail properties panel (2,301 lines) — status, assignee, labels, project, dates | | `IssueChatThread.tsx` | Task comment/chat thread (agent + human messages) | | `IssueThreadInteractionCard.tsx` | Rich interaction card embedded in the chat thread (approvals, tool calls) | | `IssueRecoveryActionCard.tsx` | Recovery-action prompt card in a stalled/errored task thread | | `IssueScheduledRetryCard.tsx` | Scheduled-retry status card in task thread | | `IssueRunLedger.tsx` | Run/cost ledger table for a task | | `IssueMonitorActivityCard.tsx` | Monitoring/activity summary card on a task | | `IssueBlockedNotice.tsx` | Banner when a task is blocked | | `IssueAssignedBacklogNotice.tsx` | Banner when a backlog task gets assigned | | `IssueDocumentAnnotations.tsx` / `issue-output/` variants | Doc-annotation highlight overlay on task documents | | `IssueDocumentsSection.tsx` | Documents tab/section on task detail | | `IssueAttachmentsSection.tsx` | Attachments tab/section on task detail | | `IssuePlanDecompositionsSection.tsx` | Sub-task/decomposition list section | | `IssueRelatedWorkPanel.tsx` | Related-issues panel | | `IssueReferenceActivitySummary.tsx` / `IssueReferencePill.tsx` | Inline task-reference chip + activity rollup | | `IssueSiblingNavigation.tsx` | Prev/next sibling task nav | | `IssueLinkQuicklook.tsx` / `IssuesQuicklook.tsx` | Hover-preview popover for a linked task / task list | | `IssueFiltersPopover.tsx` | Filter builder popover for the issues list | | `IssueWorkspaceCard.tsx` | Card summarizing a task's execution workspace | | `IssueContinuationHandoff.tsx` | Handoff-to-next-run card | | `NewIssueDialog.tsx` | Create-task dialog | ### 2.2 Agent / execution | Component | Purpose | |---|---| | `AgentCapsule.tsx` | The brand "capsule" motif — 3-state agent avatar (slot/configured/online) | | `AgentActionButtons.tsx` | Start/stop/pause agent action row | | `AgentBubbleActionRow.tsx` | Action row (copy/vote/timestamp/menu) under a conf-room agent chat bubble — **only one implementation found**, confirming PRIOR-ART's concern about a duplicate is resolved | | `AgentConfigForm.tsx` | Agent configuration form (model, instructions, etc.) | | `AgentIconPicker.tsx` | Icon picker for agent avatars | | `AgentProperties.tsx` | Agent detail properties panel | | `ActiveAgentsPanel.tsx` | Sidebar/dashboard panel of currently-running agents | | `LiveRunWidget.tsx` | Live run status widget | | `RunChatSurface.tsx` | Shared chat-surface shell used by both conf-room and task chat | | `ClaudeSubscriptionPanel.tsx` / `CodexSubscriptionPanel.tsx` | Provider-specific subscription/quota panels — parallel, provider-specific (not a duplicate; see 3.4) | | `ProviderQuotaCard.tsx` / `QuotaBar.tsx` | Generic quota display card / bar | ### 2.3 Chat / composer | Component | Purpose | |---|---| | `ChatComposer.tsx` (380 lines) | Shared lean composer (conf-room + reused where a single-line/simple composer suffices) | | `MarkdownEditor.tsx` (1,425 lines) | Rich MDX-based editor with mention autocomplete — task comment composer | | `OnboardingChat.tsx` | Onboarding-flow chat surface | | `CommentThread.tsx` | Generic comment-thread renderer (non-task contexts) | | `MarkdownBody.tsx` / `WorkspaceFileMarkdownBody.tsx` | Rendered markdown output (read-only) — general vs. workspace-file-scoped | **KNOWN-DUPLICATES.md lead verified:** `ChatComposer` vs `MarkdownEditor`-based task composer — confirmed genuinely different in scope (380 vs 1,425 lines; ChatComposer has no mention-autocomplete/MDX machinery). Per KNOWN-DUPLICATES.md this was a deliberate non-unification (PAP-101) — **flagged in section 5, not recommended for merge.** ### 2.4 Status / chips / badges | Component | Purpose | |---|---| | `StatusIcon.tsx` | Interactive status glyph + popover to change status (wraps `StatusGlyph`) | | `StatusGlyph.tsx` | Pure SVG glyph renderer per status (sm/md/lg), no interactivity | | `StatusBadge.tsx` | Custom `` pill components: `StatusBadge` (generic run/goal/approval), `AgentStatusBadge`, heartbeat capsule — **does not wrap the shadcn `Badge` primitive** (see shadcn candidates 4a) | | `PriorityIcon.tsx` | Priority-level icon | | `ExternalObjectStatusIcon.tsx` / `ExternalObjectStatusSummary.tsx` / `ExternalObjectPill.tsx` | External-object (linked PR/doc/etc.) status glyph, rollup summary, and inline pill — a third, deliberately separate status-presentation family | | `BlockedReasonChip.tsx` | Chip explaining why a task is blocked | | `SourceTrustBadge.tsx` / `SourceResolvedFoldBadge.tsx` / `SourceResolvedFoldCallout.tsx` | Trust/fold badges for external content sources | | `ProductivityReviewBadge.tsx` | Review-status badge | **KNOWN-DUPLICATES.md lead verified:** StatusIcon / inline-mention chips / task chips are intentionally three separate systems (StatusIcon+StatusGlyph = task status glyph family; `ExternalObjectStatusIcon`/`Pill`/`Summary` = a second, external-object-specific family; mention chips in `lib/mention-chips.ts` + markdown CSS = a third, generic "chip in prose" family). **Documented here per instruction, not merged.** ### 2.5 Sidebar / navigation | Component | Purpose | |---|---| | `Sidebar.tsx` / `SidebarShell.tsx` / `SecondarySidebar.tsx` / `InstanceSidebar.tsx` | Primary/secondary/instance-scoped sidebar shells | | `SidebarSection.tsx` / `SidebarNavItem.tsx` | Sidebar section grouping + nav item row | | `SidebarProjects.tsx` / `SidebarStarredProjects.tsx` | Project list / starred-project list in sidebar | | `SidebarAgents.tsx` | Agent list in sidebar | | `SidebarAccountMenu.tsx` / `SidebarCompanyMenu.tsx` | Account and company switcher menus | | `SidebarServerInfo.tsx` | Server/instance info footer | | `RequestCollapsedSidebar.tsx` | Collapsed-state sidebar for request/approval views | | `RoutineSubSidebar.tsx` | Routine-scoped sub-sidebar | | `MobileBottomNav.tsx` | Mobile bottom tab bar | | `BreadcrumbBar.tsx` (uses `ui/breadcrumb`) | Page breadcrumb bar wrapper | | `PageTabBar.tsx` | In-page tab bar | | `CommandPalette.tsx` | ⌘K command palette (wraps `ui/command`) | **Sidebar text-size cluster** (cross-ref TOKEN-AUDIT.md 3.1): `Sidebar.tsx`, `SidebarNavItem.tsx`, `SidebarAgents.tsx`, `SidebarProjects.tsx`, `SidebarStarredProjects.tsx`, `SidebarAccountMenu.tsx` all independently use `text-[13px]` — six components implementing what looks like one shared "sidebar row label" intent as six separate arbitrary values. Flagged for the human type-scale decision, not merged here. ### 2.6 Pipelines / routines / goals | Component | Purpose | |---|---| | `PipelineHealthWarnings.tsx` / `PipelineLivenessBanner.tsx` | Pipeline health/liveness banners | | `PipelineItemBodyDocument.tsx` / `PipelineStageHistoryPanel.tsx` / `PipelineWorkReferences.tsx` | Pipeline stage detail panels | | `PipelinesExperimentalGate.tsx` | Feature-flag gate wrapper for pipelines | | `RoutineList.tsx` / `ManagedRoutinesList.tsx` | Routine list views | | `RoutineActivityRow.tsx` / `RoutineHistoryTab.tsx` | Routine activity row + history tab | | `RoutineRunVariablesDialog.tsx` / `RoutineVariablesEditor.tsx` | Routine run-variable input dialog/editor | | `RoutineSaveBar.tsx` / `RoutineTriggerCard.tsx` | Routine save-bar + trigger config card | | `routine-sections/editable-sections.tsx` (+2 more) | Editable routine-section blocks | | `ScheduleEditor.tsx` | Cron/schedule editor | | `GoalTree.tsx` / `GoalProperties.tsx` / `NewGoalDialog.tsx` | Goal hierarchy tree, detail panel, create dialog | ### 2.7 Finance / budget | Component | Purpose | |---|---| | `AccountingModelCard.tsx` / `BillerSpendCard.tsx` / `FinanceBillerCard.tsx` / `FinanceKindCard.tsx` / `FinanceTimelineCard.tsx` | Various finance-dashboard cards — 5 distinct card shapes for different finance groupings (model/biller/kind/timeline); worth a human check for whether all 5 are truly distinct or 2-3 could share a base card | | `BudgetIncidentCard.tsx` / `BudgetPolicyCard.tsx` / `BudgetSidebarMarker.tsx` | Budget incident, policy config, sidebar marker | | `MetricCard.tsx` | Generic metric-display card | ### 2.8 Files / documents / artifacts | Component | Purpose | |---|---| | `FileTree.tsx` | General file-tree renderer + `buildFileTree`/`FileTreeNode` model | | `PackageFileTree.tsx` | Thin wrapper around `FileTree` (`wrapLabels` default) — reuses `FileTreeProps`, not a duplicate | | `WorkspaceFileBrowser.tsx` | Execution-workspace file browser — **defines its own parallel tree-node model** (`WorkspaceFileTreeNode`, `buildWorkspaceFileTree`, `compareTreeNodes`) instead of reusing `FileTree`'s `FileTreeNode`/`buildFileTree` — see suspected duplicates (3.2) | | `FileViewerSheet.tsx` | File content viewer sheet (wraps `ui/sheet`) | | `FileTree.tsx`'s `parseFrontmatter` | Shared frontmatter parser (also exported, reused elsewhere) | | `DocumentAnnotationLayer.tsx` / `DocumentAnnotationPanel.tsx` | Document highlight/annotation overlay + side panel | | `DocumentDiffModal.tsx` | Document diff viewer modal | | `DocumentFrameHeader.tsx` | Header chrome for an embedded document frame | | `ArtifactFileChip.tsx` / `artifacts/ArtifactCard.tsx` / `artifacts/ArtifactGroupCard.tsx` / `ArtifactsPanel.tsx` | Artifact chip, card, grouped-card, and panel — internally consistent `rounded-[8px]` cluster (TOKEN-AUDIT 3.5) | | `ImageGalleryModal.tsx` | Image gallery lightbox modal | ### 2.9 Onboarding / access / identity | Component | Purpose | |---|---| | `OnboardingWizard.tsx` / `OnboardingWizardVariant.tsx` | Onboarding flow + an alternate variant — worth a human check on whether the variant is still live or leftover from an A/B (see 3.5) | | `FrontDoor.tsx` | Landing/entry gate component | | `CloudAccessGate.tsx` / `ConferenceRoomChatGate.tsx` / `PipelinesExperimentalGate.tsx` | Three separate feature-flag/access gate wrappers — same pattern (children-if-enabled), each hand-rolled per feature; candidate for a shared `FeatureGate` primitive (flagged, not built here) | | `Identity.tsx` | Avatar + name identity chip (`deriveInitials` helper) | | `MembershipAction.tsx` | Membership accept/decline action row | | `access/` (2 files) | Access-request related components | | `ReportsToPicker.tsx` / `ExecutionParticipantPicker.tsx` / `InlineEntitySelector.tsx` / `SearchableSelect.tsx` | Four distinct entity-picker components — overlapping purpose (pick a person/entity from a list); see suspected duplicates (3.3) | | `SecretBindingPicker.tsx` / `environment-variables-editor/` (5 files) | Secrets/env-var binding UI | ### 2.10 System / chrome / misc | Component | Purpose | |---|---| | `Layout.tsx` | App shell layout | | `EmptyState.tsx` | **The single canonical empty-state component** — confirmed only one exists, matching DESIGN.md principle 1 | | `SystemNotice.tsx` | Global system notice banner | | `DevRestartBanner.tsx` | Dev-mode restart-required banner | | `ToastViewport.tsx` + `context/ToastContext.tsx` | Fully custom toast notification system — no shadcn `sonner`/toast primitive installed (see shadcn candidates 4a) | | `KeyboardShortcutsCheatsheet.tsx` | ⌘K-adjacent shortcuts help modal | | `PageSkeleton.tsx` | Loading skeleton (wraps `ui/skeleton`) | | `RouteErrorBoundary.tsx` | Route-level error boundary | | `ThemeToggle.tsx` | Light/dark toggle | | `CopyText.tsx` | Copy-to-clipboard text control | | `EntityRow.tsx` | Generic entity list row (used across several list contexts) | | `SwipeToArchive.tsx` | Mobile swipe-to-archive gesture wrapper | | `ScrollToBottom.tsx` | Scroll-to-bottom floating button | | `FoldCurtain.tsx` | Collapsible "show more" curtain/fade | | `InlineEditor.tsx` | Generic inline-edit-in-place control | | `JsonSchemaForm.tsx` | JSON-schema-driven dynamic form renderer | | `TrustPresetSection.tsx` | Trust-level preset picker section | | `WorktreeBanner.tsx` | Worktree-branding banner | | `CompanyPatternIcon.tsx` | Canvas-rendered company pattern/avatar icon | | `CompanySwitcher.tsx` / `CompanySettingsSidebar.tsx` | Company switcher + settings sidebar | | `ProjectProperties.tsx` / `ProjectTile.tsx` / `ProjectWorkspaceSummaryCard.tsx` / `ProjectWorkspacesContent.tsx` | Project detail panel, tile, workspace summary card, workspace content | | `ApprovalCard.tsx` / `ApprovalPayload.tsx` | Approval request card + payload renderer | | `ExecutionWorkspaceCloseDialog.tsx` | Close-workspace confirm dialog | | `ResponsibleUserDenialNotice.tsx` | Denial-notice banner | | `BootstrapPendingPage.tsx` | Bootstrap-pending full-page state | | `MissingPluginTabPlaceholder.tsx` | Placeholder when a plugin tab isn't installed | | `StandaloneBrowserControls.tsx` | Standalone-mode browser chrome controls | | `AsciiArtAnimation.tsx` | Decorative ASCII animation (boot/loading) | | `OpenCodeLogoIcon.tsx` | Static logo icon | | `StarToggle.tsx` | Star/favorite toggle button | | `OutputFeedbackButtons.tsx` | Thumbs up/down feedback buttons on agent output | | `KanbanBoard.tsx` | Kanban board view (alternate to `IssuesList` list view) | | `agent-config-primitives.tsx` | Shared field primitives for agent config forms | | `PropertiesPanel.tsx` | Generic properties-panel shell (used by Issue/Project/Agent/Goal Properties) | ### 2.11 Nested subdirectories | Directory | Files | Purpose | |---|---|---| | `access/` | 2 | Access-request UI | | `artifacts/` | 2 | `ArtifactCard`, `ArtifactGroupCard` | | `environment-variables-editor/` | 5 | Env-var/secret editor rows and index | | `interrupt-handoff/` | 1 | `InterruptHandoffViews.tsx` | | `issue-output/` | 5 | Task output file tiles / sections | | `issue-properties/` | 5 | Task properties panel (full impl, see 2.1) | | `routine-sections/` | 3 | Editable routine section blocks | | `search/` | 3 | Search result row, `HighlightedText`, `MatchSourceChip` | | `timeline/` | 1 | `WorkTimelineChart.tsx` | | `transcript/` | 1 | `RunTranscriptView.tsx` | --- ## 3. Pages — `ui/src/pages/` (73) Grouped by area; one line each. | Page | Purpose | |---|---| | `Dashboard.tsx` | Home/overview dashboard | | `IssueDetail.tsx` / `IssuesList` route pages | Task detail + list routes | | `AgentDetail.tsx` / `Agents.tsx` | Agent detail + list | | `ProjectDetail.tsx` / `Projects.test.tsx`-adjacent list page | Project detail + list | | `Pipelines.tsx` / `PipelineSettings.tsx` | Pipeline list + settings | | `Routines.tsx` | Routine list/detail | | `Org.tsx` / `OrgChart.tsx` | Org directory + org chart visualization | | `Costs.tsx` | Cost/budget dashboard | | `Timeline.tsx` | Work timeline view (wraps `timeline/WorkTimelineChart`) | | `Inbox.tsx` | Notifications/requests inbox | | `Approvals.tsx` / `ApprovalDetail.tsx` | Approval queue + detail | | `CompanySettings.tsx` / `CompanySkills.tsx` / `CompanyEnvironments.tsx` / `CompanyAccess.tsx` / `CompanyImport.tsx` / `CompanyExport.tsx` | Company-scoped settings sub-pages | | `TeamCatalog.tsx` | Team/role catalog | | `UserProfile.tsx` / `ProfileSettings.tsx` | User profile view + settings | | `Secrets.tsx` / `secrets/*` (5 files) | Secrets management + sub-tabs (import-from-vault, definitions, my-secrets, presentation helpers, missing-banner) | | `PluginManager.tsx` / `PluginSettings.tsx` | Plugin management + per-plugin settings | | `AdapterManager.tsx` | Adapter (agent runtime) management | | `CloudUpstream.tsx` / `CloudUpstreamUxLab.tsx` | Cloud-upstream connection page + its UxLab showcase twin | | `InviteLanding.tsx` / `InviteUxLab.tsx` | Invite acceptance landing page + UxLab showcase twin | | `BootstrapSetupUxLab.tsx` | Bootstrap-setup flow showcase | | `ResponsibleUserDenialUxLab.tsx` | Denial-flow showcase | | `SystemNoticeUxLab.tsx` | System-notice showcase | | `IssueChatUxLab.tsx` / `RunTranscriptUxLab.tsx` | Chat + transcript showcases | | `DesignGuide.tsx` | Design-system showcase/reference page | | `BoardChat.tsx` / `BoardClaim.tsx` | Board (concierge) chat + claim flow | | `NotFound.tsx` | 404 page | | `CliAuth.tsx` | CLI auth handoff page | | `JoinRequestQueue.tsx` | Join-request queue | | `InstanceAccess.tsx` / `InstanceGeneralSettings.tsx` / `InstanceExperimentalSettings.tsx` | Instance-level settings pages | | `ExecutionWorkspaceDetail.tsx` / `ProjectWorkspaceDetail.tsx` | Execution/project workspace detail pages | | `Companies.tsx` | Multi-company switcher/list | | `CompanyImport.tsx` / `CompanyExport.tsx` | Import/export company data | | `RoutineDetail.tsx` | Routine detail route | **UxLab pages are real routes** (confirmed via `App.tsx` — `/ux-lab/*`, `/design-guide`), not build-excluded demo code, so all hardcoded values inside them are in-scope per DESIGN.md even though they're showcase surfaces rather than product screens a typical operator visits daily. --- ## 4. Shadcn candidates **All items below are recommendations only — no swaps happen in this run.** ### 4a. Custom components duplicating an available shadcn primitive | Custom component | Duplicates | Recommended replacement | Expected visual impact | |---|---|---|---| | `ToastViewport.tsx` + `context/ToastContext.tsx` | shadcn's `sonner`-based toast pattern (not currently installed) | Install `sonner`/toast primitive, migrate `useToastActions`/`useToastState` call sites to it | Low if the registry's toast is restyled to match current `toneClasses`/`toneDotClasses` tint system; the current implementation already has custom tone colors (`sky`/`emerald`/`amber`/`red`) that would need to carry over as variant props — a naive swap would look different unless those tones are preserved | | `components/StatusBadge.tsx` (`StatusBadge`, `AgentStatusBadge`) | shadcn `Badge` primitive (installed, `ui/badge.tsx`) | Keep as a distinct component (status badges need the `.status-chip` color-mix mechanic Badge's variant system doesn't support) but consider having it render `` internally with a custom class rather than a bare ``, for consistency of base styles (focus ring, disabled states, etc.) | Low — internal implementation change only if done carefully; skip if it risks the WCAG-tuned status hues | | Hand-rolled bordered-container `
`/`
    ` patterns (`rounded-md border bg-card`-shaped) found in ~26 files (`pages/AdapterManager.tsx`, `pages/TeamCatalog.tsx`, `pages/InstanceAccess.tsx`, `pages/BootstrapSetupUxLab.tsx`, `pages/ResponsibleUserDenialUxLab.tsx`, `pages/CliAuth.tsx`, `pages/BoardClaim.tsx`, `pages/BoardChat.tsx`, `pages/InstanceGeneralSettings.tsx`, `pages/ProjectWorkspaceDetail.tsx`, `pages/Timeline.tsx`, `pages/PluginManager.tsx`, `pages/JoinRequestQueue.tsx`, `pages/InstanceExperimentalSettings.tsx`, `App.tsx`, `components/BootstrapPendingPage.tsx`, `components/IssuePlanDecompositionsSection.tsx`, `components/BlockedInboxView.tsx`, `components/ApprovalCard.tsx`, + ~7 more) vs. shadcn `Card` (installed, only imported in 21 files) | `Card`/`CardContent` | Low-to-medium — many of these are `
      ` list wrappers, not literal card content; a swap would need per-site judgment, not a blanket codemod. Flagging the cluster, not asserting every site should change. | | Hand-rolled pill/badge-shaped ``s (`rounded-full px-2 text-[...]`) outside `Badge` usage, ~34 files | `Badge` (installed, 35 files already use it) | `Badge` with a custom `className` for color | Low if colors are preserved as `className` overrides | | `plugins/launchers.tsx` generic plugin-shell overlay (`role="dialog"`, hand-rolled backdrop `bg-black/45`, manual z-index math, `rounded-xl`/`rounded-2xl`) | `Dialog` / `Sheet` / `Popover` (all installed) | Case-by-case: this component multiplexes dialog/drawer/popover shell types from one plugin-host abstraction, which none of the three installed primitives do individually — a clean swap likely means keeping the multiplexer but delegating each `shellType` branch to the matching installed primitive instead of a fully custom `
      ` tree | Medium — this is the most structurally custom overlay in the codebase; recommend closer human review before treating it as a simple swap | ### 4b. Installed shadcn components drifted from the registry `npx shadcn@latest diff` (run from `ui/`, network available) reported **"No updates found"** for the aggregate diff and for spot-checked `button` and `dialog` individually. **No drift detected** against the current registry for any of the 22 standard-named primitives. (`radio-card` and `toggle-switch` aren't standard registry names so `diff` cannot evaluate them — see 4c.) ### 4c. Raw Radix/plain elements where an installed shadcn wrapper exists - **No raw `@radix-ui/*` imports were found outside `ui/src/components/ui/`** (`rg -l '@radix-ui' -g '*.tsx' -g '!components/ui/*'` returned zero files) — every Radix primitive in the app is properly routed through the `components/ui/` wrapper layer. This is a clean result; no action needed. - One raw hand-rolled modal (`plugins/launchers.tsx`, `role="dialog"` on a plain `
      `) exists where `Dialog`/`Sheet` wrappers are installed — see 4a above, same finding, cross-listed here because it's also a "plain element where a wrapper exists" case. - `radio-card.tsx` and `toggle-switch.tsx` are functioning as de facto custom primitives sitting in the `ui/` folder alongside real shadcn components, but they were not installed via the registry (no matching registry names). Recommend a human decide whether to (a) leave them as intentionally custom, documenting why the registry's `radio-group`/`switch` don't fit, or (b) evaluate swapping to the registry versions if the customization was incidental rather than deliberate. --- ## 5. Suspected duplicates (with evidence) All of the below are **leads for human review**, not verdicts, per KNOWN-DUPLICATES.md's framing. ### 5.1 Seed leads from KNOWN-DUPLICATES.md — verified | Lead | Finding | |---|---| | `ChatComposer` vs `MarkdownEditor`-based task composer | **Confirmed genuinely distinct** (380 vs 1,425 lines; different prop interfaces `ChatComposerProps` vs `MarkdownEditorProps`; MarkdownEditor owns mention-autocomplete machinery — `findMentionMatch`, `computeMentionMenuPosition`, `placeCaretAfterMentionAnchor` — that ChatComposer has none of). Matches KNOWN-DUPLICATES.md's note that this was a deliberate non-unification (PAP-101). **Needs human decision: re-confirm this split should remain permanent, or revisit unification now that both have matured further.** | | `AgentBubbleActionRow.tsx` duplicate check | **Only one file found** (`components/AgentBubbleActionRow.tsx`). The concurrent-work duplicate PRIOR-ART/KNOWN-DUPLICATES.md warned about is not present on this branch — resolved. | | `StatusIcon` vs inline-mention chips vs task chips | **Confirmed three separate, intentionally distinct systems**: (1) `StatusIcon`/`StatusGlyph` — task status glyph, drives from `--status-task-icon-*` tokens; (2) `ExternalObjectStatusIcon`/`ExternalObjectPill`/`ExternalObjectStatusSummary` — a parallel system for external (PR/doc/etc.) object status, independent color/severity model (`externalObjectStatusIcon`, `externalObjectStatusToneSeverity` in `lib/status-colors.ts`); (3) mention chips (`lib/mention-chips.ts` + `.paperclip-mention-chip` CSS in `index.css`) — generic "entity reference in prose" chip unrelated to status at all. Documented per instruction, **not merged.** | ### 5.2 New leads found during this audit | Lead | Evidence | |---|---| | `FileTree.tsx` vs `WorkspaceFileBrowser.tsx` tree models | `FileTree.tsx` exports `buildFileTree`, `FileTreeNode`, `countFiles`, `collectAllPaths`, `parseFrontmatter`. `WorkspaceFileBrowser.tsx` independently defines its own `WorkspaceFileTreeNode`/`WorkspaceFileTreeFolderNode`/`WorkspaceFileTreeFileNode`, `buildWorkspaceFileTree`, `compareTreeNodes`, `finalizeTreeFolder` — a parallel tree-building implementation rather than reuse of `FileTree`'s exported helpers. `PackageFileTree.tsx` by contrast correctly wraps `FileTree` (reuses `FileTreeProps`). **Needs human decision**: was `WorkspaceFileBrowser`'s separate model a deliberate choice (different node shape needs — e.g. it may need workspace-specific metadata `FileTreeNode` lacks) or copy-paste-and-diverge drift? | | `agentStatusBadge` vs `brandChipBadge` (`lib/status-colors.ts`) | Byte-for-byte identical maps for the 4 shared keys (gray/blue/amber/red — same hex, same dark-mode alpha suffixes); `brandChipBadge` additionally has `green`/`violet`. Cross-referenced in TOKEN-AUDIT.md section 1.1. **Recommend collapsing `agentStatusBadge` into `brandChipBadge`** (or making it an alias) since they are provably identical, not just similar. | | Entity-picker family: `ReportsToPicker.tsx`, `ExecutionParticipantPicker.tsx`, `InlineEntitySelector.tsx`, `SearchableSelect.tsx` | Four components with overlapping "pick one entity from a searchable list" purpose. Not verified identical (each has domain-specific filtering — reports-to org hierarchy, execution participants, generic inline selection, generic searchable select) but the prop-surface overlap (all take an options list + selected value + onChange) is a plausible consolidation candidate. **Flagged, not verified as true duplicates** — would need a closer prop-by-prop diff in a follow-up run. | ### 5.3 Corrections after closer inspection - `OnboardingWizardVariant.tsx` (10 lines) is **not** a duplicate of `OnboardingWizard.tsx` (1,786 lines) — it's a thin routing wrapper (`export function OnboardingWizardVariant() { return ; }`) left over from a since-retired experimental-flag variant (per its own doc comment: "Conference-room chat is now the only surface left behind `enableConferenceRoomChat`; onboarding stays available without that experimental flag"). Both are routed in `App.tsx`. **No action needed** — this is a naming leftover, not visual/logic duplication; candidate for a trivial rename-and-inline cleanup in a future non-visual refactor, out of scope here. - `components/IssueProperties.tsx` is a 1-line re-export barrel (`export { IssueProperties } from "./issue-properties";`) — not a duplicate of `components/issue-properties/IssueProperties.tsx`, just a compatibility import path. ### 5.4 Finance card family — needs closer human review `AccountingModelCard.tsx`, `BillerSpendCard.tsx`, `FinanceBillerCard.tsx`, `FinanceKindCard.tsx`, `FinanceTimelineCard.tsx` — five components with card-shaped, finance-dashboard purposes and naming that overlaps enough (`BillerSpendCard` vs `FinanceBillerCard`) to warrant a closer look than this audit had time for. Not verified as duplicates; flagged as a lead only. ### 5.5 Feature-gate wrapper pattern (not a duplicate, but a repeated pattern) `CloudAccessGate.tsx`, `ConferenceRoomChatGate.tsx`, `PipelinesExperimentalGate.tsx` each independently implement the same "render children only if flag X is enabled, else render fallback" shape. Not byte-identical (each checks a different flag/hook), so not a strict duplicate, but a strong candidate for a shared `FeatureGate`/`ExperimentalGate` primitive that takes a flag-check function as a prop. Flagged as a recommendation, not built here. --- ## 6. Needs human decision (required section) 1. **`ChatComposer` vs `MarkdownEditor`-based composer** — KNOWN-DUPLICATES.md already flags this as deliberately unmerged (PAP-101). This audit confirms the split is real (not accidental) given the large capability gap. Human call: keep permanently split, or revisit now. **DECIDED (Run 2 review, DECISION-SHEET.md C1): keep the split, documented as deliberate — user re-confirmed after visual review.** 2. **`FileTree.tsx` vs `WorkspaceFileBrowser.tsx` independent tree models** — is `WorkspaceFileBrowser`'s separate `WorkspaceFileTreeNode` model justified by different data needs, or is it drift that should be refactored onto `FileTree`'s exported `buildFileTree`/`FileTreeNode`? Needs someone who knows both call sites' actual data shapes. **DECIDED (Run 2 review, DECISION-SHEET.md C2): investigate data-shape needs as Run 3 prep; refactor onto FileTree only if shapes align.** **RESOLVED (Run 3 investigation): KEEP SEPARATE — shapes do not align; see §7.1.** 3. **`agentStatusBadge` vs `brandChipBadge`** (`lib/status-colors.ts`) — these are provably byte-identical for their 4 shared keys. Recommend collapsing, but doing so touches every call site importing `agentStatusBadge`, so it's a human-approved Run 2/3 item, not automatic. **RESOLVED (Run 2 review, DECISION-SHEET.md A1): collapsed — `agentStatusBadge` had zero importing call sites and was deleted; `brandChipBadge` is the single map.** 4. **Entity-picker family** (`ReportsToPicker`, `ExecutionParticipantPicker`, `InlineEntitySelector`, `SearchableSelect`) — plausible consolidation candidate on prop-surface similarity alone; needs a closer prop-by-prop and behavior diff (not done in this pass) before any merge recommendation can be made with confidence. **DECIDED (Run 2 review, DECISION-SHEET.md C3): prop-by-prop diff queued as Run 3 prep; no merge without it.** **RESOLVED (Run 3 investigation): all four KEEP SEPARATE — no copy-paste drift found; see §7.2.** 5. **Finance card family** (5 components, section 5.4) — needs a domain-knowledgeable human to confirm whether all 5 are truly distinct dashboard needs or 2-3 could share a base `FinanceCard`. **DECIDED (Run 2 review, DECISION-SHEET.md C4): keep all five; revisit only if a sixth appears.** 6. **Hand-rolled card-shaped containers vs. `Card` primitive** (~26 files) and **hand-rolled pill spans vs. `Badge`** (~34 files) — both are large, low-risk-looking consolidation opportunities, but "low risk" was assessed at a glance only; a real swap pass needs per-site visual verification (this is exactly what Storybook snapshots from Phase 0 would catch if these were touched). **DECIDED (Run 2 review, DECISION-SHEET.md C5): queued for Run 3 as the shadcn-swap list, with per-site snapshot verification.** 7. **`plugins/launchers.tsx` custom multiplexed overlay** — the most structurally custom modal-like component in the app; recommend a dedicated closer look before deciding whether/how to route it through `Dialog`/`Sheet`/`Popover`, since it currently does something none of the three do alone (switch shell type per plugin action). **DECIDED (Run 2 review, DECISION-SHEET.md C6): dedicated review task; excluded from Run 3.** 8. **`radio-card.tsx` / `toggle-switch.tsx` non-standard "shadcn" primitives** — confirm whether these were deliberately custom-built (and should stay documented as such) or are stale/incidental deviations from `radio-group`/`switch` that should be swapped in a later run. **DECIDED (Run 2 review, DECISION-SHEET.md C7): deliberately custom — documented as such; no swap.** 9. **`StatusBadge`/`AgentStatusBadge` not wrapping the installed `Badge` primitive** — worth a human call on whether unifying the base markup (while keeping the custom `.status-chip` color-mix mechanic) is worth the churn, or whether the current bespoke `` approach should just be documented as an intentional, permanent exception (similar to the StatusIcon/ExternalObject/mention-chip three-way split already documented). **DECIDED (Run 2 review, DECISION-SHEET.md C8): documented as an intentional exception — the WCAG-tuned `.status-chip` mechanic stays bespoke.** 10. **Toast system has no installed shadcn primitive to compare against** — `ToastViewport`/`ToastContext` is fully custom because no `sonner`/toast component is installed at all. Human call: install one and migrate, or formally document the custom toast as the system's permanent choice (it already has a working tone/variant system). **DECIDED (Run 2 review, DECISION-SHEET.md C9): decision deferred to Run 4, when the toast's palette colors get retokenized; sonner-behind-a-pushToast-facade is the alternative to evaluate then.** --- ## 7. Run 3 investigation verdicts (C2/C3) Investigated Jul 7 2026 on `design/component-convergence` per DECISION-SHEET.md C2/C3. Both are read-only verdicts; no merges executed (neither met the "copy-paste drift with identical data shapes" bar). ### 7.1 C2 — WorkspaceFileBrowser vs FileTree tree models: KEEP SEPARATE **Deliberate divergence, not copy-paste drift.** No shared git ancestry: `git log --follow` shows FileTree.tsx originating in 3c73ed26b (plugin host surface, #5205) and WorkspaceFileBrowser.tsx independently in 468edd8b2 (workspace file viewer, #7681); no rename/copy relationship, and the only commit touching both is the mechanical token retune (c07e650cd). **Data shapes are distinct, not overlapping:** - `FileTreeNode` (FileTree.tsx:18-25): `kind: "dir"|"file"`, keyed on `path`, children on every node, optional `action` string. Built by `buildFileTree(files: Record)` — a flat path→content map fed static blobs (CompanyExport.tsx:696/762/832, CompanyImport.tsx:783/903, AgentDetail.tsx:1902). - `WorkspaceFileTreeNode` (WorkspaceFileBrowser.tsx:211-227): `kind: "folder"|"file"` (different literals), composite `key` = `kind:workspaceId:relativePath`, baked-in `depth`, folder-only `lazy` flag, and file nodes embed the entire `WorkspaceFileListFileItem` server DTO (~15 fields: capabilities, previewKind, byteSize, download URLs — packages/shared/src/types/workspace-file-resource.ts:50-94). Built from an array of server DTOs with rootPath prefix-stripping. **Behavior domains differ:** FileTree is an eager, static tree with tri-state checkboxes, badges/tones, frontmatter parsing, and a full roving-tabindex keyboard grid; WorkspaceFileBrowser is a server-paginated live browser with lazy folders, per-folder "load more" via `useQueries`, a debounced search combobox, and opposite sort order (folders-first vs FileTree's deliberate files-first, FileTree.tsx:96-99). Neither uses the other's load-bearing features; a merge would be a superficial shared-recursion abstraction both sides immediately override. ### 7.2 C3 — Entity-picker family: all four KEEP SEPARATE **No copy-paste lineage:** four independent creation commits by different efforts — InlineEntitySelector (3709901db, Feb 26), ReportsToPicker (61f53b647, Mar 20), ExecutionParticipantPicker (be518529b, Apr 7, extracted from inline pill UI), SearchableSelect (50ae8fc65, Jun 24, #8597, born cmdk-based). `git log --follow` shows no cross-file rename/copy. Per-component rationale: - **ReportsToPicker**: no search, no keyboard nav, domain `Agent[]` single-select with unique manager-state UI (terminated-manager banner, stale-ID handling, ReportsToPicker.tsx:92-104). 2 call sites. A thin bespoke widget, not a drifted copy. - **ExecutionParticipantPicker**: the only multi-select (toggle set) and the only one with async data fetching (`useQuery` user directory); encodes selections as `agent:`/`user:` tagged strings coupled to execution-policy plumbing. Not behavior-preservingly mergeable into any single-select. - **InlineEntitySelector vs SearchableSelect** (the only real candidate pair): different engines (hand-rolled input + manual Arrow/Enter/Tab keyboard machine with tab-advance chain driving NewIssueDialog's field flow, InlineEntitySelector.tsx:154-189/90-101 — vs cmdk `Command shouldFilter={false}`, SearchableSelect.tsx:246); different option shapes (`{id,label,searchText}` flat + synthetic none-row vs `{key,value,label,title,searchText,disabled}` grouped + generic ``); each has unique load-bearing features (recent-ordering/onConfirm vs createItem/deriveGroups/scoreOption/veto-close). ~33 call sites on InlineEntitySelector incl. the plugin bridge (plugins/bridge-init.ts:389/479). High-risk, low-reward rewrite — default keep-as-is holds. ### 7.3 Interactive-card affordance (Run 3 review feedback) `Card` gained an `interactive` prop — pointer cursor, quiet hover (border→foreground/20 + shadow-md lift), focus-visible ring — used when the whole card is a click target (e.g. Companies selector). Skills tiles (CompanySkills `SkillCard`) and artifact cards (`ArtifactCard`/`ArtifactGroupCard`) apply the same recipe verbatim since they cannot render through Card (button/Link semantics). Static container Cards stay affordance-free by design. ## In-task connections — 2026-09-07 | Reusable surface | Production owner | Hosts / coverage | |---|---|---| | Connection request card | `ui/src/features/connections/ConnectionIntentInteractionBody.tsx` | Task timeline, interaction card, design guide; pending, reuse, authorizing, retry, resolved, audience and error stories | | Connection setup flow | `ui/src/features/connections/ConnectionSetupFlow.tsx` | Connections page and task dialog share provider forms, OAuth, validation and additive installs | | OAuth handoff | `OAuthConnectStateScreen` in the shared setup module | Entry, starting, open window, blocked popup, closure, callback failure, retry and new-tab fallback | | Identity and agent access | `AccessStep` in the shared setup module | Personal, organization, dedicated agent, unavailable identity and loading; task host fixes install reach to the requester | | Setup completion | `ConnectionSetupCompletionScreen` in the shared setup module | Page and dialog; identity, granted agent access and enabled actions | Independently addressable examples live under `Connections/In-task connections` in Storybook. The task composer remains available while a card is pending. These components use the existing token and primitive layers.