mirror of
https://github.com/paperclipai/paperclip
synced 2026-05-05 22:52:06 +02:00
Compare commits
22 Commits
pap-3598/c
...
run-a-clau
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eec4ff855a | ||
|
|
873f15d990 | ||
|
|
ec37967dd3 | ||
|
|
859523b58b | ||
|
|
917b72aaf4 | ||
|
|
0710f166da | ||
|
|
51dd51b98a | ||
|
|
774679bf50 | ||
|
|
4996295b07 | ||
|
|
568e1c15df | ||
|
|
6b0f407943 | ||
|
|
389f8105ac | ||
|
|
78eed3b197 | ||
|
|
aecf32e60b | ||
|
|
2ede0af41f | ||
|
|
31e9a92853 | ||
|
|
0f5895e4e5 | ||
|
|
2f9f657df5 | ||
|
|
53db6095b0 | ||
|
|
3907d6dc40 | ||
|
|
1d74e78a96 | ||
|
|
0875aa54ff |
122
doc/design-system/REVIEW.md
Normal file
122
doc/design-system/REVIEW.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Paperclip DS Extraction — Review
|
||||
|
||||
- **Generated:** 2026-04-21
|
||||
- **Repo SHA:** `a26e1288b627e82c554445732c7d844648e6b5e1`
|
||||
- **Branch:** `sockmonster-ds-extraction`
|
||||
- **Discovery config:** [`_discovery.json`](./_discovery.json)
|
||||
- **Scope:** `ui/` (`@paperclipai/ui`). Plugin SDK (`packages/plugins/sdk/src/ui/`) treated as contract surface, not implementation surface.
|
||||
|
||||
This is the entry point. Everything else is linked from here. Contents are ordered by **expected human value**, not by stage.
|
||||
|
||||
---
|
||||
|
||||
## Bottom line
|
||||
|
||||
One finding sits upstream of most of the others — resolving it moves four pattern docs from "pending" to "codifiable" and unblocks the single biggest token gap.
|
||||
|
||||
> **The app has a canonical status/priority color catalog (`ui/src/lib/status-colors.ts`) that bypasses the DS token layer and uses raw Tailwind palette classes across 11 hues and ~24 status keys.** Status indicators (`StatusIcon`, `StatusBadge`, `PriorityIcon`, `agentStatusDot`), chart colors (`ActivityCharts.tsx`, hardcoded hex), budget severity indicators (`BudgetPolicyCard`, `BudgetIncidentCard`, `BudgetSidebarMarker`), and quota fills (`QuotaBar`) are **four distinct systems encoding the same red/amber/green severity concept**, none of which share DS tokens.
|
||||
|
||||
A `--signal-*` token family would collapse four surfaces onto one vocabulary and make [status-display.md](./patterns/status-display.md), [quota-display.md](./patterns/quota-display.md), and the severity-indicator pattern opportunity all codifiable. See [tokens-review.md §4](./tokens/tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds) and [patterns-review.md §6](./patterns/patterns-review.md#6-severity-indicator-3-level-health-display--pattern-opportunity).
|
||||
|
||||
Three other findings are high-value but smaller in scope:
|
||||
- **`destructive-foreground` has a buggy light-mode value** equal to `destructive` itself (would render invisible if anyone used it — nobody does, so the bug is masked). [tokens-review.md §2](./tokens/tokens-review.md#2-destructive-foreground-has-a-wrong-light-mode-value-and-is-unused)
|
||||
- **13 color tokens are dead** (all 5 `chart-*`, all 8 `sidebar-*`). Consolidating would drop color-token count from 32 → 19. [tokens-review.md §1, §3](./tokens/tokens-review.md#1-chart--tokens-are-dead)
|
||||
- **The radius scale is non-monotonic and under-specified** — 227 uses of `rounded-lg` / `rounded-xl` resolve to square corners because `--radius-lg` / `--radius-xl` = 0. Needs a founder call on whether this is intentional flat-design or a stale migration state. [tokens-review.md §Radius scale](./tokens/tokens-review.md#radius-scale--under-founder-review)
|
||||
|
||||
---
|
||||
|
||||
## Recommended review order
|
||||
|
||||
Sequenced so each step unblocks the next. Total time estimated ~2–3 hours.
|
||||
|
||||
| # | Read | Decide | Est. |
|
||||
|---|---|---|---|
|
||||
| 1 | [tokens-review.md §High-confidence drift](./tokens/tokens-review.md#high-confidence-drift-likely-should-be-fixed) | Scope the signal-token work. Confirm dead-token deletions (chart-*, sidebar-*, destructive-foreground). | **25 min** |
|
||||
| 2 | [tokens-review.md §Radius scale](./tokens/tokens-review.md#radius-scale--under-founder-review) | One call: intentional flat lg/xl, or restore a monotonic scale. | **15 min** |
|
||||
| 3 | [components-review.md §Likely duplicates](./components/components-review.md#likely-duplicates) | Nine duplicate families. For each, note "merge/keep/defer" — patterns flow from the decisions. | **30 min** |
|
||||
| 4 | [components-review.md §Plugin SDK contract gap](./components/components-review.md#plugin-sdk-contract-gap) | Choose: fulfill the 9 missing contracts, shrink them, or hybrid. | **15 min** |
|
||||
| 5 | [patterns-review.md §Variance across documented patterns](./patterns/patterns-review.md#variance-across-documented-patterns-whats-inconsistent-between-instances) | Look at the status-element variance in detail pages (four different treatments across eight pages). | **15 min** |
|
||||
| 6 | [patterns-review.md §Paperclip-domain patterns](./patterns/patterns-review.md#paperclip-domain-patterns-worth-calling-out-opportunities-not-ratified-patterns) | Reality-check the run-transcript / heartbeat / metric-cell opportunities before any codify step. | **20 min** |
|
||||
| 7 | [components-review.md §Naming inconsistencies](./components/components-review.md#naming-inconsistencies) | Lower priority — no decision required today, but at least skim. | **10 min** |
|
||||
| 8 | [components-review.md §Story coverage gaps](./components/components-review.md#story-coverage-gaps) | Shadcn primitives missing from `foundations.stories.tsx` (`collapsible`, `dropdown-menu`, `avatar`, `skeleton`, `scroll-area`) is a small, targeted fix. | **10 min** |
|
||||
|
||||
---
|
||||
|
||||
## Confidence
|
||||
|
||||
### High confidence (probably correct, spot-check only)
|
||||
|
||||
- **32 color tokens** extracted from `ui/src/index.css` (19 semantic surfaces, 5 chart, 8 sidebar).
|
||||
- **5 radius tokens**, with value + definition-site recorded.
|
||||
- **Usage counts per color and radius token** computed by unioning Tailwind-utility occurrences and `var(--token)` references across `ui/src/**/*.{ts,tsx,css}` (excluding the definition file itself). Counts are rough by intent — within ±10%.
|
||||
- **135 component files** enumerated, classified into 22 primitives / 64 composites / 47 standalones / 2 non-component utilities.
|
||||
- **104 components** cross-referenced against 14 Storybook files via import-graph parsing.
|
||||
- **50 pages** enumerated; per-page import set captured.
|
||||
- **11 plugin SDK ambient components** enumerated with host-implementation status.
|
||||
- **4 components confirmed as storybook-only** (0 production uses): `AccountingModelCard`, `AgentProperties`, `CompanySwitcher`, `ExecutionParticipantPicker`.
|
||||
|
||||
### Medium confidence (review carefully)
|
||||
|
||||
- **Duplicate-family flags.** Eight families surfaced ([components-review.md §Likely duplicates](./components/components-review.md#likely-duplicates)) are based on name parallelism and/or shared imports. The strongest signals (entity-creation dialogs, subscription panels) need a side-by-side diff to confirm merge-ability; this extraction didn't do that.
|
||||
- **`BillerSpendCard` vs `FinanceBillerCard` as likely-true-duplicate.** Flagged per directive. Not confirmed without a diff.
|
||||
- **Pattern instance counts.** The `detail-page` and `list-page` patterns were identified by import-set intersection, which is a proxy for structural similarity. A page can import a component and not actually render it in the expected position; pattern shape is inferred, not verified pixel-by-pixel.
|
||||
- **CVA variant extraction for primitives.** Parsed 3 files successfully (`button`, `badge`, one more). The rest of the primitives likely have variants that the static parser missed.
|
||||
- **The severity-indicator pattern ([patterns-review.md §6](./patterns/patterns-review.md#6-severity-indicator-3-level-health-display--pattern-opportunity)).** Named as an *opportunity*, not a ratified pattern — cross-system evidence is strong but the four systems weren't compared pixel-for-pixel; they may not actually agree on what "warning" looks like.
|
||||
- **Story coverage set.** Computed by parsing imports in `.stories.tsx` files. A component that's imported by a story but never actually rendered would falsely appear covered. Low risk given story-file structure but not validated.
|
||||
|
||||
### Low confidence (likely wrong, incomplete, or judgment-heavy)
|
||||
|
||||
- **Motion tokens.** None exist as variables — motion is inline `@keyframes` + `cubic-bezier()`. Pattern docs don't describe motion. The 5 keyframes in `index.css` are listed; their callers are not cross-referenced.
|
||||
- **Typography.** No project-local font/type tokens found — the section is near-empty because Tailwind v4 defaults carry the load plus `@tailwindcss/typography`. If there are intended type-scale conventions in components that weren't captured by token extraction, those are missed.
|
||||
- **Elevation / shadows.** No tokens, so no inventory. Ad-hoc `shadow-[…]` values across polished surfaces were enumerated in [tokens-review.md §9](./tokens/tokens-review.md#9-arbitrary-shadow-values-in-production-surfaces), but the list is not exhaustive.
|
||||
- **Prop extraction for primitives using `React.ComponentProps<"button"> & VariantProps<...>`.** The static parser looks for `*Props` interfaces; inline-type components (most shadcn primitives) get "no Props interface found" in their detail files.
|
||||
- **Per-component token consumption cross-reference.** Components/detail files don't list which specific tokens each component consumes (would require per-file class-attribute parsing). Token usage counts are global; per-component token drift is flagged only where specific drift was found.
|
||||
- **Pattern: "detail-page header."** Called out as a sub-pattern inside detail-page doc but not given its own file — instances share only 4–5 imports, not a complete shape.
|
||||
|
||||
---
|
||||
|
||||
## Known scope limitations
|
||||
|
||||
- **Plugin SDK UI.** In-scope as a contract surface (documented in [components/index.md §Plugin SDK contracts](./components/index.md#plugin-sdk-contracts-11)). Not in-scope for pattern extraction — host implementations are covered; plugin-side usage patterns are not.
|
||||
- **Low-usage components (1–2 code imports, 76 of them).** Listed in [components/index.md](./components/index.md) with status marker `📘 below-threshold`; no dedicated detail file. Per the directive: *nothing gets silently dropped*.
|
||||
- **Pattern documentation:** capped at 10 real patterns. Eleven pattern files exist because the duplicate-family directive required documenting three below-threshold pairs (subscription-panel, sidebar-menu pair inside sidebar-chrome, quota-display). Pattern opportunities surfaced in patterns-review.md are not yet pattern files.
|
||||
- **UX Lab pages (`InviteUxLab`, `IssueChatUxLab`, `RunTranscriptUxLab`).** Acknowledged prototypes with distinct visual language. Excluded from pattern extraction. Their raw-palette usage is counted in drift stats but not pursued.
|
||||
- **Hermes / adapter code.** `ui/src/adapters/` contains per-adapter config fields. Not a DS concern; skipped.
|
||||
- **Mobile treatments.** `MobileBottomNav` and `SwipeToArchive` are noted but not extracted as their own pattern. Mobile patterns appear to live inside individual list/detail pages rather than as shared primitives.
|
||||
- **Diff mode.** This is a fresh run; `doc/design-system/` did not exist before. No diff was generated. Subsequent re-runs should run in diff mode (see [ds-extraction skill §Diff mode](../../.agents/skills/ds-extraction/SKILL.md#diff-mode)).
|
||||
|
||||
---
|
||||
|
||||
## What's on disk
|
||||
|
||||
```
|
||||
doc/design-system/
|
||||
├── REVIEW.md ← you are here
|
||||
├── _discovery.json ← Stage 0 output
|
||||
├── _pages.json ← Stage 2 scratch (50 pages)
|
||||
├── _composition-graph.json ← Stage 2 scratch (135 components)
|
||||
├── _stories.json ← Stage 2 scratch (14 stories)
|
||||
├── tokens/
|
||||
│ ├── tokens.md ← canonical human-readable inventory
|
||||
│ ├── tokens.json ← machine-readable for downstream tooling
|
||||
│ └── tokens-review.md ← the high-value drift artifact
|
||||
├── components/
|
||||
│ ├── index.md ← all 135 files + 11 SDK contracts, with status markers
|
||||
│ ├── components-review.md ← duplicates, naming, token non-compliance, story gaps, SDK gap
|
||||
│ └── [ComponentName].md × 53 ← per-component detail files (3+ uses threshold)
|
||||
└── patterns/
|
||||
├── index.md
|
||||
├── patterns-review.md ← variance, opportunities, what to resolve before re-running
|
||||
├── list-page.md ← 12 instances
|
||||
├── detail-page.md ← 8 instances
|
||||
├── sidebar-chrome.md ← 6 + 2 instances
|
||||
├── finance-card.md ← 5 instances
|
||||
├── entity-properties-panel.md ← 4 + 1 instances (open Q on generic)
|
||||
├── entity-creation-dialog.md ← 4 instances
|
||||
├── status-display.md ← 3 components + catalog (pending signal tokens)
|
||||
├── entity-row.md ← 3 instances
|
||||
├── subscription-panel.md ← 2 instances (below threshold — documented)
|
||||
└── quota-display.md ← 2 instances (below threshold — documented)
|
||||
```
|
||||
|
||||
Total: **~80 files**.
|
||||
2767
doc/design-system/_composition-graph.json
Normal file
2767
doc/design-system/_composition-graph.json
Normal file
File diff suppressed because it is too large
Load Diff
229
doc/design-system/_discovery.json
Normal file
229
doc/design-system/_discovery.json
Normal file
@@ -0,0 +1,229 @@
|
||||
{
|
||||
"generated_at": "2026-04-21T00:00:00Z",
|
||||
"repo_sha": "a26e1288b627e82c554445732c7d844648e6b5e1",
|
||||
"branch": "sockmonster-ds-extraction",
|
||||
"styling": {
|
||||
"tailwind_version": "v4",
|
||||
"tailwind_config": null,
|
||||
"tailwind_config_note": "No tailwind.config.* file. Tailwind v4 CSS-first config: theme is declared via @theme inline blocks in ui/src/index.css. Build integration via @tailwindcss/vite.",
|
||||
"css_variables_file": "ui/src/index.css",
|
||||
"uses_css_variables": true,
|
||||
"uses_cva": true,
|
||||
"uses_cn_helper": true,
|
||||
"cn_helper_location": "ui/src/lib/utils.ts:6",
|
||||
"shadcn_present": true,
|
||||
"shadcn_style": "new-york",
|
||||
"shadcn_base_color": "neutral",
|
||||
"shadcn_css_variables": true,
|
||||
"shadcn_rsc": false,
|
||||
"shadcn_icon_library": "lucide",
|
||||
"shadcn_aliases": {
|
||||
"components": "@/components",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks",
|
||||
"utils": "@/lib/utils"
|
||||
},
|
||||
"shadcn_skill_path": ".agents/skills/shadcn/SKILL.md",
|
||||
"other_styling": [
|
||||
{
|
||||
"library": "@tailwindcss/typography",
|
||||
"usage": "@plugin in ui/src/index.css; prose class styling for markdown"
|
||||
}
|
||||
],
|
||||
"notes": "Tailwind v4 with shadcn/ui in new-york style. components.json present. cn() = clsx + tailwind-merge. Custom @custom-variant dark (&:is(.dark *)). No tailwindcss-animate plugin."
|
||||
},
|
||||
"tokens": {
|
||||
"color": {
|
||||
"sources": ["ui/src/index.css"],
|
||||
"authoritative_source": "ui/src/index.css",
|
||||
"definition_blocks": [
|
||||
{ "block": "@theme inline", "role": "exposes --color-* aliases to Tailwind", "line_range": "6-43" },
|
||||
{ "block": ":root", "role": "authoritative light-mode values", "line_range": "45-80" },
|
||||
{ "block": ".dark", "role": "dark-mode overrides", "line_range": "82-115" }
|
||||
],
|
||||
"count_estimate": 32,
|
||||
"categories": {
|
||||
"semantic_neutral_and_intent": [
|
||||
"background", "foreground", "card", "card-foreground", "popover", "popover-foreground",
|
||||
"primary", "primary-foreground", "secondary", "secondary-foreground",
|
||||
"muted", "muted-foreground", "accent", "accent-foreground",
|
||||
"destructive", "destructive-foreground",
|
||||
"border", "input", "ring"
|
||||
],
|
||||
"chart": ["chart-1", "chart-2", "chart-3", "chart-4", "chart-5"],
|
||||
"sidebar": [
|
||||
"sidebar", "sidebar-foreground",
|
||||
"sidebar-primary", "sidebar-primary-foreground",
|
||||
"sidebar-accent", "sidebar-accent-foreground",
|
||||
"sidebar-border", "sidebar-ring"
|
||||
]
|
||||
},
|
||||
"includes_signal_green": false,
|
||||
"value_format": "oklch",
|
||||
"dark_mode_convention": ".dark class selector via @custom-variant"
|
||||
},
|
||||
"spacing": {
|
||||
"sources": [],
|
||||
"authoritative_source": null,
|
||||
"count_estimate": 0,
|
||||
"note": "No project-local spacing tokens. Spacing uses Tailwind v4 defaults inherited from tailwindcss package."
|
||||
},
|
||||
"type": {
|
||||
"sources": [],
|
||||
"authoritative_source": null,
|
||||
"count_estimate": 0,
|
||||
"font_faces": [],
|
||||
"google_fonts": [],
|
||||
"note": "No project-local font/type tokens. Typography uses Tailwind v4 defaults + @tailwindcss/typography plugin. Markdown styling via `.paperclip-markdown` and `.paperclip-mdxeditor-content` classes with hardcoded font-size/line-height values in index.css."
|
||||
},
|
||||
"radius": {
|
||||
"sources": ["ui/src/index.css"],
|
||||
"authoritative_source": "ui/src/index.css",
|
||||
"count_estimate": 5,
|
||||
"tokens": [
|
||||
{ "name": "--radius", "value": "0", "defined_at": "ui/src/index.css:47", "scope": ":root" },
|
||||
{ "name": "--radius-sm", "value": "0.375rem", "defined_at": "ui/src/index.css:39", "scope": "@theme" },
|
||||
{ "name": "--radius-md", "value": "0.5rem", "defined_at": "ui/src/index.css:40", "scope": "@theme" },
|
||||
{ "name": "--radius-lg", "value": "0px", "defined_at": "ui/src/index.css:41", "scope": "@theme" },
|
||||
{ "name": "--radius-xl", "value": "0px", "defined_at": "ui/src/index.css:42", "scope": "@theme" }
|
||||
],
|
||||
"note": "Unusual: --radius-lg and --radius-xl are 0px while --radius-sm and --radius-md are non-zero. Likely intentional flat-design choice at the outer scale, but worth confirming. Also --radius (base, :root) = 0 used by MDXEditor integration; not part of @theme."
|
||||
},
|
||||
"motion": {
|
||||
"sources": ["ui/src/index.css"],
|
||||
"authoritative_source": null,
|
||||
"count_estimate": 0,
|
||||
"css_variable_tokens": [],
|
||||
"keyframes": [
|
||||
{ "name": "dashboard-activity-enter", "defined_at": "ui/src/index.css:228" },
|
||||
{ "name": "dashboard-activity-highlight", "defined_at": "ui/src/index.css:246" },
|
||||
{ "name": "cot-line-slide-in", "defined_at": "ui/src/index.css:272" },
|
||||
{ "name": "cot-line-slide-out", "defined_at": "ui/src/index.css:277" },
|
||||
{ "name": "shimmer-text-slide", "defined_at": "ui/src/index.css:298" }
|
||||
],
|
||||
"tailwindcss_animate_plugin": false,
|
||||
"note": "No --motion-* or --duration-* tokens. Motion is defined as inline @keyframes + inline cubic-bezier values (commonly cubic-bezier(0.16, 1, 0.3, 1) and cubic-bezier(0.4, 0, 0.2, 1)). prefers-reduced-motion respected."
|
||||
},
|
||||
"elevation": {
|
||||
"sources": [],
|
||||
"authoritative_source": null,
|
||||
"count_estimate": 0,
|
||||
"note": "No --shadow-* tokens and no theme.boxShadow. Project appears to avoid shadows as a design choice (borders and background shifts carry elevation). Verify during extraction by grepping for box-shadow usage in components."
|
||||
},
|
||||
"scoped_non_ds_variables": [
|
||||
{
|
||||
"group": "MDXEditor theme bridge",
|
||||
"selector": ".paperclip-mdxeditor-scope, .paperclip-mdxeditor",
|
||||
"line_range": "332-361",
|
||||
"variable_count": 24,
|
||||
"role": "Maps host DS tokens onto MDXEditor's internal token names (--baseBase, --accentSolid, etc.). Consumed alias layer, not authoritative DS tokens."
|
||||
},
|
||||
{
|
||||
"group": "Shimmer text effect",
|
||||
"selector": ".shimmer-text",
|
||||
"variable_count": 2,
|
||||
"role": "Component-local (--shimmer-base, --shimmer-highlight)."
|
||||
}
|
||||
]
|
||||
},
|
||||
"components": {
|
||||
"primary_root": "ui/src/components/",
|
||||
"layout": "mixed",
|
||||
"layout_notes": "Not purely flat and not purely nested. Subdirectories exist for a specific subset; the majority live flat at the top level. Naming convention for primitives is lowercase-kebab (button.tsx, dropdown-menu.tsx); composites/features use PascalCase (AgentConfigForm.tsx).",
|
||||
"subdirectories": [
|
||||
{ "path": "ui/src/components/ui/", "role": "shadcn primitives", "file_count": 22 },
|
||||
{ "path": "ui/src/components/access/", "role": "access-control feature cluster", "file_count": 3 },
|
||||
{ "path": "ui/src/components/transcript/", "role": "run transcript feature cluster", "file_count": 2 }
|
||||
],
|
||||
"top_level_tsx_count": 108,
|
||||
"primitives_in_components_ui": [
|
||||
"avatar", "badge", "breadcrumb", "button", "card", "checkbox", "collapsible",
|
||||
"command", "dialog", "dropdown-menu", "input", "label", "popover",
|
||||
"scroll-area", "select", "separator", "sheet", "skeleton", "tabs",
|
||||
"textarea", "toggle-switch", "tooltip"
|
||||
],
|
||||
"count_estimate": 133,
|
||||
"plugin_sdk_components": {
|
||||
"path": "packages/plugins/sdk/src/ui/components.ts",
|
||||
"status": "ambient-types-only",
|
||||
"count": 11,
|
||||
"declared_components": [
|
||||
"MetricCard", "StatusBadge", "DataTable", "TimeseriesChart", "MarkdownBlock",
|
||||
"KeyValueList", "ActionBar", "LogView", "JsonTree", "Spinner", "ErrorBoundary"
|
||||
],
|
||||
"runtime_model": "Host provides implementations via renderSdkUiComponent(name, props) runtime injection. Plugin bundles ship type declarations only.",
|
||||
"name_collision_check": "MetricCard.tsx and StatusBadge.tsx exist at ui/src/components/ top-level. The other 9 declared components have no obvious matching file — may exist under different names (e.g., MarkdownBody ≈ MarkdownBlock, JsonSchemaForm unrelated) or may not be implemented yet."
|
||||
}
|
||||
},
|
||||
"usage_surfaces": {
|
||||
"pages_root": "ui/src/pages/",
|
||||
"page_count_estimate": 50,
|
||||
"page_count_method": "find ui/src/pages -name '*.tsx' -not -name '*.test.tsx'",
|
||||
"other_surfaces": [
|
||||
{ "path": "ui/src/App.tsx", "role": "root router" },
|
||||
{ "path": "ui/src/plugins/", "role": "plugin slot & launcher rendering (slots.tsx, launchers.tsx)" }
|
||||
],
|
||||
"notable_pages": {
|
||||
"design_guide": "ui/src/pages/DesignGuide.tsx — an existing in-app design reference page. Imports shadcn primitives; worth cross-referencing during Stage 3 for intended-vs-actual primitive usage. Its presence means a partial DS narrative already exists in-code.",
|
||||
"ux_labs": [
|
||||
"ui/src/pages/InviteUxLab.tsx",
|
||||
"ui/src/pages/IssueChatUxLab.tsx",
|
||||
"ui/src/pages/RunTranscriptUxLab.tsx"
|
||||
]
|
||||
}
|
||||
},
|
||||
"storybook": {
|
||||
"present": true,
|
||||
"version": "10.3.5",
|
||||
"config_path": "ui/storybook/.storybook/main.ts",
|
||||
"stories_location": "centralized",
|
||||
"stories_glob": "ui/storybook/stories/**/*.stories.@(ts|tsx|mdx)",
|
||||
"story_file_count": 14,
|
||||
"story_organization": "thematic",
|
||||
"story_organization_note": "Stories are organized by domain/theme (foundations, navigation-layout, dialogs-modals, chat-comments, forms-editors, status-language, data-viz-misc, agent-management, issue-management, projects-goals-workspaces, budget-finance, control-plane-surfaces, ux-labs, overview) — NOT one-story-per-component. A single .stories.tsx file typically imports and composes many components. 'Component covered by story' must be computed by parsing import graphs of story files, not by file naming.",
|
||||
"story_files": [
|
||||
"foundations.stories.tsx",
|
||||
"overview.stories.tsx",
|
||||
"status-language.stories.tsx",
|
||||
"navigation-layout.stories.tsx",
|
||||
"dialogs-modals.stories.tsx",
|
||||
"forms-editors.stories.tsx",
|
||||
"chat-comments.stories.tsx",
|
||||
"data-viz-misc.stories.tsx",
|
||||
"agent-management.stories.tsx",
|
||||
"issue-management.stories.tsx",
|
||||
"projects-goals-workspaces.stories.tsx",
|
||||
"budget-finance.stories.tsx",
|
||||
"control-plane-surfaces.stories.tsx",
|
||||
"ux-labs.stories.tsx"
|
||||
],
|
||||
"addons": ["@storybook/addon-docs", "@storybook/addon-a11y"],
|
||||
"covered_components": null,
|
||||
"covered_components_note": "Deferred to Stage 2 (parse story imports). Discovery only confirms stories exist, not per-component coverage."
|
||||
},
|
||||
"existing_docs": {
|
||||
"design_system_dir_present": false,
|
||||
"locations": [
|
||||
{ "path": "ui/README.md", "role": "package readme; non-DS" },
|
||||
{ "path": "ui/src/pages/DesignGuide.tsx", "role": "in-app design reference page (component-level showcase)" }
|
||||
],
|
||||
"figma_sync_config": null,
|
||||
"style_dictionary_config": null,
|
||||
"tokens_studio_config": null
|
||||
},
|
||||
"known_gaps": [
|
||||
"Plugin SDK UI is a types-only ambient bridge (packages/plugins/sdk/src/ui/components.ts). The host-provided component kit promised by the SDK is partial: only MetricCard and StatusBadge have matching host implementations by name. 9 other declared components (DataTable, TimeseriesChart, MarkdownBlock, KeyValueList, ActionBar, LogView, JsonTree, Spinner, ErrorBoundary) have no obvious host implementation. PLUGIN_SPEC.md:30 confirms: 'The current runtime does not yet ship a real host-provided plugin UI component kit'.",
|
||||
"No dedicated spacing, type, or elevation tokens. Those categories rely on Tailwind v4 defaults. Extraction should not synthesize repo-specific tokens where none exist.",
|
||||
"No central motion token language. Motion is expressed as per-feature @keyframes with inline easing. Treat motion as a candidate for future tokenization rather than documenting a current system.",
|
||||
"No Figma/style-dictionary/tokens-studio integration. The design system is code-authored, not design-tool-synced."
|
||||
],
|
||||
"uncertainties": [
|
||||
"Storybook organization is thematic (14 composite stories), not per-component. The extraction skill's 'covered_by_story' signal needs to be computed by parsing each story file's import graph and surfacing components used inside render bodies. Flag before Stage 2 so the skill doesn't default to file-name matching.",
|
||||
"Radius scale is non-monotonic: --radius-sm = 0.375rem, --radius-md = 0.5rem, --radius-lg = 0px, --radius-xl = 0px. Flat-design choice or stale values? Also --radius (base) = 0 at :root coexists with the @theme tokens; which is canonical for Tailwind rounded utilities? Worth confirming before Stage 1 drift analysis flags every rounded-lg usage.",
|
||||
"Plugin SDK UI contracts 11 shared components but only 2 appear to be implemented by that name in ui/. For extraction scope, should we (a) treat the SDK declarations as DS contract and flag missing implementations as gaps, or (b) ignore the SDK and document only what exists in ui/? Recommendation: (a) — it's higher-value signal for the human reviewer.",
|
||||
"ui/src/components/ has a mixed layout: 22 shadcn primitives in a 'ui/' subdirectory and 108 components flat at the top level. The top-level mix contains features, composites, one-off pages pieces, and true reusable patterns. Stage 3 will need a heuristic (composition-graph-based) rather than directory-based category inference.",
|
||||
"Total component count (~133) is meaningfully larger than the skill's illustrative example (87). With the 3+-usage threshold for detail files, output should stay tractable, but the skill should confirm the threshold is right at this scale before Stage 3 starts generating per-component .md files.",
|
||||
"MDXEditor CSS variable bridge (.paperclip-mdxeditor-scope, 24 --base*/--accent* variables) is DS-adjacent — it consumes host tokens and maps them to MDXEditor internals. Should Stage 1 include these in tokens.json? Recommendation: no — they are consumed aliases, not authoritative tokens. Flag them in tokens-review.md as 'integration layer' rather than as drift."
|
||||
]
|
||||
}
|
||||
646
doc/design-system/_pages.json
Normal file
646
doc/design-system/_pages.json
Normal file
@@ -0,0 +1,646 @@
|
||||
{
|
||||
"generated_at": "2026-04-21T00:00:00Z",
|
||||
"repo_sha": "a26e1288b627e82c554445732c7d844648e6b5e1",
|
||||
"page_count": 50,
|
||||
"method": "Import-graph extraction: 'from \"@/components/<name>\"' and relative imports. Top-level JSX tree not parsed \u2014 import set is the proxy for rendered-components set. Components a page imports but never renders would inflate the count slightly; low risk here given app style.",
|
||||
"pages": {
|
||||
"Activity": {
|
||||
"path": "ui/src/pages/Activity.tsx",
|
||||
"components_imported": [
|
||||
"ActivityRow",
|
||||
"EmptyState",
|
||||
"PageSkeleton",
|
||||
"select"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"AdapterManager": {
|
||||
"path": "ui/src/pages/AdapterManager.tsx",
|
||||
"components_imported": [
|
||||
"PathInstructionsModal",
|
||||
"badge",
|
||||
"button",
|
||||
"card",
|
||||
"dialog",
|
||||
"input",
|
||||
"label"
|
||||
],
|
||||
"component_count": 7
|
||||
},
|
||||
"AgentDetail": {
|
||||
"path": "ui/src/pages/AgentDetail.tsx",
|
||||
"components_imported": [
|
||||
"ActivityCharts",
|
||||
"AgentActionButtons",
|
||||
"AgentConfigForm",
|
||||
"AgentIconPicker",
|
||||
"BudgetPolicyCard",
|
||||
"CopyText",
|
||||
"EntityRow",
|
||||
"Identity",
|
||||
"MarkdownBody",
|
||||
"MarkdownEditor",
|
||||
"PackageFileTree",
|
||||
"PageSkeleton",
|
||||
"PageTabBar",
|
||||
"RunTranscriptView",
|
||||
"ScrollToBottom",
|
||||
"StatusBadge",
|
||||
"agent-config-primitives",
|
||||
"button",
|
||||
"collapsible",
|
||||
"input",
|
||||
"popover",
|
||||
"skeleton",
|
||||
"tabs",
|
||||
"toggle-switch",
|
||||
"tooltip"
|
||||
],
|
||||
"component_count": 25
|
||||
},
|
||||
"Agents": {
|
||||
"path": "ui/src/pages/Agents.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"EntityRow",
|
||||
"PageSkeleton",
|
||||
"PageTabBar",
|
||||
"StatusBadge",
|
||||
"button",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 7
|
||||
},
|
||||
"ApprovalDetail": {
|
||||
"path": "ui/src/pages/ApprovalDetail.tsx",
|
||||
"components_imported": [
|
||||
"ApprovalPayload",
|
||||
"Identity",
|
||||
"MarkdownBody",
|
||||
"PageSkeleton",
|
||||
"StatusBadge",
|
||||
"button",
|
||||
"textarea"
|
||||
],
|
||||
"component_count": 7
|
||||
},
|
||||
"Approvals": {
|
||||
"path": "ui/src/pages/Approvals.tsx",
|
||||
"components_imported": [
|
||||
"ApprovalCard",
|
||||
"PageSkeleton",
|
||||
"PageTabBar",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"Auth": {
|
||||
"path": "ui/src/pages/Auth.tsx",
|
||||
"components_imported": [
|
||||
"AsciiArtAnimation",
|
||||
"button"
|
||||
],
|
||||
"component_count": 2
|
||||
},
|
||||
"BoardClaim": {
|
||||
"path": "ui/src/pages/BoardClaim.tsx",
|
||||
"components_imported": [
|
||||
"button"
|
||||
],
|
||||
"component_count": 1
|
||||
},
|
||||
"CliAuth": {
|
||||
"path": "ui/src/pages/CliAuth.tsx",
|
||||
"components_imported": [
|
||||
"button"
|
||||
],
|
||||
"component_count": 1
|
||||
},
|
||||
"Companies": {
|
||||
"path": "ui/src/pages/Companies.tsx",
|
||||
"components_imported": [
|
||||
"button",
|
||||
"dropdown-menu",
|
||||
"input"
|
||||
],
|
||||
"component_count": 3
|
||||
},
|
||||
"CompanyAccess": {
|
||||
"path": "ui/src/pages/CompanyAccess.tsx",
|
||||
"components_imported": [
|
||||
"badge",
|
||||
"button",
|
||||
"checkbox",
|
||||
"dialog"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"CompanyExport": {
|
||||
"path": "ui/src/pages/CompanyExport.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"MarkdownBody",
|
||||
"PackageFileTree",
|
||||
"PageSkeleton",
|
||||
"button"
|
||||
],
|
||||
"component_count": 5
|
||||
},
|
||||
"CompanyImport": {
|
||||
"path": "ui/src/pages/CompanyImport.tsx",
|
||||
"components_imported": [
|
||||
"AgentConfigForm",
|
||||
"EmptyState",
|
||||
"MarkdownBody",
|
||||
"PackageFileTree",
|
||||
"agent-config-defaults",
|
||||
"agent-config-primitives",
|
||||
"button"
|
||||
],
|
||||
"component_count": 7
|
||||
},
|
||||
"CompanyInvites": {
|
||||
"path": "ui/src/pages/CompanyInvites.tsx",
|
||||
"components_imported": [
|
||||
"button"
|
||||
],
|
||||
"component_count": 1
|
||||
},
|
||||
"CompanySettings": {
|
||||
"path": "ui/src/pages/CompanySettings.tsx",
|
||||
"components_imported": [
|
||||
"CompanyPatternIcon",
|
||||
"agent-config-primitives",
|
||||
"button"
|
||||
],
|
||||
"component_count": 3
|
||||
},
|
||||
"CompanySkills": {
|
||||
"path": "ui/src/pages/CompanySkills.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"MarkdownBody",
|
||||
"MarkdownEditor",
|
||||
"PageSkeleton",
|
||||
"button",
|
||||
"dialog",
|
||||
"input",
|
||||
"textarea",
|
||||
"tooltip"
|
||||
],
|
||||
"component_count": 9
|
||||
},
|
||||
"Costs": {
|
||||
"path": "ui/src/pages/Costs.tsx",
|
||||
"components_imported": [
|
||||
"BillerSpendCard",
|
||||
"BudgetIncidentCard",
|
||||
"BudgetPolicyCard",
|
||||
"EmptyState",
|
||||
"FinanceBillerCard",
|
||||
"FinanceKindCard",
|
||||
"FinanceTimelineCard",
|
||||
"Identity",
|
||||
"PageSkeleton",
|
||||
"PageTabBar",
|
||||
"ProviderQuotaCard",
|
||||
"StatusBadge",
|
||||
"button",
|
||||
"card",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 15
|
||||
},
|
||||
"Dashboard": {
|
||||
"path": "ui/src/pages/Dashboard.tsx",
|
||||
"components_imported": [
|
||||
"ActiveAgentsPanel",
|
||||
"ActivityCharts",
|
||||
"ActivityRow",
|
||||
"EmptyState",
|
||||
"Identity",
|
||||
"MetricCard",
|
||||
"PageSkeleton",
|
||||
"StatusIcon"
|
||||
],
|
||||
"component_count": 8
|
||||
},
|
||||
"DesignGuide": {
|
||||
"path": "ui/src/pages/DesignGuide.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"EntityRow",
|
||||
"FilterBar",
|
||||
"Identity",
|
||||
"InlineEditor",
|
||||
"IssueReferencePill",
|
||||
"MetricCard",
|
||||
"PageSkeleton",
|
||||
"PriorityIcon",
|
||||
"StatusBadge",
|
||||
"StatusIcon",
|
||||
"avatar",
|
||||
"badge",
|
||||
"breadcrumb",
|
||||
"button",
|
||||
"card",
|
||||
"checkbox",
|
||||
"collapsible",
|
||||
"command",
|
||||
"dialog",
|
||||
"dropdown-menu",
|
||||
"input",
|
||||
"label",
|
||||
"popover",
|
||||
"scroll-area",
|
||||
"select",
|
||||
"separator",
|
||||
"sheet",
|
||||
"skeleton",
|
||||
"tabs",
|
||||
"textarea",
|
||||
"tooltip"
|
||||
],
|
||||
"component_count": 32
|
||||
},
|
||||
"ExecutionWorkspaceDetail": {
|
||||
"path": "ui/src/pages/ExecutionWorkspaceDetail.tsx",
|
||||
"components_imported": [
|
||||
"CopyText",
|
||||
"ExecutionWorkspaceCloseDialog",
|
||||
"IssuesList",
|
||||
"PageTabBar",
|
||||
"WorkspaceRuntimeControls",
|
||||
"button",
|
||||
"card",
|
||||
"input",
|
||||
"separator",
|
||||
"tabs",
|
||||
"textarea"
|
||||
],
|
||||
"component_count": 11
|
||||
},
|
||||
"GoalDetail": {
|
||||
"path": "ui/src/pages/GoalDetail.tsx",
|
||||
"components_imported": [
|
||||
"EntityRow",
|
||||
"GoalProperties",
|
||||
"GoalTree",
|
||||
"InlineEditor",
|
||||
"PageSkeleton",
|
||||
"StatusBadge",
|
||||
"button",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 8
|
||||
},
|
||||
"Goals": {
|
||||
"path": "ui/src/pages/Goals.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"GoalTree",
|
||||
"PageSkeleton",
|
||||
"button"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"Inbox": {
|
||||
"path": "ui/src/pages/Inbox.tsx",
|
||||
"components_imported": [
|
||||
"ApprovalPayload",
|
||||
"EmptyState",
|
||||
"IssueColumns",
|
||||
"IssueFiltersPopover",
|
||||
"IssueGroupHeader",
|
||||
"IssueRow",
|
||||
"PageSkeleton",
|
||||
"PageTabBar",
|
||||
"StatusBadge",
|
||||
"StatusIcon",
|
||||
"SwipeToArchive",
|
||||
"button",
|
||||
"dialog",
|
||||
"input",
|
||||
"popover",
|
||||
"select",
|
||||
"separator",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 18
|
||||
},
|
||||
"InstanceAccess": {
|
||||
"path": "ui/src/pages/InstanceAccess.tsx",
|
||||
"components_imported": [
|
||||
"button",
|
||||
"checkbox"
|
||||
],
|
||||
"component_count": 2
|
||||
},
|
||||
"InstanceExperimentalSettings": {
|
||||
"path": "ui/src/pages/InstanceExperimentalSettings.tsx",
|
||||
"components_imported": [
|
||||
"toggle-switch"
|
||||
],
|
||||
"component_count": 1
|
||||
},
|
||||
"InstanceGeneralSettings": {
|
||||
"path": "ui/src/pages/InstanceGeneralSettings.tsx",
|
||||
"components_imported": [
|
||||
"ModeBadge",
|
||||
"button",
|
||||
"toggle-switch"
|
||||
],
|
||||
"component_count": 3
|
||||
},
|
||||
"InstanceSettings": {
|
||||
"path": "ui/src/pages/InstanceSettings.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"badge",
|
||||
"button",
|
||||
"card"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"InviteLanding": {
|
||||
"path": "ui/src/pages/InviteLanding.tsx",
|
||||
"components_imported": [
|
||||
"CompanyPatternIcon",
|
||||
"button"
|
||||
],
|
||||
"component_count": 2
|
||||
},
|
||||
"InviteUxLab": {
|
||||
"path": "ui/src/pages/InviteUxLab.tsx",
|
||||
"components_imported": [
|
||||
"CompanyPatternIcon",
|
||||
"badge",
|
||||
"button",
|
||||
"card"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"IssueChatUxLab": {
|
||||
"path": "ui/src/pages/IssueChatUxLab.tsx",
|
||||
"components_imported": [
|
||||
"IssueChatThread",
|
||||
"badge",
|
||||
"button",
|
||||
"card"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"IssueDetail": {
|
||||
"path": "ui/src/pages/IssueDetail.tsx",
|
||||
"components_imported": [
|
||||
"ApprovalCard",
|
||||
"Identity",
|
||||
"ImageGalleryModal",
|
||||
"InlineEditor",
|
||||
"IssueChatThread",
|
||||
"IssueContinuationHandoff",
|
||||
"IssueDocumentsSection",
|
||||
"IssueProperties",
|
||||
"IssueReferenceActivitySummary",
|
||||
"IssueRelatedWorkPanel",
|
||||
"IssueRunLedger",
|
||||
"IssueWorkspaceCard",
|
||||
"IssuesList",
|
||||
"MarkdownEditor",
|
||||
"PriorityIcon",
|
||||
"ScrollToBottom",
|
||||
"StatusIcon",
|
||||
"button",
|
||||
"popover",
|
||||
"scroll-area",
|
||||
"separator",
|
||||
"sheet",
|
||||
"skeleton",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 24
|
||||
},
|
||||
"Issues": {
|
||||
"path": "ui/src/pages/Issues.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"IssuesList"
|
||||
],
|
||||
"component_count": 2
|
||||
},
|
||||
"JoinRequestQueue": {
|
||||
"path": "ui/src/pages/JoinRequestQueue.tsx",
|
||||
"components_imported": [
|
||||
"badge",
|
||||
"button"
|
||||
],
|
||||
"component_count": 2
|
||||
},
|
||||
"MyIssues": {
|
||||
"path": "ui/src/pages/MyIssues.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"EntityRow",
|
||||
"PageSkeleton",
|
||||
"StatusIcon"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"NewAgent": {
|
||||
"path": "ui/src/pages/NewAgent.tsx",
|
||||
"components_imported": [
|
||||
"AgentConfigForm",
|
||||
"ReportsToPicker",
|
||||
"agent-config-defaults",
|
||||
"agent-config-primitives",
|
||||
"button",
|
||||
"checkbox",
|
||||
"popover"
|
||||
],
|
||||
"component_count": 7
|
||||
},
|
||||
"NotFound": {
|
||||
"path": "ui/src/pages/NotFound.tsx",
|
||||
"components_imported": [
|
||||
"button"
|
||||
],
|
||||
"component_count": 1
|
||||
},
|
||||
"Org": {
|
||||
"path": "ui/src/pages/Org.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"PageSkeleton",
|
||||
"StatusBadge"
|
||||
],
|
||||
"component_count": 3
|
||||
},
|
||||
"OrgChart": {
|
||||
"path": "ui/src/pages/OrgChart.tsx",
|
||||
"components_imported": [
|
||||
"AgentIconPicker",
|
||||
"EmptyState",
|
||||
"PageSkeleton",
|
||||
"button"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"PluginManager": {
|
||||
"path": "ui/src/pages/PluginManager.tsx",
|
||||
"components_imported": [
|
||||
"badge",
|
||||
"button",
|
||||
"card",
|
||||
"dialog",
|
||||
"input",
|
||||
"label"
|
||||
],
|
||||
"component_count": 6
|
||||
},
|
||||
"PluginPage": {
|
||||
"path": "ui/src/pages/PluginPage.tsx",
|
||||
"components_imported": [
|
||||
"button"
|
||||
],
|
||||
"component_count": 1
|
||||
},
|
||||
"PluginSettings": {
|
||||
"path": "ui/src/pages/PluginSettings.tsx",
|
||||
"components_imported": [
|
||||
"JsonSchemaForm",
|
||||
"PageTabBar",
|
||||
"badge",
|
||||
"button",
|
||||
"card",
|
||||
"separator",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 7
|
||||
},
|
||||
"ProfileSettings": {
|
||||
"path": "ui/src/pages/ProfileSettings.tsx",
|
||||
"components_imported": [
|
||||
"avatar",
|
||||
"button",
|
||||
"input",
|
||||
"label"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"ProjectDetail": {
|
||||
"path": "ui/src/pages/ProjectDetail.tsx",
|
||||
"components_imported": [
|
||||
"BudgetPolicyCard",
|
||||
"InlineEditor",
|
||||
"IssuesList",
|
||||
"PageSkeleton",
|
||||
"PageTabBar",
|
||||
"ProjectProperties",
|
||||
"ProjectWorkspacesContent",
|
||||
"StatusBadge",
|
||||
"button",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 10
|
||||
},
|
||||
"ProjectWorkspaceDetail": {
|
||||
"path": "ui/src/pages/ProjectWorkspaceDetail.tsx",
|
||||
"components_imported": [
|
||||
"PathInstructionsModal",
|
||||
"WorkspaceRuntimeControls",
|
||||
"button",
|
||||
"separator"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"Projects": {
|
||||
"path": "ui/src/pages/Projects.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"EntityRow",
|
||||
"PageSkeleton",
|
||||
"StatusBadge",
|
||||
"button"
|
||||
],
|
||||
"component_count": 5
|
||||
},
|
||||
"RoutineDetail": {
|
||||
"path": "ui/src/pages/RoutineDetail.tsx",
|
||||
"components_imported": [
|
||||
"AgentActionButtons",
|
||||
"AgentIconPicker",
|
||||
"EmptyState",
|
||||
"InlineEntitySelector",
|
||||
"LiveRunWidget",
|
||||
"MarkdownEditor",
|
||||
"PageSkeleton",
|
||||
"RoutineRunVariablesDialog",
|
||||
"RoutineVariablesEditor",
|
||||
"ScheduleEditor",
|
||||
"badge",
|
||||
"button",
|
||||
"collapsible",
|
||||
"input",
|
||||
"label",
|
||||
"select",
|
||||
"separator",
|
||||
"tabs",
|
||||
"toggle-switch"
|
||||
],
|
||||
"component_count": 19
|
||||
},
|
||||
"Routines": {
|
||||
"path": "ui/src/pages/Routines.tsx",
|
||||
"components_imported": [
|
||||
"AgentIconPicker",
|
||||
"EmptyState",
|
||||
"InlineEntitySelector",
|
||||
"IssuesList",
|
||||
"MarkdownEditor",
|
||||
"PageSkeleton",
|
||||
"PageTabBar",
|
||||
"RoutineRunVariablesDialog",
|
||||
"RoutineVariablesEditor",
|
||||
"button",
|
||||
"card",
|
||||
"collapsible",
|
||||
"dialog",
|
||||
"dropdown-menu",
|
||||
"popover",
|
||||
"select",
|
||||
"tabs",
|
||||
"toggle-switch"
|
||||
],
|
||||
"component_count": 18
|
||||
},
|
||||
"RunTranscriptUxLab": {
|
||||
"path": "ui/src/pages/RunTranscriptUxLab.tsx",
|
||||
"components_imported": [
|
||||
"Identity",
|
||||
"RunTranscriptView",
|
||||
"StatusBadge",
|
||||
"badge",
|
||||
"button"
|
||||
],
|
||||
"component_count": 5
|
||||
},
|
||||
"UserProfile": {
|
||||
"path": "ui/src/pages/UserProfile.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"PageSkeleton",
|
||||
"StatusBadge",
|
||||
"avatar"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"Workspaces": {
|
||||
"path": "ui/src/pages/Workspaces.tsx",
|
||||
"components_imported": [
|
||||
"PageSkeleton",
|
||||
"ProjectWorkspacesContent"
|
||||
],
|
||||
"component_count": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
338
doc/design-system/_stories.json
Normal file
338
doc/design-system/_stories.json
Normal file
@@ -0,0 +1,338 @@
|
||||
{
|
||||
"generated_at": "2026-04-21T00:00:00Z",
|
||||
"repo_sha": "a26e1288b627e82c554445732c7d844648e6b5e1",
|
||||
"story_files": {
|
||||
"agent-management.stories.tsx": {
|
||||
"path": "ui/storybook/stories/agent-management.stories.tsx",
|
||||
"components_imported": [
|
||||
"ActiveAgentsPanel",
|
||||
"AgentActionButtons",
|
||||
"AgentConfigForm",
|
||||
"AgentIconPicker",
|
||||
"AgentProperties",
|
||||
"agent-config-defaults",
|
||||
"agent-config-primitives",
|
||||
"badge",
|
||||
"button",
|
||||
"card",
|
||||
"select",
|
||||
"separator"
|
||||
],
|
||||
"component_count": 12
|
||||
},
|
||||
"budget-finance.stories.tsx": {
|
||||
"path": "ui/storybook/stories/budget-finance.stories.tsx",
|
||||
"components_imported": [
|
||||
"AccountingModelCard",
|
||||
"BillerSpendCard",
|
||||
"BudgetIncidentCard",
|
||||
"BudgetSidebarMarker",
|
||||
"ClaudeSubscriptionPanel",
|
||||
"CodexSubscriptionPanel",
|
||||
"FinanceBillerCard",
|
||||
"FinanceKindCard",
|
||||
"FinanceTimelineCard",
|
||||
"ProviderQuotaCard",
|
||||
"badge",
|
||||
"card"
|
||||
],
|
||||
"component_count": 12
|
||||
},
|
||||
"chat-comments.stories.tsx": {
|
||||
"path": "ui/storybook/stories/chat-comments.stories.tsx",
|
||||
"components_imported": [
|
||||
"CommentThread",
|
||||
"InlineEntitySelector",
|
||||
"IssueChatThread",
|
||||
"MarkdownEditor",
|
||||
"RunChatSurface",
|
||||
"badge",
|
||||
"card"
|
||||
],
|
||||
"component_count": 7
|
||||
},
|
||||
"control-plane-surfaces.stories.tsx": {
|
||||
"path": "ui/storybook/stories/control-plane-surfaces.stories.tsx",
|
||||
"components_imported": [
|
||||
"ActivityRow",
|
||||
"ApprovalCard",
|
||||
"BudgetPolicyCard",
|
||||
"Identity",
|
||||
"IssueRow",
|
||||
"PriorityIcon",
|
||||
"StatusBadge",
|
||||
"badge",
|
||||
"card"
|
||||
],
|
||||
"component_count": 9
|
||||
},
|
||||
"data-viz-misc.stories.tsx": {
|
||||
"path": "ui/storybook/stories/data-viz-misc.stories.tsx",
|
||||
"components_imported": [
|
||||
"ActivityCharts",
|
||||
"AsciiArtAnimation",
|
||||
"CompanyPatternIcon",
|
||||
"EntityRow",
|
||||
"FilterBar",
|
||||
"KanbanBoard",
|
||||
"LiveRunWidget",
|
||||
"OnboardingWizard",
|
||||
"PackageFileTree",
|
||||
"PageSkeleton",
|
||||
"StatusBadge",
|
||||
"SwipeToArchive",
|
||||
"badge",
|
||||
"button",
|
||||
"card"
|
||||
],
|
||||
"component_count": 15
|
||||
},
|
||||
"dialogs-modals.stories.tsx": {
|
||||
"path": "ui/storybook/stories/dialogs-modals.stories.tsx",
|
||||
"components_imported": [
|
||||
"DocumentDiffModal",
|
||||
"ExecutionWorkspaceCloseDialog",
|
||||
"ImageGalleryModal",
|
||||
"NewAgentDialog",
|
||||
"NewGoalDialog",
|
||||
"NewIssueDialog",
|
||||
"NewProjectDialog",
|
||||
"PathInstructionsModal",
|
||||
"badge"
|
||||
],
|
||||
"component_count": 9
|
||||
},
|
||||
"forms-editors.stories.tsx": {
|
||||
"path": "ui/storybook/stories/forms-editors.stories.tsx",
|
||||
"components_imported": [
|
||||
"EnvVarEditor",
|
||||
"ExecutionParticipantPicker",
|
||||
"InlineEditor",
|
||||
"InlineEntitySelector",
|
||||
"JsonSchemaForm",
|
||||
"MarkdownBody",
|
||||
"MarkdownEditor",
|
||||
"ReportsToPicker",
|
||||
"RoutineRunVariablesDialog",
|
||||
"RoutineVariablesEditor",
|
||||
"ScheduleEditor",
|
||||
"badge",
|
||||
"button"
|
||||
],
|
||||
"component_count": 13
|
||||
},
|
||||
"foundations.stories.tsx": {
|
||||
"path": "ui/storybook/stories/foundations.stories.tsx",
|
||||
"components_imported": [
|
||||
"badge",
|
||||
"button",
|
||||
"card",
|
||||
"checkbox",
|
||||
"dialog",
|
||||
"input",
|
||||
"label",
|
||||
"popover",
|
||||
"select",
|
||||
"separator",
|
||||
"tabs",
|
||||
"textarea",
|
||||
"toggle-switch",
|
||||
"tooltip"
|
||||
],
|
||||
"component_count": 14
|
||||
},
|
||||
"issue-management.stories.tsx": {
|
||||
"path": "ui/storybook/stories/issue-management.stories.tsx",
|
||||
"components_imported": [
|
||||
"Identity",
|
||||
"IssueColumns",
|
||||
"IssueContinuationHandoff",
|
||||
"IssueDocumentsSection",
|
||||
"IssueFiltersPopover",
|
||||
"IssueGroupHeader",
|
||||
"IssueLinkQuicklook",
|
||||
"IssueProperties",
|
||||
"IssueRunLedger",
|
||||
"IssueWorkspaceCard",
|
||||
"IssuesList",
|
||||
"IssuesQuicklook",
|
||||
"PriorityIcon",
|
||||
"StatusBadge",
|
||||
"badge",
|
||||
"button",
|
||||
"card"
|
||||
],
|
||||
"component_count": 17
|
||||
},
|
||||
"navigation-layout.stories.tsx": {
|
||||
"path": "ui/storybook/stories/navigation-layout.stories.tsx",
|
||||
"components_imported": [
|
||||
"BreadcrumbBar",
|
||||
"CommandPalette",
|
||||
"CompanyRail",
|
||||
"CompanySwitcher",
|
||||
"KeyboardShortcutsCheatsheet",
|
||||
"MobileBottomNav",
|
||||
"PageTabBar",
|
||||
"Sidebar",
|
||||
"SidebarAccountMenu",
|
||||
"SidebarCompanyMenu",
|
||||
"StatusBadge",
|
||||
"badge",
|
||||
"command",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 14
|
||||
},
|
||||
"overview.stories.tsx": {
|
||||
"path": "ui/storybook/stories/overview.stories.tsx",
|
||||
"components_imported": [
|
||||
"badge",
|
||||
"card"
|
||||
],
|
||||
"component_count": 2
|
||||
},
|
||||
"projects-goals-workspaces.stories.tsx": {
|
||||
"path": "ui/storybook/stories/projects-goals-workspaces.stories.tsx",
|
||||
"components_imported": [
|
||||
"GoalProperties",
|
||||
"GoalTree",
|
||||
"ProjectProperties",
|
||||
"ProjectWorkspaceSummaryCard",
|
||||
"ProjectWorkspacesContent",
|
||||
"WorkspaceRuntimeControls",
|
||||
"WorktreeBanner",
|
||||
"badge",
|
||||
"card"
|
||||
],
|
||||
"component_count": 9
|
||||
},
|
||||
"status-language.stories.tsx": {
|
||||
"path": "ui/storybook/stories/status-language.stories.tsx",
|
||||
"components_imported": [
|
||||
"CopyText",
|
||||
"EmptyState",
|
||||
"Identity",
|
||||
"MetricCard",
|
||||
"PriorityIcon",
|
||||
"QuotaBar",
|
||||
"StatusBadge",
|
||||
"card"
|
||||
],
|
||||
"component_count": 8
|
||||
},
|
||||
"ux-labs.stories.tsx": {
|
||||
"path": "ui/storybook/stories/ux-labs.stories.tsx",
|
||||
"components_imported": [],
|
||||
"component_count": 0
|
||||
}
|
||||
},
|
||||
"covered_components_count": 104,
|
||||
"covered_components": [
|
||||
"AccountingModelCard",
|
||||
"ActiveAgentsPanel",
|
||||
"ActivityCharts",
|
||||
"ActivityRow",
|
||||
"AgentActionButtons",
|
||||
"AgentConfigForm",
|
||||
"AgentIconPicker",
|
||||
"AgentProperties",
|
||||
"ApprovalCard",
|
||||
"AsciiArtAnimation",
|
||||
"BillerSpendCard",
|
||||
"BreadcrumbBar",
|
||||
"BudgetIncidentCard",
|
||||
"BudgetPolicyCard",
|
||||
"BudgetSidebarMarker",
|
||||
"ClaudeSubscriptionPanel",
|
||||
"CodexSubscriptionPanel",
|
||||
"CommandPalette",
|
||||
"CommentThread",
|
||||
"CompanyPatternIcon",
|
||||
"CompanyRail",
|
||||
"CompanySwitcher",
|
||||
"CopyText",
|
||||
"DocumentDiffModal",
|
||||
"EmptyState",
|
||||
"EntityRow",
|
||||
"EnvVarEditor",
|
||||
"ExecutionParticipantPicker",
|
||||
"ExecutionWorkspaceCloseDialog",
|
||||
"FilterBar",
|
||||
"FinanceBillerCard",
|
||||
"FinanceKindCard",
|
||||
"FinanceTimelineCard",
|
||||
"GoalProperties",
|
||||
"GoalTree",
|
||||
"Identity",
|
||||
"ImageGalleryModal",
|
||||
"InlineEditor",
|
||||
"InlineEntitySelector",
|
||||
"IssueChatThread",
|
||||
"IssueColumns",
|
||||
"IssueContinuationHandoff",
|
||||
"IssueDocumentsSection",
|
||||
"IssueFiltersPopover",
|
||||
"IssueGroupHeader",
|
||||
"IssueLinkQuicklook",
|
||||
"IssueProperties",
|
||||
"IssueRow",
|
||||
"IssueRunLedger",
|
||||
"IssueWorkspaceCard",
|
||||
"IssuesList",
|
||||
"IssuesQuicklook",
|
||||
"JsonSchemaForm",
|
||||
"KanbanBoard",
|
||||
"KeyboardShortcutsCheatsheet",
|
||||
"LiveRunWidget",
|
||||
"MarkdownBody",
|
||||
"MarkdownEditor",
|
||||
"MetricCard",
|
||||
"MobileBottomNav",
|
||||
"NewAgentDialog",
|
||||
"NewGoalDialog",
|
||||
"NewIssueDialog",
|
||||
"NewProjectDialog",
|
||||
"OnboardingWizard",
|
||||
"PackageFileTree",
|
||||
"PageSkeleton",
|
||||
"PageTabBar",
|
||||
"PathInstructionsModal",
|
||||
"PriorityIcon",
|
||||
"ProjectProperties",
|
||||
"ProjectWorkspaceSummaryCard",
|
||||
"ProjectWorkspacesContent",
|
||||
"ProviderQuotaCard",
|
||||
"QuotaBar",
|
||||
"ReportsToPicker",
|
||||
"RoutineRunVariablesDialog",
|
||||
"RoutineVariablesEditor",
|
||||
"RunChatSurface",
|
||||
"ScheduleEditor",
|
||||
"Sidebar",
|
||||
"SidebarAccountMenu",
|
||||
"SidebarCompanyMenu",
|
||||
"StatusBadge",
|
||||
"SwipeToArchive",
|
||||
"WorkspaceRuntimeControls",
|
||||
"WorktreeBanner",
|
||||
"agent-config-defaults",
|
||||
"agent-config-primitives",
|
||||
"badge",
|
||||
"button",
|
||||
"card",
|
||||
"checkbox",
|
||||
"command",
|
||||
"dialog",
|
||||
"input",
|
||||
"label",
|
||||
"popover",
|
||||
"select",
|
||||
"separator",
|
||||
"tabs",
|
||||
"textarea",
|
||||
"toggle-switch",
|
||||
"tooltip"
|
||||
],
|
||||
"covered_components_note": "Computed by parsing 'from \"@/components/...\"' and relative imports across all .stories.tsx files. Coverage is set membership \u2014 a component appears once if any story imports it, regardless of how many variants/states are rendered."
|
||||
}
|
||||
21
doc/design-system/components/ActivityCharts.md
Normal file
21
doc/design-system/components/ActivityCharts.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# ActivityCharts
|
||||
|
||||
`ui/src/components/ActivityCharts.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 3 imports (2 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 274 lines
|
||||
- **Sibling exports:** ChartCard, IssueStatusChart, PriorityChart, RunActivityChart, SuccessRateChart
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `Dashboard`
|
||||
40
doc/design-system/components/AgentConfigForm.md
Normal file
40
doc/design-system/components/AgentConfigForm.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# AgentConfigForm
|
||||
|
||||
`ui/src/components/AgentConfigForm.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 5 imports (3 pages, 0 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 1403 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `AgentConfigFormProps`
|
||||
|
||||
```ts
|
||||
adapterModels?: AdapterModel[];
|
||||
onDirtyChange?: (dirty: boolean) => void;
|
||||
onSaveActionChange?: (save: (() => void) | null) => void;
|
||||
onCancelActionChange?: (cancel: (() => void) | null) => void;
|
||||
hideInlineSave?: boolean;
|
||||
showAdapterTypeField?: boolean;
|
||||
showAdapterTestEnvironmentButton?: boolean;
|
||||
showCreateRunPolicySection?: boolean;
|
||||
hideInstructionsFile?: boolean;
|
||||
/** Hide the prompt template field from the Identity section (used when it's shown in a separate Prompts tab). */
|
||||
hidePromptTemplate?: boolean;
|
||||
/** "cards" renders each section as heading + bordered card (for settings pages). Default: "inline" (border-b dividers). */
|
||||
sectionLayout?: "inline" | "cards";
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md), [popover](./Popover.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `CompanyImport`, `NewAgent`
|
||||
38
doc/design-system/components/AgentIconPicker.md
Normal file
38
doc/design-system/components/AgentIconPicker.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# AgentIconPicker
|
||||
|
||||
`ui/src/components/AgentIconPicker.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 13 imports (4 pages, 9 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 81 lines
|
||||
- **Sibling exports:** AgentIcon
|
||||
|
||||
## Props
|
||||
|
||||
### `AgentIconProps`
|
||||
|
||||
```ts
|
||||
icon: string | null | undefined;
|
||||
className?: string;
|
||||
```
|
||||
|
||||
### `AgentIconPickerProps`
|
||||
|
||||
```ts
|
||||
value: string | null | undefined;
|
||||
onChange: (icon: string) => void;
|
||||
children: React.ReactNode;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [input](./Input.md), [popover](./Popover.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `OrgChart`, `RoutineDetail`, `Routines`
|
||||
24
doc/design-system/components/ApprovalCard.md
Normal file
24
doc/design-system/components/ApprovalCard.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# ApprovalCard
|
||||
|
||||
`ui/src/components/ApprovalCard.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 3 imports (2 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 153 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [badge](./Badge.md), [button](./Button.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `Approvals`, `IssueDetail`
|
||||
21
doc/design-system/components/ApprovalPayload.md
Normal file
21
doc/design-system/components/ApprovalPayload.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# ApprovalPayload
|
||||
|
||||
`ui/src/components/ApprovalPayload.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 4 imports (2 pages, 2 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 248 lines
|
||||
- **Sibling exports:** ApprovalPayloadRenderer, BoardApprovalPayload, BudgetOverridePayload, CeoStrategyPayload, HireAgentPayload
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `ApprovalDetail`, `Inbox`
|
||||
22
doc/design-system/components/Avatar.md
Normal file
22
doc/design-system/components/Avatar.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Avatar
|
||||
|
||||
`ui/src/components/ui/avatar.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 7 imports (3 pages, 4 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 108 lines
|
||||
- **Sibling exports:** AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `DesignGuide`, `ProfileSettings`, `UserProfile`
|
||||
- **Components:** `CommentThread`, `Identity`, `IssueChatThread`, `SidebarAccountMenu`
|
||||
26
doc/design-system/components/Badge.md
Normal file
26
doc/design-system/components/Badge.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Badge
|
||||
|
||||
`ui/src/components/ui/badge.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 18 imports (11 pages, 7 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 49 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Variants (CVA)
|
||||
|
||||
- **variant**: `default`, `secondary`, `destructive`, `outline`, `ghost`, `link`
|
||||
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AdapterManager`, `CompanyAccess`, `DesignGuide`, `InstanceSettings`, `InviteUxLab` … (+6 more)
|
||||
- **Components:** `ApprovalCard`, `BudgetIncidentCard`, `FilterBar`, `FinanceTimelineCard`, `IssueFiltersPopover` … (+2 more)
|
||||
24
doc/design-system/components/BudgetPolicyCard.md
Normal file
24
doc/design-system/components/BudgetPolicyCard.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# BudgetPolicyCard
|
||||
|
||||
`ui/src/components/BudgetPolicyCard.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 3 imports (3 pages, 0 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 220 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md), [card](./Card.md), [input](./Input.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `Costs`, `ProjectDetail`
|
||||
27
doc/design-system/components/Button.md
Normal file
27
doc/design-system/components/Button.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Button
|
||||
|
||||
`ui/src/components/ui/button.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 81 imports (41 pages, 38 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 71 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Variants (CVA)
|
||||
|
||||
- **variant**: `default`, `destructive`, `outline`, `secondary`, `ghost`, `link`
|
||||
- **size**: `default`, `xs`, `sm`, `lg`, `icon`, `icon-xs`, `icon-sm`, `icon-lg`
|
||||
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AdapterManager`, `AgentDetail`, `Agents`, `ApprovalDetail`, `Auth` … (+36 more)
|
||||
- **Components:** `AgentActionButtons`, `AgentConfigForm`, `ApprovalCard`, `BreadcrumbBar`, `BudgetIncidentCard` … (+33 more)
|
||||
22
doc/design-system/components/Card.md
Normal file
22
doc/design-system/components/Card.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Card
|
||||
|
||||
`ui/src/components/ui/card.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 18 imports (10 pages, 8 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 93 lines
|
||||
- **Sibling exports:** CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AdapterManager`, `Costs`, `DesignGuide`, `ExecutionWorkspaceDetail`, `InstanceSettings` … (+5 more)
|
||||
- **Components:** `AccountingModelCard`, `BillerSpendCard`, `BudgetIncidentCard`, `BudgetPolicyCard`, `FinanceBillerCard` … (+3 more)
|
||||
21
doc/design-system/components/Checkbox.md
Normal file
21
doc/design-system/components/Checkbox.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Checkbox
|
||||
|
||||
`ui/src/components/ui/checkbox.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 6 imports (4 pages, 2 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 33 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `CompanyAccess`, `DesignGuide`, `InstanceAccess`, `NewAgent`
|
||||
- **Components:** `IssueFiltersPopover`, `JsonSchemaForm`
|
||||
22
doc/design-system/components/Collapsible.md
Normal file
22
doc/design-system/components/Collapsible.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Collapsible
|
||||
|
||||
`ui/src/components/ui/collapsible.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 8 imports (4 pages, 4 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 34 lines
|
||||
- **Sibling exports:** CollapsibleContent, CollapsibleTrigger
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `DesignGuide`, `RoutineDetail`, `Routines`
|
||||
- **Components:** `IssuesList`, `RoutineVariablesEditor`, `SidebarAgents`, `SidebarProjects`
|
||||
28
doc/design-system/components/CompanyPatternIcon.md
Normal file
28
doc/design-system/components/CompanyPatternIcon.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# CompanyPatternIcon
|
||||
|
||||
`ui/src/components/CompanyPatternIcon.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 4 imports (3 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 218 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `CompanyPatternIconProps`
|
||||
|
||||
```ts
|
||||
companyName: string;
|
||||
logoUrl?: string | null;
|
||||
brandColor?: string | null;
|
||||
className?: string;
|
||||
logoFit?: "cover" | "contain";
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `CompanySettings`, `InviteLanding`, `InviteUxLab`
|
||||
32
doc/design-system/components/CopyText.md
Normal file
32
doc/design-system/components/CopyText.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# CopyText
|
||||
|
||||
`ui/src/components/CopyText.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 3 imports (2 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 88 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `CopyTextProps`
|
||||
|
||||
```ts
|
||||
text: string;
|
||||
/** What to display. Defaults to `text`. */
|
||||
children?: React.ReactNode;
|
||||
containerClassName?: string;
|
||||
className?: string;
|
||||
ariaLabel?: string;
|
||||
title?: string;
|
||||
/** Tooltip message shown after copying. Default: "Copied!" */
|
||||
copiedLabel?: string;
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `ExecutionWorkspaceDetail`
|
||||
26
doc/design-system/components/Dialog.md
Normal file
26
doc/design-system/components/Dialog.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Dialog
|
||||
|
||||
`ui/src/components/ui/dialog.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 21 imports (7 pages, 14 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 157 lines
|
||||
- **Sibling exports:** DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AdapterManager`, `CompanyAccess`, `CompanySkills`, `DesignGuide`, `Inbox` … (+2 more)
|
||||
- **Components:** `DocumentDiffModal`, `IssueChatThread`, `KeyboardShortcutsCheatsheet`, `NewAgentDialog`, `NewGoalDialog` … (+9 more)
|
||||
22
doc/design-system/components/DropdownMenu.md
Normal file
22
doc/design-system/components/DropdownMenu.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# DropdownMenu
|
||||
|
||||
`ui/src/components/ui/dropdown-menu.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 8 imports (3 pages, 5 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 258 lines
|
||||
- **Sibling exports:** DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `Companies`, `DesignGuide`, `Routines`
|
||||
- **Components:** `CompanySwitcher`, `IssueChatThread`, `IssueColumns`, `IssueDocumentsSection`, `SidebarCompanyMenu`
|
||||
31
doc/design-system/components/EmptyState.md
Normal file
31
doc/design-system/components/EmptyState.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# EmptyState
|
||||
|
||||
`ui/src/components/EmptyState.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 20 imports (19 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 28 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `EmptyStateProps`
|
||||
|
||||
```ts
|
||||
icon: LucideIcon;
|
||||
message: string;
|
||||
action?: string;
|
||||
onAction?: () => void;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `Activity`, `Agents`, `CompanyExport`, `CompanyImport`, `CompanySkills` … (+14 more)
|
||||
32
doc/design-system/components/EntityRow.md
Normal file
32
doc/design-system/components/EntityRow.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# EntityRow
|
||||
|
||||
`ui/src/components/EntityRow.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 6 imports (6 pages, 0 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 70 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `EntityRowProps`
|
||||
|
||||
```ts
|
||||
leading?: ReactNode;
|
||||
identifier?: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
trailing?: ReactNode;
|
||||
selected?: boolean;
|
||||
to?: string;
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `Agents`, `DesignGuide`, `GoalDetail`, `MyIssues` … (+1 more)
|
||||
32
doc/design-system/components/Identity.md
Normal file
32
doc/design-system/components/Identity.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Identity
|
||||
|
||||
`ui/src/components/Identity.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 19 imports (7 pages, 12 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 40 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `IdentityProps`
|
||||
|
||||
```ts
|
||||
name: string;
|
||||
avatarUrl?: string | null;
|
||||
initials?: string;
|
||||
size?: IdentitySize;
|
||||
className?: string;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [avatar](./Avatar.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `ApprovalDetail`, `Costs`, `Dashboard`, `DesignGuide` … (+2 more)
|
||||
34
doc/design-system/components/InlineEditor.md
Normal file
34
doc/design-system/components/InlineEditor.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# InlineEditor
|
||||
|
||||
`ui/src/components/InlineEditor.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 6 imports (4 pages, 2 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 310 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `InlineEditorProps`
|
||||
|
||||
```ts
|
||||
value: string;
|
||||
onSave: (value: string) => void | Promise<unknown>;
|
||||
as?: "h1" | "h2" | "p" | "span";
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
multiline?: boolean;
|
||||
imageUploadHandler?: (file: File) => Promise<string>;
|
||||
/** Called when a non-image file is dropped onto the editor. */
|
||||
onDropFile?: (file: File) => Promise<void>;
|
||||
mentions?: MentionOption[];
|
||||
nullable?: boolean;
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `DesignGuide`, `GoalDetail`, `IssueDetail`, `ProjectDetail`
|
||||
43
doc/design-system/components/InlineEntitySelector.md
Normal file
43
doc/design-system/components/InlineEntitySelector.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# InlineEntitySelector
|
||||
|
||||
`ui/src/components/InlineEntitySelector.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 8 imports (2 pages, 4 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 215 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `InlineEntitySelectorProps`
|
||||
|
||||
```ts
|
||||
value: string;
|
||||
options: InlineEntityOption[];
|
||||
placeholder: string;
|
||||
noneLabel: string;
|
||||
searchPlaceholder: string;
|
||||
emptyMessage: string;
|
||||
onChange: (id: string) => void;
|
||||
onConfirm?: () => void;
|
||||
className?: string;
|
||||
renderTriggerValue?: (option: InlineEntityOption | null) => ReactNode;
|
||||
renderOption?: (option: InlineEntityOption, isSelected: boolean) => ReactNode;
|
||||
recentOptionIds?: string[];
|
||||
/** Skip the Portal so the popover stays in the DOM tree (fixes scroll inside Dialogs). */
|
||||
disablePortal?: boolean;
|
||||
/** Open the popover when the trigger receives keyboard/programmatic focus. */
|
||||
openOnFocus?: boolean;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [popover](./Popover.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `RoutineDetail`, `Routines`
|
||||
21
doc/design-system/components/Input.md
Normal file
21
doc/design-system/components/Input.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Input
|
||||
|
||||
`ui/src/components/ui/input.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 20 imports (10 pages, 10 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 22 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AdapterManager`, `AgentDetail`, `Companies`, `CompanySkills`, `DesignGuide` … (+5 more)
|
||||
- **Components:** `AgentIconPicker`, `BudgetIncidentCard`, `BudgetPolicyCard`, `IssueDocumentsSection`, `IssueFiltersPopover` … (+5 more)
|
||||
73
doc/design-system/components/IssueChatThread.md
Normal file
73
doc/design-system/components/IssueChatThread.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# IssueChatThread
|
||||
|
||||
`ui/src/components/IssueChatThread.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 4 imports (2 pages, 2 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 2399 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `IssueChatComposerProps`
|
||||
|
||||
```ts
|
||||
onImageUpload?: (file: File) => Promise<string>;
|
||||
onAttachImage?: (file: File) => Promise<void>;
|
||||
draftKey?: string;
|
||||
enableReassign?: boolean;
|
||||
reassignOptions?: InlineEntityOption[];
|
||||
currentAssigneeValue?: string;
|
||||
suggestedAssigneeValue?: string;
|
||||
mentions?: MentionOption[];
|
||||
agentMap?: Map<string, Agent>;
|
||||
composerDisabledReason?: string | null;
|
||||
issueStatus?: string;
|
||||
```
|
||||
|
||||
### `IssueChatThreadProps`
|
||||
|
||||
```ts
|
||||
comments: IssueChatComment[];
|
||||
feedbackVotes?: FeedbackVote[];
|
||||
feedbackDataSharingPreference?: FeedbackDataSharingPreference;
|
||||
feedbackTermsUrl?: string | null;
|
||||
linkedRuns?: IssueChatLinkedRun[];
|
||||
timelineEvents?: IssueTimelineEvent[];
|
||||
liveRuns?: LiveRunForIssue[];
|
||||
activeRun?: ActiveRunForIssue | null;
|
||||
blockedBy?: IssueRelationIssueSummary[];
|
||||
companyId?: string | null;
|
||||
projectId?: string | null;
|
||||
issueStatus?: string;
|
||||
agentMap?: Map<string, Agent>;
|
||||
currentUserId?: string | null;
|
||||
userLabelMap?: ReadonlyMap<string, string> | null;
|
||||
userProfileMap?: ReadonlyMap<string, CompanyUserProfile> | null;
|
||||
onVote?: (
|
||||
commentId: string,
|
||||
vote: FeedbackVoteValue,
|
||||
options?: { allowSharing?: boolean; reason?: string
|
||||
```
|
||||
|
||||
### `IssueChatErrorBoundaryProps`
|
||||
|
||||
```ts
|
||||
resetKey: string;
|
||||
messages: readonly ThreadMessage[];
|
||||
emptyMessage: string;
|
||||
variant: "full" | "embedded";
|
||||
children: ReactNode;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [avatar](./Avatar.md), [button](./Button.md), [dialog](./Dialog.md), `dropdown-menu`, [popover](./Popover.md), [textarea](./Textarea.md), [tooltip](./Tooltip.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `IssueChatUxLab`, `IssueDetail`
|
||||
24
doc/design-system/components/IssueFiltersPopover.md
Normal file
24
doc/design-system/components/IssueFiltersPopover.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# IssueFiltersPopover
|
||||
|
||||
`ui/src/components/IssueFiltersPopover.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 3 imports (1 pages, 2 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 366 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [badge](./Badge.md), [button](./Button.md), [checkbox](./Checkbox.md), [input](./Input.md), [popover](./Popover.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `Inbox`
|
||||
25
doc/design-system/components/IssueLinkQuicklook.md
Normal file
25
doc/design-system/components/IssueLinkQuicklook.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# IssueLinkQuicklook
|
||||
|
||||
`ui/src/components/IssueLinkQuicklook.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 3 imports (0 pages, 2 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 182 lines
|
||||
- **Sibling exports:** IssueQuicklookCard
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [popover](./Popover.md)
|
||||
- **Composites:** [StatusIcon](./StatusIcon.md)
|
||||
|
||||
## Used by
|
||||
|
||||
20
doc/design-system/components/IssueReferencePill.md
Normal file
20
doc/design-system/components/IssueReferencePill.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# IssueReferencePill
|
||||
|
||||
`ui/src/components/IssueReferencePill.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 4 imports (1 pages, 3 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 56 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `DesignGuide`
|
||||
38
doc/design-system/components/IssueRow.md
Normal file
38
doc/design-system/components/IssueRow.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# IssueRow
|
||||
|
||||
`ui/src/components/IssueRow.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 3 imports (1 pages, 2 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 169 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `IssueRowProps`
|
||||
|
||||
```ts
|
||||
issue: Issue;
|
||||
issueLinkState?: unknown;
|
||||
selected?: boolean;
|
||||
mobileLeading?: ReactNode;
|
||||
desktopMetaLeading?: ReactNode;
|
||||
desktopLeadingSpacer?: boolean;
|
||||
mobileMeta?: ReactNode;
|
||||
desktopTrailing?: ReactNode;
|
||||
trailingMeta?: ReactNode;
|
||||
titleSuffix?: ReactNode;
|
||||
unreadState?: UnreadState | null;
|
||||
onMarkRead?: () => void;
|
||||
onArchive?: () => void;
|
||||
archiveDisabled?: boolean;
|
||||
className?: string;
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `Inbox`
|
||||
41
doc/design-system/components/IssueWorkspaceCard.md
Normal file
41
doc/design-system/components/IssueWorkspaceCard.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# IssueWorkspaceCard
|
||||
|
||||
`ui/src/components/IssueWorkspaceCard.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 3 imports (1 pages, 2 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 522 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `IssueWorkspaceCardProps`
|
||||
|
||||
```ts
|
||||
issue: Omit<
|
||||
Pick<
|
||||
Issue,
|
||||
| "companyId"
|
||||
| "projectId"
|
||||
| "projectWorkspaceId"
|
||||
| "executionWorkspaceId"
|
||||
| "executionWorkspacePreference"
|
||||
| "executionWorkspaceSettings"
|
||||
>,
|
||||
"companyId"
|
||||
> & {
|
||||
companyId: string | null;
|
||||
currentExecutionWorkspace?: ExecutionWorkspace | null;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md), [skeleton](./Skeleton.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `IssueDetail`
|
||||
45
doc/design-system/components/IssuesList.md
Normal file
45
doc/design-system/components/IssuesList.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# IssuesList
|
||||
|
||||
`ui/src/components/IssuesList.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 6 imports (5 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 1170 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `IssuesListProps`
|
||||
|
||||
```ts
|
||||
issues: Issue[];
|
||||
isLoading?: boolean;
|
||||
error?: Error | null;
|
||||
agents?: Agent[];
|
||||
projects?: ProjectOption[];
|
||||
liveIssueIds?: Set<string>;
|
||||
projectId?: string;
|
||||
viewStateKey: string;
|
||||
issueLinkState?: unknown;
|
||||
initialAssignees?: string[];
|
||||
initialWorkspaces?: string[];
|
||||
initialSearch?: string;
|
||||
searchFilters?: Omit<IssueListRequestFilters, "q" | "projectId" | "limit" | "includeRoutineExecutions">;
|
||||
baseCreateIssueDefaults?: Record<string, unknown>;
|
||||
createIssueLabel?: string;
|
||||
enableRoutineVisibilityFilter?: boolean;
|
||||
onSearchChange?: (search: string) => void;
|
||||
onUpdateIssue: (id: string, data: Record<string, unknown>) => void;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md), [collapsible](./Collapsible.md), [input](./Input.md), [popover](./Popover.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `ExecutionWorkspaceDetail`, `IssueDetail`, `Issues`, `ProjectDetail`, `Routines`
|
||||
21
doc/design-system/components/Label.md
Normal file
21
doc/design-system/components/Label.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Label
|
||||
|
||||
`ui/src/components/ui/label.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 8 imports (5 pages, 3 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 23 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AdapterManager`, `DesignGuide`, `PluginManager`, `ProfileSettings`, `RoutineDetail`
|
||||
- **Components:** `JsonSchemaForm`, `RoutineRunVariablesDialog`, `RoutineVariablesEditor`
|
||||
32
doc/design-system/components/MarkdownBody.md
Normal file
32
doc/design-system/components/MarkdownBody.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# MarkdownBody
|
||||
|
||||
`ui/src/components/MarkdownBody.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 11 imports (5 pages, 6 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 326 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `MarkdownBodyProps`
|
||||
|
||||
```ts
|
||||
children: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
softBreaks?: boolean;
|
||||
linkIssueReferences?: boolean;
|
||||
/** Optional resolver for relative image paths (e.g. within export packages) */
|
||||
resolveImageSrc?: (src: string) => string | null;
|
||||
/** Called when a user clicks an inline image */
|
||||
onImageClick?: (src: string) => void;
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `ApprovalDetail`, `CompanyExport`, `CompanyImport`, `CompanySkills`
|
||||
39
doc/design-system/components/MarkdownEditor.md
Normal file
39
doc/design-system/components/MarkdownEditor.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# MarkdownEditor
|
||||
|
||||
`ui/src/components/MarkdownEditor.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 16 imports (5 pages, 9 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 1204 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `MarkdownEditorProps`
|
||||
|
||||
```ts
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
contentClassName?: string;
|
||||
onBlur?: () => void;
|
||||
imageUploadHandler?: (file: File) => Promise<string>;
|
||||
/** Called when a non-image file is dropped onto the editor (e.g. .zip). */
|
||||
onDropFile?: (file: File) => Promise<void>;
|
||||
bordered?: boolean;
|
||||
/** List of mentionable entities. Enables @-mention autocomplete. */
|
||||
mentions?: MentionOption[];
|
||||
/** Called on Cmd/Ctrl+Enter */
|
||||
onSubmit?: () => void;
|
||||
/** Render the rich editor without allowing edits. */
|
||||
readOnly?: boolean;
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `CompanySkills`, `IssueDetail`, `RoutineDetail`, `Routines`
|
||||
21
doc/design-system/components/PackageFileTree.md
Normal file
21
doc/design-system/components/PackageFileTree.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# PackageFileTree
|
||||
|
||||
`ui/src/components/PackageFileTree.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 3 imports (3 pages, 0 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 327 lines
|
||||
- **Sibling exports:** FRONTMATTER_FIELD_LABELS
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `CompanyExport`, `CompanyImport`
|
||||
36
doc/design-system/components/PageSkeleton.md
Normal file
36
doc/design-system/components/PageSkeleton.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# PageSkeleton
|
||||
|
||||
`ui/src/components/PageSkeleton.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 23 imports (22 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 181 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `PageSkeletonProps`
|
||||
|
||||
```ts
|
||||
variant?:
|
||||
| "list"
|
||||
| "issues-list"
|
||||
| "detail"
|
||||
| "dashboard"
|
||||
| "approvals"
|
||||
| "costs"
|
||||
| "inbox"
|
||||
| "org-chart";
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [skeleton](./Skeleton.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `Activity`, `AgentDetail`, `Agents`, `ApprovalDetail`, `Approvals` … (+17 more)
|
||||
32
doc/design-system/components/PageTabBar.md
Normal file
32
doc/design-system/components/PageTabBar.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# PageTabBar
|
||||
|
||||
`ui/src/components/PageTabBar.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 10 imports (9 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 46 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `PageTabBarProps`
|
||||
|
||||
```ts
|
||||
items: PageTabItem[];
|
||||
value?: string;
|
||||
onValueChange?: (value: string) => void;
|
||||
align?: "center" | "start";
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [tabs](./Tabs.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `Agents`, `Approvals`, `Costs`, `ExecutionWorkspaceDetail` … (+4 more)
|
||||
- **Components:** `CompanySettingsNav`
|
||||
30
doc/design-system/components/PathInstructionsModal.md
Normal file
30
doc/design-system/components/PathInstructionsModal.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# PathInstructionsModal
|
||||
|
||||
`ui/src/components/PathInstructionsModal.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 12 imports (2 pages, 3 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 144 lines
|
||||
- **Sibling exports:** ChoosePathButton
|
||||
|
||||
## Props
|
||||
|
||||
### `PathInstructionsModalProps`
|
||||
|
||||
```ts
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [dialog](./Dialog.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AdapterManager`, `ProjectWorkspaceDetail`
|
||||
22
doc/design-system/components/Popover.md
Normal file
22
doc/design-system/components/Popover.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Popover
|
||||
|
||||
`ui/src/components/ui/popover.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 27 imports (6 pages, 20 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 89 lines
|
||||
- **Sibling exports:** PopoverAnchor, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `DesignGuide`, `Inbox`, `IssueDetail`, `NewAgent` … (+1 more)
|
||||
- **Components:** `AgentConfigForm`, `AgentIconPicker`, `ExecutionParticipantPicker`, `GoalProperties`, `InlineEntitySelector` … (+15 more)
|
||||
31
doc/design-system/components/PriorityIcon.md
Normal file
31
doc/design-system/components/PriorityIcon.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# PriorityIcon
|
||||
|
||||
`ui/src/components/PriorityIcon.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 5 imports (2 pages, 3 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 78 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `PriorityIconProps`
|
||||
|
||||
```ts
|
||||
priority: string;
|
||||
onChange?: (priority: string) => void;
|
||||
className?: string;
|
||||
showLabel?: boolean;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md), [popover](./Popover.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `DesignGuide`, `IssueDetail`
|
||||
24
doc/design-system/components/RoutineRunVariablesDialog.md
Normal file
24
doc/design-system/components/RoutineRunVariablesDialog.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# RoutineRunVariablesDialog
|
||||
|
||||
`ui/src/components/RoutineRunVariablesDialog.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 3 imports (2 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 519 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md), [dialog](./Dialog.md), [input](./Input.md), [label](./Label.md), [select](./Select.md), [textarea](./Textarea.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `RoutineDetail`, `Routines`
|
||||
32
doc/design-system/components/RunTranscriptView.md
Normal file
32
doc/design-system/components/RunTranscriptView.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# RunTranscriptView
|
||||
|
||||
`ui/src/components/transcript/RunTranscriptView.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 3 imports (2 pages, 1 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 1446 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `RunTranscriptViewProps`
|
||||
|
||||
```ts
|
||||
entries: TranscriptEntry[];
|
||||
mode?: TranscriptMode;
|
||||
density?: TranscriptDensity;
|
||||
limit?: number;
|
||||
streaming?: boolean;
|
||||
collapseStdout?: boolean;
|
||||
emptyMessage?: string;
|
||||
className?: string;
|
||||
thinkingClassName?: string;
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `RunTranscriptUxLab`
|
||||
22
doc/design-system/components/ScrollArea.md
Normal file
22
doc/design-system/components/ScrollArea.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# ScrollArea
|
||||
|
||||
`ui/src/components/ui/scroll-area.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 3 imports (2 pages, 1 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 57 lines
|
||||
- **Sibling exports:** ScrollBar
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `DesignGuide`, `IssueDetail`
|
||||
- **Components:** `PropertiesPanel`
|
||||
22
doc/design-system/components/Select.md
Normal file
22
doc/design-system/components/Select.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Select
|
||||
|
||||
`ui/src/components/ui/select.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 10 imports (5 pages, 5 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 189 lines
|
||||
- **Sibling exports:** SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `Activity`, `DesignGuide`, `Inbox`, `RoutineDetail`, `Routines`
|
||||
- **Components:** `DocumentDiffModal`, `JsonSchemaForm`, `RoutineRunVariablesDialog`, `RoutineVariablesEditor`, `ScheduleEditor`
|
||||
21
doc/design-system/components/Separator.md
Normal file
21
doc/design-system/components/Separator.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Separator
|
||||
|
||||
`ui/src/components/ui/separator.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 11 imports (7 pages, 4 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 29 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `DesignGuide`, `ExecutionWorkspaceDetail`, `Inbox`, `IssueDetail`, `PluginSettings` … (+2 more)
|
||||
- **Components:** `AgentProperties`, `GoalProperties`, `IssueProperties`, `ProjectProperties`
|
||||
33
doc/design-system/components/SidebarNavItem.md
Normal file
33
doc/design-system/components/SidebarNavItem.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# SidebarNavItem
|
||||
|
||||
`ui/src/components/SidebarNavItem.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 3 imports (0 pages, 3 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 95 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `SidebarNavItemProps`
|
||||
|
||||
```ts
|
||||
to: string;
|
||||
label: string;
|
||||
icon: LucideIcon;
|
||||
end?: boolean;
|
||||
className?: string;
|
||||
badge?: number;
|
||||
badgeTone?: "default" | "danger";
|
||||
textBadge?: string;
|
||||
textBadgeTone?: "default" | "amber";
|
||||
alert?: boolean;
|
||||
liveCount?: number;
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
21
doc/design-system/components/Skeleton.md
Normal file
21
doc/design-system/components/Skeleton.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Skeleton
|
||||
|
||||
`ui/src/components/ui/skeleton.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 6 imports (3 pages, 3 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 14 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `DesignGuide`, `IssueDetail`
|
||||
- **Components:** `IssueWorkspaceCard`, `PageSkeleton`, `ProviderQuotaCard`
|
||||
20
doc/design-system/components/StatusBadge.md
Normal file
20
doc/design-system/components/StatusBadge.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# StatusBadge
|
||||
|
||||
`ui/src/components/StatusBadge.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 19 imports (12 pages, 7 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 16 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `Agents`, `ApprovalDetail`, `Costs`, `DesignGuide` … (+7 more)
|
||||
32
doc/design-system/components/StatusIcon.md
Normal file
32
doc/design-system/components/StatusIcon.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# StatusIcon
|
||||
|
||||
`ui/src/components/StatusIcon.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 14 imports (5 pages, 9 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 72 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `StatusIconProps`
|
||||
|
||||
```ts
|
||||
status: string;
|
||||
onChange?: (status: string) => void;
|
||||
className?: string;
|
||||
showLabel?: boolean;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md), [popover](./Popover.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `Dashboard`, `DesignGuide`, `Inbox`, `IssueDetail`, `MyIssues`
|
||||
- **Components:** `IssueLinkQuicklook` … (+8 more)
|
||||
27
doc/design-system/components/Tabs.md
Normal file
27
doc/design-system/components/Tabs.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Tabs
|
||||
|
||||
`ui/src/components/ui/tabs.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 15 imports (13 pages, 2 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 90 lines
|
||||
- **Sibling exports:** TabsContent, TabsList, TabsTrigger
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Variants (CVA)
|
||||
|
||||
- **variant**: `default`, `line`
|
||||
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `Agents`, `Approvals`, `Costs`, `DesignGuide` … (+8 more)
|
||||
- **Components:** `CompanySettingsNav`, `PageTabBar`
|
||||
21
doc/design-system/components/Textarea.md
Normal file
21
doc/design-system/components/Textarea.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Textarea
|
||||
|
||||
`ui/src/components/ui/textarea.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 9 imports (4 pages, 5 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 19 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `ApprovalDetail`, `CompanySkills`, `DesignGuide`, `ExecutionWorkspaceDetail`
|
||||
- **Components:** `IssueChatThread`, `JsonSchemaForm`, `OutputFeedbackButtons`, `RoutineRunVariablesDialog`, `RoutineVariablesEditor`
|
||||
21
doc/design-system/components/ToggleSwitch.md
Normal file
21
doc/design-system/components/ToggleSwitch.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# ToggleSwitch
|
||||
|
||||
`ui/src/components/ui/toggle-switch.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 8 imports (5 pages, 3 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 60 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `InstanceExperimentalSettings`, `InstanceGeneralSettings`, `RoutineDetail`, `Routines`
|
||||
- **Components:** `NewIssueDialog`, `ProjectProperties`, `agent-config-primitives`
|
||||
22
doc/design-system/components/Tooltip.md
Normal file
22
doc/design-system/components/Tooltip.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Tooltip
|
||||
|
||||
`ui/src/components/ui/tooltip.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 11 imports (3 pages, 7 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 58 lines
|
||||
- **Sibling exports:** TooltipContent, TooltipProvider, TooltipTrigger
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `CompanySkills`, `DesignGuide`
|
||||
- **Components:** `CompanyRail`, `IssueChatThread`, `IssueColumns`, `NewProjectDialog`, `ProjectProperties` … (+2 more)
|
||||
38
doc/design-system/components/WorkspaceRuntimeControls.md
Normal file
38
doc/design-system/components/WorkspaceRuntimeControls.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# WorkspaceRuntimeControls
|
||||
|
||||
`ui/src/components/WorkspaceRuntimeControls.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 3 imports (2 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 454 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `WorkspaceRuntimeControlsProps`
|
||||
|
||||
```ts
|
||||
sections: WorkspaceRuntimeControlSections;
|
||||
items?: never;
|
||||
isPending?: boolean;
|
||||
pendingRequest?: WorkspaceRuntimeControlRequest | null;
|
||||
serviceEmptyMessage?: string;
|
||||
jobEmptyMessage?: string;
|
||||
emptyMessage?: never;
|
||||
disabledHint?: string | null;
|
||||
onAction: (request: WorkspaceRuntimeControlRequest) => void;
|
||||
className?: string;
|
||||
square?: boolean;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `ExecutionWorkspaceDetail`, `ProjectWorkspaceDetail`
|
||||
25
doc/design-system/components/agent-config-primitives.md
Normal file
25
doc/design-system/components/agent-config-primitives.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# agent-config-primitives
|
||||
|
||||
`ui/src/components/agent-config-primitives.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 19 imports (4 pages, 3 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 464 lines
|
||||
- **Sibling exports:** AutoExpandTextarea, ChoosePathButton, CollapsibleSection, DraftInput, DraftNumberInput, DraftTextarea
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md), [dialog](./Dialog.md), `toggle-switch`, [tooltip](./Tooltip.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `CompanyImport`, `CompanySettings`, `NewAgent`
|
||||
393
doc/design-system/components/components-review.md
Normal file
393
doc/design-system/components/components-review.md
Normal file
@@ -0,0 +1,393 @@
|
||||
# Components Review
|
||||
|
||||
- **Generated:** 2026-04-21
|
||||
- **Repo SHA:** a26e1288b627e82c554445732c7d844648e6b5e1
|
||||
- **Inventory:** [index.md](./index.md)
|
||||
- **Token drift feeding components:** [../tokens/tokens-review.md](../tokens/tokens-review.md)
|
||||
- **Pattern docs drawn from this review:** [../patterns/index.md](../patterns/index.md)
|
||||
|
||||
---
|
||||
|
||||
## How to read this document
|
||||
|
||||
This is a **persistent backlog**, not just a snapshot review. It captures opportunities identified during DS extraction. **All items are intentionally deferred** per the 2026-04-21 decision: no component merges, no renames, no file-casing changes were made. Address opportunistically when the relevant code is touched for other reasons, or schedule as standalone follow-up projects.
|
||||
|
||||
Each entry below includes:
|
||||
- A **problem statement** — what was observed and why it might matter.
|
||||
- **Affected files** — so the next developer doesn't have to re-trace.
|
||||
- A **suggested resolution** — or "needs discussion" when the right call is not obvious.
|
||||
|
||||
Entries are ordered by expected value, not by stage. Sections are self-contained — you can pick one up cold without reading the rest.
|
||||
|
||||
---
|
||||
|
||||
## Likely duplicates
|
||||
|
||||
Pairs or families whose names, compositions, or role overlap enough that they may be consolidatable. None of these are automatic merges — each is a judgment call. Items 1–7 and 9 are documented as single patterns in [../patterns/](../patterns/) so the family is visible to future pattern extraction; this section captures the consolidation opportunity.
|
||||
|
||||
### 1. Entity-creation dialog family: `NewAgentDialog` / `NewGoalDialog` / `NewIssueDialog` / `NewProjectDialog`
|
||||
|
||||
Four parallel "create new X" dialogs, each used 1–2 times, each a composite that wraps a form-in-a-dialog. Strong candidate for a single generic `NewEntityDialog` or a `useNewEntityForm` hook plus entity-specific field sets.
|
||||
|
||||
| File | Uses |
|
||||
|---|---|
|
||||
| `ui/src/components/NewAgentDialog.tsx` | 1 |
|
||||
| `ui/src/components/NewGoalDialog.tsx` | 1 |
|
||||
| `ui/src/components/NewIssueDialog.tsx` | 2 |
|
||||
| `ui/src/components/NewProjectDialog.tsx` | 1 |
|
||||
|
||||
**Verify:** open the four files and diff them. If ≥60% body overlap, consolidate. Impact on Stage 4: otherwise a "New-entity dialog" pattern would be proposed with 4 instances, which is better represented as a single component.
|
||||
|
||||
### 2. Properties-panel family: `AgentProperties` / `GoalProperties` / `IssueProperties` / `ProjectProperties` (+ generic `PropertiesPanel`)
|
||||
|
||||
**Problem.** Four entity-specific property-panel components sit next to a generic `PropertiesPanel`. It is unclear whether `PropertiesPanel` **composes** the four (i.e., hosts their body via a context slot — which is what the 29-line `PropertiesPanel.tsx` appears to do) or whether the four **duplicate** the outer-chrome work that `PropertiesPanel` could own. `AgentProperties` is also unused in production (Storybook-only).
|
||||
|
||||
**Affected files.**
|
||||
|
||||
| File | Uses | Notes |
|
||||
|---|---|---|
|
||||
| `ui/src/components/PropertiesPanel.tsx` | 1 | 29-line generic chrome; reads `panelContent` from `usePanel()` context |
|
||||
| `ui/src/components/IssueProperties.tsx` | 2 | 1370-line entity-specific body |
|
||||
| `ui/src/components/GoalProperties.tsx` | 1 | Entity-specific body |
|
||||
| `ui/src/components/ProjectProperties.tsx` | 1 | 1140-line entity-specific body |
|
||||
| `ui/src/components/AgentProperties.tsx` | **0** | Storybook-only — see §Unused components |
|
||||
|
||||
**Suggested resolution: needs discussion.** Per the 2026-04-21 decision, the open question of "composes vs duplicates" is **flagged but not resolved**. Reading the source suggests `PropertiesPanel` is the outer slot and the four bodies are what gets passed into it — so not a literal duplicate. But the four bodies each re-roll section headers, separators, save-state plumbing, and field layout; those might be factor-able into a shared `<PropertiesPanelBody>` helper. Open `IssueProperties.tsx` and `ProjectProperties.tsx` side-by-side before making any call. Documented as-is in [../patterns/entity-properties-panel.md](../patterns/entity-properties-panel.md).
|
||||
|
||||
### 3. Subscription panel pair: `ClaudeSubscriptionPanel` ↔ `CodexSubscriptionPanel`
|
||||
|
||||
**Problem.** Two components with parallel names, parallel props shape (both accept `windows: QuotaWindow[]` + optional `source` / `error`), rendering ordered subscription-quota windows for a single provider. Both used exactly once — always dispatched from `ProviderQuotaCard`. When a third vendor (Gemini? Cursor?) is added, the pattern becomes "a third copy-paste" unless consolidated. Today's cost of keeping them separate is minor because the pair is only 2.
|
||||
|
||||
**Affected files.**
|
||||
|
||||
- `ui/src/components/ClaudeSubscriptionPanel.tsx` (1 use — 140 lines)
|
||||
- `ui/src/components/CodexSubscriptionPanel.tsx` (1 use)
|
||||
- `ui/src/components/ProviderQuotaCard.tsx` — composes both, dispatches by vendor
|
||||
|
||||
**Suggested resolution.** Diff-test the two files. If ≥70% body overlap, collapse to `SubscriptionPanel({ vendor: "claude" | "codex" | … })` with per-vendor window-key config as data. If divergence is higher, keep the pair and let `ProviderQuotaCard` continue to dispatch. Documented as-is in [../patterns/subscription-panel.md](../patterns/subscription-panel.md).
|
||||
|
||||
### 4. Sidebar-menu pair: `SidebarAccountMenu` ↔ `SidebarCompanyMenu`
|
||||
|
||||
**Problem.** Two dropdown components anchored to the sidebar slot — one for account actions (user profile, sign out), one for company actions (switch company, settings). Same visual affordance (dropdown triggered from a sidebar button), different data subject. Each used twice; neither is a hotspot.
|
||||
|
||||
**Affected files.**
|
||||
|
||||
- `ui/src/components/SidebarAccountMenu.tsx` (2 uses)
|
||||
- `ui/src/components/SidebarCompanyMenu.tsx` (2 uses)
|
||||
|
||||
**Suggested resolution.** Read both and check the body overlap. Natural consolidations if similar: `<SidebarMenu kind="account" | "company">` driven by kind, or a more general `<SidebarMenu>` that accepts items via a `children` slot. If the bodies are genuinely different (different menu items, different trigger layout), the pair stays and the shared pattern is the sidebar-slot anchoring — which is already captured by `SidebarNavItem`. Documented as-is in [../patterns/sidebar-chrome.md — §The sidebar-menu pair](../patterns/sidebar-chrome.md#the-sidebar-menu-pair).
|
||||
|
||||
### 5. Finance card family: `BillerSpendCard` / `FinanceBillerCard` / `FinanceKindCard` / `FinanceTimelineCard` / `AccountingModelCard`
|
||||
|
||||
**Problem.** Five cards in the finance/accounting surface, each used 0–1 times, all sharing the shadcn `Card` family as substrate. Two specific flags surfaced per the 2026-04-21 directive:
|
||||
|
||||
1. **`BillerSpendCard` ↔ `FinanceBillerCard` — likely a true duplicate.** Both name "Biller" in their filename. Both render a per-biller financial summary. They consume **different data models** (`CostByBiller` vs `FinanceByBiller`) — either two genuinely different reporting concepts whose names fail to distinguish them, or one superseded the other and the predecessor survived. Line counts differ (145 vs 44), consistent with a "rich" / "slim" pair.
|
||||
2. **`AccountingModelCard` is unused.** Zero imports anywhere in the codebase; only appears in Storybook. Either abandoned or awaiting a page.
|
||||
|
||||
**Affected files.**
|
||||
|
||||
| File | Uses | Data model | Lines |
|
||||
|---|---|---|---|
|
||||
| `ui/src/components/BillerSpendCard.tsx` | 1 | `CostByBiller` + `CostByProviderModel` | 145 |
|
||||
| `ui/src/components/FinanceBillerCard.tsx` | 1 | `FinanceByBiller` | 44 |
|
||||
| `ui/src/components/FinanceKindCard.tsx` | 1 | `FinanceByKind` (inferred) | — |
|
||||
| `ui/src/components/FinanceTimelineCard.tsx` | 1 | timeline rollup | — |
|
||||
| `ui/src/components/AccountingModelCard.tsx` | **0** | — | — |
|
||||
|
||||
**Suggested resolution.**
|
||||
- Diff `BillerSpendCard` vs `FinanceBillerCard` side-by-side. Rename for clarity, merge, or confirm as distinct-but-adjacent.
|
||||
- Decide `AccountingModelCard`'s fate — adopt it into a page, or delete.
|
||||
- The broader family (4 cards) is naturally a "Finance / accounting card" pattern. Documented as-is in [../patterns/finance-card.md](../patterns/finance-card.md).
|
||||
|
||||
### 6. Row family: `ActivityRow` / `EntityRow` / `IssueRow`
|
||||
|
||||
**Problem.** Three row components, one generic and two entity-specific. `EntityRow` is the generic (6 uses), `IssueRow` is issue-specific with an extensive slot interface (3 uses — Inbox + SwipeToArchive), `ActivityRow` is activity-event-specific (2 uses — Activity page). `IssueRow`'s 15-field prop shape (six of them optional `ReactNode` slot props) looks like it could be expressed as `<EntityRow kind="issue" />` plus issue-specific defaults.
|
||||
|
||||
**Affected files.**
|
||||
|
||||
| File | Uses | Notes |
|
||||
|---|---|---|
|
||||
| `ui/src/components/EntityRow.tsx` | 6 | Truly generic — leading / identifier / title / subtitle / trailing slot API |
|
||||
| `ui/src/components/IssueRow.tsx` | 3 | Composes `StatusIcon`; unread-state + archive action + mobile/desktop split |
|
||||
| `ui/src/components/ActivityRow.tsx` | 2 | Composes `Identity`, `IssueReferenceActivitySummary` — activity-event-specific |
|
||||
|
||||
**Suggested resolution.** Compare `IssueRow` against `EntityRow` directly. If the six slot props plus `unreadState` / `onArchive` can be implemented as `EntityRow` extensions (default slots keyed on `issue`), collapse. `ActivityRow`'s activity-verb formatting is genuinely specialized and is likely worth keeping separate even if `IssueRow` consolidates. Also relevant: the main list pages (Issues, Agents, Projects, …) don't use `EntityRow` today — see [../patterns/list-page.md — Open questions](../patterns/list-page.md#open-questions--risks). Documented as-is in [../patterns/entity-row.md](../patterns/entity-row.md).
|
||||
|
||||
### 7. Sidebar triad: `Sidebar` / `InstanceSidebar` / `CompanySettingsSidebar` + `CompanyRail` + `CompanySettingsNav` + `MobileBottomNav`
|
||||
|
||||
**Problem.** Six components with overlapping responsibilities — all are "chrome around the main view." Not literal duplicates because each targets a different surface (main nav vs instance settings vs company settings vs company switcher vs tab nav vs mobile bottom bar), but the three-word vocabulary (Sidebar / Rail / Nav) obscures whether these are variants of one structural pattern or separate components that happen to live near each other. They share navigation primitives (`SidebarNavItem`, `SidebarSection`) but not a unifying wrapper. The dead `sidebar-*` tokens in `ui/src/index.css` were designed for this family and none of these consume them (see [tokens-review.md §3](../tokens/tokens-review.md#3-sidebar--tokens-are-dead)).
|
||||
|
||||
**Affected files.**
|
||||
|
||||
| File | Uses | Role (inferred) |
|
||||
|---|---|---|
|
||||
| `ui/src/components/Sidebar.tsx` | ≥3 | Main app navigation |
|
||||
| `ui/src/components/InstanceSidebar.tsx` | 1 | Instance-settings scope |
|
||||
| `ui/src/components/CompanySettingsSidebar.tsx` | 2 | Company-settings scope |
|
||||
| `ui/src/components/CompanyRail.tsx` | 1 (260 lines, dnd-kit-driven) | Sortable company switcher rail |
|
||||
| `ui/src/components/access/CompanySettingsNav.tsx` | 1 | Settings-page top tab nav |
|
||||
| `ui/src/components/MobileBottomNav.tsx` | ≥3 | Mobile-bottom-tab alternative |
|
||||
|
||||
**Suggested resolution: needs discussion.** Three possible framings:
|
||||
- **(a) They are genuinely different components** (different layouts, different primitives, different affordances) and the naming convergence is coincidental. Closest to how the code reads today.
|
||||
- **(b) They are variants of a `<Sidebar variant="main" | "settings" | "rail" | …>`** and should consolidate under one name. Requires auditing their visual shape.
|
||||
- **(c) They are three distinct patterns** — "sidebar" (persistent rail), "rail" (narrow-strip), "nav" (tab-bar) — and the current spread is correct but the naming convention for picking between them isn't written down anywhere.
|
||||
|
||||
Tied to [§Naming inconsistencies — Sidebar / Rail / Nav](#sidebar--rail--nav). Documented as-is in [../patterns/sidebar-chrome.md](../patterns/sidebar-chrome.md).
|
||||
|
||||
### 8. Status display triad: `StatusIcon` / `StatusBadge` / `PriorityIcon`
|
||||
|
||||
**Problem.** Three components render entity status/priority across the app with the same visual language but different affordances. All three consume `ui/src/lib/status-colors.ts` — a canonical TypeScript catalog mapping status strings to raw Tailwind-palette classes. Two of the three (`StatusIcon`, `PriorityIcon`) type their primary prop as an **untyped string**; `StatusBadge` uses a typed variant. This inconsistency plus the fact that the catalog bypasses the DS token layer makes this a pattern-shape-pending item rather than a straightforward duplicate flag.
|
||||
|
||||
**Affected files.**
|
||||
|
||||
| File | Uses | Prop type | Notes |
|
||||
|---|---|---|---|
|
||||
| `ui/src/components/StatusIcon.tsx` | 14 | `status: string` (untyped) | Circle + popover picker |
|
||||
| `ui/src/components/StatusBadge.tsx` | 19 | `{ status: string }` wrapping `statusBadge[status]` | 15-line pill |
|
||||
| `ui/src/components/PriorityIcon.tsx` | 5 | `priority: string` (untyped) | Arrow/triangle + popover picker |
|
||||
| `ui/src/lib/status-colors.ts` | — | — | Catalog consumed by all three |
|
||||
| `ui/src/components/AgentActionButtons.tsx` | — | — | Consumes `agentStatusDot` from the same catalog |
|
||||
|
||||
**2026-04-21 status.** The token-side of this problem was **partially addressed**: `--signal-success` / `--signal-success-foreground` landed as action-severity tokens paired with `--destructive` (see [tokens-review.md §4](../tokens/tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds)). `status-colors.ts` itself is **not** touched — tokenizing its entity-state coloring into a `--status-*` family is a deferred future project. So the two problems here are now independent:
|
||||
- **Signal (action severity)** = tokens exist, no consumers yet, opt-in.
|
||||
- **Status (entity state)** = catalog stays as raw Tailwind palette, unchanged.
|
||||
|
||||
**Suggested resolution: do not codify a unified status-display pattern in this DS pass.** Pattern shape is explicitly pending the eventual signal-token / status-token scoping. When that project happens, the shape of these three components (typed enum props, class naming, default-fallback behavior) will want to change in sync. Documented as-is in [../patterns/status-display.md](../patterns/status-display.md).
|
||||
|
||||
Separately — and independent of the tokens — the `status: string` / `priority: string` untyped props could be typed as string literal unions over the keys of `status-colors.ts`'s records without touching colors. That's a small, self-contained follow-up.
|
||||
|
||||
### 9. Quota display: `ProviderQuotaCard` ↔ `QuotaBar`
|
||||
|
||||
**Problem.** Two components share the "quota" root name but differ in suffix and in role. `QuotaBar` is a rendering primitive — one horizontal bar with a percent-used fill and a three-level color threshold. `ProviderQuotaCard` is a card composer that *uses* `QuotaBar` (multiple times, for different time windows) plus `ClaudeSubscriptionPanel` / `CodexSubscriptionPanel`. Functionally different, but the `-Bar` / `-Card` naming spread suggests parallelism that isn't there.
|
||||
|
||||
Secondary concern: `QuotaBar` hardcodes three-level severity as raw Tailwind palette (`bg-red-400`, `bg-yellow-400`, `bg-green-400`). It is one of four places in the codebase that encode the same red/amber/green severity language without shared tokens — see [../tokens/tokens-review.md §4](../tokens/tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds) and [../patterns/patterns-review.md §6 — Severity indicator](../patterns/patterns-review.md#6-severity-indicator-3-level-health-display--pattern-opportunity).
|
||||
|
||||
**Affected files.**
|
||||
|
||||
- `ui/src/components/QuotaBar.tsx` (2 uses — 65 lines — primitive bar)
|
||||
- `ui/src/components/ProviderQuotaCard.tsx` (1 use — 416 lines — card composer that embeds `QuotaBar`)
|
||||
|
||||
**Suggested resolution.** Not a consolidation target — different roles. The naming can be clearer if a future refactor happens (e.g., rename the composer to `ProviderQuotaSummary` or `QuotaOverviewCard`). Separate concern: the three-level severity coloring in `QuotaBar` would collapse onto signal/status tokens when that broader work lands. Documented as-is in [../patterns/quota-display.md](../patterns/quota-display.md).
|
||||
|
||||
---
|
||||
|
||||
## Naming inconsistencies
|
||||
|
||||
**Status: deferred (2026-04-21).** All vocabulary decisions below (Dialog-vs-Modal, Picker-vs-Selector, Editor-vs-Form, Sidebar-vs-Rail-vs-Nav, Card-vs-Panel-vs-Widget, file-name casing, prop-vocabulary conventions) are intentionally unresolved. Addressing opportunistically when the relevant code is touched for other reasons, or as a standalone naming-pass project. Each subsection is self-contained — pick any one up cold without reading the others. In pattern docs under `../patterns/`, vocabulary is noted as observed variance (e.g., "dialog pattern — some implementations named `*Modal`, same primitive") rather than as a canonical prescription.
|
||||
|
||||
### Container-word proliferation: `Card` vs `Panel` vs `Widget` vs `Modal` vs `Dialog`
|
||||
|
||||
Counts of components by suffix:
|
||||
|
||||
| Suffix | Count | Examples |
|
||||
|---|---|---|
|
||||
| `Card` | 12 | `ApprovalCard`, `BudgetPolicyCard`, `MetricCard`, … |
|
||||
| `Panel` | 5 | `ActiveAgentsPanel`, `ClaudeSubscriptionPanel`, `PropertiesPanel`, … |
|
||||
| `Widget` | 1 | `LiveRunWidget` |
|
||||
| `Modal` | 3 | `DocumentDiffModal`, `ImageGalleryModal`, `PathInstructionsModal` |
|
||||
| `Dialog` | 6 | `NewAgentDialog`, `ExecutionWorkspaceCloseDialog`, `RoutineRunVariablesDialog`, … |
|
||||
|
||||
**Dialog vs Modal:** both Modal- and Dialog-named components use the same `dialog.tsx` primitive. There's no structural distinction — just two names for the same thing. Pick one. The shadcn default is `Dialog`; keeping `Dialog` is the lower-friction move.
|
||||
|
||||
**Card vs Panel:** less clear-cut. Rough pattern in this codebase:
|
||||
- `*Card` when the thing is a discrete piece of content in a grid (`BudgetPolicyCard`, `FinanceBillerCard`).
|
||||
- `*Panel` when the thing is a larger region that groups related content (`ActiveAgentsPanel`, `PropertiesPanel`).
|
||||
- But there are violations: `ClaudeSubscriptionPanel` and `AccountingModelCard` look alike structurally and are adjacent in usage.
|
||||
|
||||
**Widget (1):** only `LiveRunWidget` uses this. Either absorb into `Card`/`Panel` or codify `Widget` as a distinct concept (e.g., "dashboard-tile with its own data fetch and refresh cadence") and use it consistently.
|
||||
|
||||
### `Picker` vs `Selector`
|
||||
|
||||
| Name | Uses | Picks what |
|
||||
|---|---|---|
|
||||
| `AgentIconPicker` | 13 | An icon |
|
||||
| `ExecutionParticipantPicker` | 0 (unused) | A participant |
|
||||
| `ReportsToPicker` | 2 | An agent |
|
||||
| `InlineEntitySelector` | 1 | An entity |
|
||||
|
||||
All four wrap a popover-plus-list. Picker and Selector are synonymous here. Pick one term.
|
||||
|
||||
### `Editor` vs `Form`
|
||||
|
||||
| Name | Uses | Purpose |
|
||||
|---|---|---|
|
||||
| `MarkdownEditor` | 16 | Markdown input |
|
||||
| `InlineEditor` | 6 | Generic text editor |
|
||||
| `EnvVarEditor` | 2 | Structured key=value list |
|
||||
| `RoutineVariablesEditor` | 2 | Structured variable list |
|
||||
| `ScheduleEditor` | 1 | Cron schedule |
|
||||
| `AgentConfigForm` | 5 | Full agent config |
|
||||
| `JsonSchemaForm` | 1 | Schema-driven form |
|
||||
|
||||
The line between Editor and Form is fuzzy: `RoutineVariablesEditor` looks like a form, `JsonSchemaForm` could have been named `JsonSchemaEditor`. Suggested rule: **Editor** for content inputs (text, schedule, markdown); **Form** for labeled-field structured forms. Audit whether any renaming is worth the churn; otherwise document the rule and enforce for new additions.
|
||||
|
||||
### Sidebar / Rail / Nav
|
||||
|
||||
For what are structurally all "chrome around the main content area":
|
||||
|
||||
- `Sidebar.tsx`
|
||||
- `InstanceSidebar.tsx`
|
||||
- `CompanySettingsSidebar.tsx`
|
||||
- `CompanyRail.tsx`
|
||||
- `MobileBottomNav.tsx`
|
||||
- `access/CompanySettingsNav.tsx`
|
||||
|
||||
Pick a default term (`Sidebar`) and use a prefix for variants (`InstanceSidebar`, `CompanySettingsSidebar`). Reserve `Rail` for genuinely different (narrow-strip) affordances, `Nav` for tab-bar-style navigation. The current split is inconsistent with itself.
|
||||
|
||||
### File-naming convention is inconsistent
|
||||
|
||||
Most composites are `PascalCase.tsx`. Shadcn primitives are `kebab-case.tsx`. But `agent-config-primitives.tsx` and `agent-config-defaults.ts` sit in the top-level composite directory in kebab-case — they don't belong with the shadcn primitives (not in `ui/`) but also don't match the PascalCase of their neighbors.
|
||||
|
||||
Suggested fix: rename `agent-config-primitives.tsx` → `AgentConfigPrimitives.tsx` (or split into per-component files if the 11 exports warrant it) and `agent-config-defaults.ts` → `agentConfigDefaults.ts` to match JS convention for non-component modules.
|
||||
|
||||
### Prop vocabulary is underspecified
|
||||
|
||||
A static scan for conventional variant-shaping props (`variant`, `size`, `intent`, `tone`, `kind`, `state`, `status`, `mode`, `level`, `severity`, `priority`) found them used in just **7** components across the codebase (excluding shadcn primitives where they're well-defined):
|
||||
|
||||
| Prop | Component | Type |
|
||||
|---|---|---|
|
||||
| `variant` | `IssueChatThread` | `"full" \| "embedded"` |
|
||||
| `variant` | `PageSkeleton` | `... \| "list"` (opaque) |
|
||||
| `size` | `Identity` | `IdentitySize` |
|
||||
| `kind` | `ProjectWorkspaceSummaryCard` | `"project_workspace" \| "execution_workspace"` |
|
||||
| `status` | `StatusIcon` | `string` (**untyped**) |
|
||||
| `mode` | `RunTranscriptView` | `TranscriptMode` |
|
||||
| `priority` | `PriorityIcon` | `string` (**untyped**) |
|
||||
|
||||
Observations:
|
||||
- `status: string` in `StatusIcon` and `priority: string` in `PriorityIcon` should be typed enums (matching the keys of `status-colors.ts`).
|
||||
- `variant` is used for completely unrelated concepts in different components — that's fine semantically, but reinforces that there's no shared prop-vocabulary convention.
|
||||
- Shadcn primitives (`button`, `badge`) use `variant`/`size` with well-defined CVA enums — these are the model.
|
||||
|
||||
---
|
||||
|
||||
## Token non-compliance
|
||||
|
||||
(Mirror of the Stage 1 token drift findings, but attributed per-component so Stage 3 reviewers can target the worst offenders.)
|
||||
|
||||
### Components that hardcode chart/status colors
|
||||
|
||||
From [tokens-review.md §1 and §4](../tokens/tokens-review.md):
|
||||
|
||||
| Component | Drift |
|
||||
|---|---|
|
||||
| `ActivityCharts.tsx` | 17 hardcoded Tailwind-palette hex values for status/priority chart colors; uses `chart-*` tokens zero times. |
|
||||
| `OrgChart.tsx` (a page) | 6 hardcoded hex values for agent status dot colors. |
|
||||
| `StatusIcon.tsx` (14 uses) | Consumes `issueStatusIcon` from `status-colors.ts` — raw Tailwind palette classes (`text-blue-600`, `border-violet-600`, etc.). |
|
||||
| `StatusBadge.tsx` (19 uses) | Consumes `statusBadge` from `status-colors.ts` — raw Tailwind palette classes. |
|
||||
| `PriorityIcon.tsx` (5 uses) | Consumes `priorityColor` — raw Tailwind palette. |
|
||||
| `AgentActionButtons.tsx` | Uses `agentStatusDot` — raw Tailwind palette. |
|
||||
|
||||
### Components with heavy raw-palette styling
|
||||
|
||||
From Stage 1's 659-hit analysis:
|
||||
|
||||
- `AgentDetail.tsx` (75 palette hits) — production page
|
||||
- `RunTranscriptView.tsx` (47 hits) — production component
|
||||
- `IssueChatThread.tsx` (22 hits) — production component
|
||||
|
||||
### Components with arbitrary radius values
|
||||
|
||||
See [tokens-review.md §7](../tokens/tokens-review.md#7-arbitrary-radius-values-bypass-the-scale-18-occurrences) for the full list. Production components in the list:
|
||||
- `CompanyRail.tsx` — `rounded-[14px]`, `rounded-[22px]`
|
||||
- Several UxLab pages (acceptable as prototypes)
|
||||
|
||||
### Recommendation
|
||||
|
||||
Do not extract per-component detail docs for `StatusIcon`, `StatusBadge`, `PriorityIcon`, `AgentActionButtons` as final specs — their color language is blocked on the signal-token decision from [tokens-review.md §4](../tokens/tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds). Stage-4 patterns that lean on these (status indicator, priority indicator, agent card with status) will change shape once signal tokens land.
|
||||
|
||||
---
|
||||
|
||||
## Story coverage gaps
|
||||
|
||||
Components with **3+ code uses and no Storybook coverage** — the highest-priority story gaps:
|
||||
|
||||
| Component | Uses | Notes |
|
||||
|---|---|---|
|
||||
| `StatusIcon` | 14 | Central status-color consumer. Coverage gap tied to §Token non-compliance. |
|
||||
| `collapsible` (primitive) | 8 | Shadcn primitive. `foundations.stories.tsx` covers many primitives but skips collapsible. |
|
||||
| `dropdown-menu` (primitive) | 8 | Same. |
|
||||
| `avatar` (primitive) | 7 | Same — despite Avatar being one of the few components with sub-parts (`AvatarGroup`, `AvatarFallback`, `AvatarBadge`). |
|
||||
| `skeleton` (primitive) | 6 | Same. |
|
||||
| `scroll-area` (primitive) | 3 | Same. |
|
||||
| `ApprovalPayload` | 4 | Feature. |
|
||||
| `IssueReferencePill` | 4 | Feature. |
|
||||
| `SidebarNavItem` | 3 | Structural. |
|
||||
| `RunTranscriptView` | 3 | Feature — the transcript rendering. |
|
||||
|
||||
Two categories:
|
||||
1. **Shadcn primitives missing from `foundations.stories.tsx`.** Small, targeted fix — add them to the existing foundations story.
|
||||
2. **Production features without a story.** `StatusIcon` is the highest value given its role across 14 call sites.
|
||||
|
||||
---
|
||||
|
||||
## Unused / low-signal components
|
||||
|
||||
### Truly dead (0 imports, no Storybook coverage): 0
|
||||
|
||||
None. Every file in `ui/src/components/` either gets imported somewhere or appears in a story.
|
||||
|
||||
### Storybook-only (0 imports, appears in a story): 4
|
||||
|
||||
These are rendered in a story file but never imported by any page or other component:
|
||||
|
||||
| Component | Storybook location (approx.) |
|
||||
|---|---|
|
||||
| `AccountingModelCard` | financial/accounting-related story |
|
||||
| `AgentProperties` | agent-management story |
|
||||
| `CompanySwitcher` | navigation-layout story |
|
||||
| `ExecutionParticipantPicker` | (story-referenced; unused in app) |
|
||||
|
||||
**Interpretation:** these are either (a) abandoned experiments still living in Storybook, (b) components waiting for the page that uses them, or (c) genuinely unused and should be deleted. Recommend: owner disposition per file. `AgentProperties` is especially surprising given the existence of sibling `IssueProperties`/`GoalProperties`/`ProjectProperties` — possibly a planned-but-not-wired variant of the properties family.
|
||||
|
||||
### Below-threshold (1–2 code uses, no detail file): 76
|
||||
|
||||
Not drift — many are legitimately single-use (one-off dialogs, one-off banners). But at 76 out of ~130 it's worth noting: this codebase heavily favors single-use components. A generic-component consolidation pass would likely shrink this group by ~30%. The candidates from §Likely duplicates above are the best places to start.
|
||||
|
||||
---
|
||||
|
||||
## Plugin SDK hybrid status, prioritization deferred
|
||||
|
||||
**Status: RESOLVED as intentional hybrid (2026-04-21).** Not drift. The plugin SDK (`packages/plugins/sdk/src/ui/components.ts`) declares 11 ambient component types; the host implements 2 (`MetricCard`, `StatusBadge`) and leaves the other 9 as contract-only. The 9 unimplemented components now carry a `@status contract-only` JSDoc tag in the SDK source so plugin authors see the status in IDE tooltips at call sites.
|
||||
|
||||
Prioritization of which of the 9 to build first is a **separate plugin-SDK roadmap conversation** — not a DS decision and not in scope here. This section captures the current state and the most likely first-implementations when that conversation happens.
|
||||
|
||||
### Current state
|
||||
|
||||
| SDK contract | Host implementation | Status |
|
||||
|---|---|---|
|
||||
| `MetricCard` | [`ui/src/components/MetricCard.tsx`](../../../ui/src/components/MetricCard.tsx) | ✅ implemented |
|
||||
| `StatusBadge` | [`ui/src/components/StatusBadge.tsx`](../../../ui/src/components/StatusBadge.tsx) | ✅ implemented |
|
||||
| `DataTable` | — | 🔌 contract-only |
|
||||
| `TimeseriesChart` | — | 🔌 contract-only |
|
||||
| `MarkdownBlock` | — | 🔌 contract-only |
|
||||
| `KeyValueList` | — | 🔌 contract-only |
|
||||
| `ActionBar` | — | 🔌 contract-only |
|
||||
| `LogView` | — | 🔌 contract-only |
|
||||
| `JsonTree` | — | 🔌 contract-only |
|
||||
| `Spinner` | — | 🔌 contract-only |
|
||||
| `ErrorBoundary` | — | 🔌 contract-only |
|
||||
|
||||
Each of the 9 contract-only entries has a JSDoc block in [`packages/plugins/sdk/src/ui/components.ts`](../../../packages/plugins/sdk/src/ui/components.ts) (lines 253–316) that tells plugin authors the runtime will fail and points here.
|
||||
|
||||
`PLUGIN_SPEC.md:30` already acknowledged this before the DS extraction: _"The current runtime does not yet ship a real host-provided plugin UI component kit."_
|
||||
|
||||
### Implementation notes (for when prioritization happens)
|
||||
|
||||
Not prescriptive — candidates surfaced during the 2026-04 extraction:
|
||||
|
||||
- **`MarkdownBlock`** — thinnest wrapper around `ui/src/components/MarkdownBody.tsx`. Possibly an alias rather than a new component.
|
||||
- **`Spinner`** — no matching host component. A ~10-line shadcn-style primitive would be the simplest new build.
|
||||
- **`KeyValueList`** — patterns exist ad-hoc inside `EntityRow`, `PropertiesPanel` bodies, and `FinanceBillerCard`. Candidate for extraction into a shared primitive.
|
||||
- **`LogView`** — no counterpart. Transcript rendering is tightly coupled to `RunTranscriptView`; a generic log viewer is a genuine new build.
|
||||
- **`JsonTree`** — no counterpart. A new build.
|
||||
- **`ErrorBoundary`** — standard React pattern; a thin wrapper around a React error boundary class.
|
||||
- **`ActionBar`**, **`DataTable`**, **`TimeseriesChart`** — each is a real component's worth of surface area. Not thin builds.
|
||||
|
||||
### Affected files if the prioritization conversation opens
|
||||
|
||||
- `packages/plugins/sdk/src/ui/components.ts` (SDK declarations + @status tags)
|
||||
- `packages/plugins/sdk/src/ui/runtime.ts` (runtime bridge — `renderSdkUiComponent`)
|
||||
- New host files in `ui/src/components/` for each implemented contract
|
||||
- [`components/index.md` — Plugin SDK contracts table](./index.md#plugin-sdk-contracts-11) (update implementation column as each lands)
|
||||
228
doc/design-system/components/index.md
Normal file
228
doc/design-system/components/index.md
Normal file
@@ -0,0 +1,228 @@
|
||||
# Components — Index
|
||||
|
||||
- **Generated:** 2026-04-21
|
||||
- **Repo SHA:** a26e1288b627e82c554445732c7d844648e6b5e1
|
||||
- **Scope:** `ui/` + plugin SDK contracts
|
||||
- **Review:** [components-review.md](./components-review.md)
|
||||
|
||||
## Counts
|
||||
|
||||
- **Total component files:** 135
|
||||
- **With dedicated detail files (3+ code uses):** 53
|
||||
- **Below threshold (1–2 uses):** 76
|
||||
- **Storybook-only (in stories, 0 code uses):** 4
|
||||
- **Dead (no uses, no stories):** 0
|
||||
- **Non-component files (hooks, defaults):** 2
|
||||
- **Plugin SDK contracts:** 11 (2 implemented by name, 9 contract-only — see §Plugin SDK contracts below)
|
||||
|
||||
### By category
|
||||
|
||||
| Category | Count |
|
||||
|---|---|
|
||||
| composite | 64 |
|
||||
| primitive | 22 |
|
||||
| standalone | 47 |
|
||||
| utility-or-hook | 2 |
|
||||
|
||||
**Status markers in the tables below:**
|
||||
|
||||
- 📗 **documented** — ≥3 imports, has its own detail file in this directory
|
||||
- 📘 **below-threshold** — 1–2 imports, no detail file
|
||||
- 📙 **storybook-only** — 0 code imports, but appears in a story file
|
||||
- ☠️ **dead** — 0 imports, 0 stories
|
||||
- 🔌 **contract-only** — plugin SDK ambient declaration with no matching host implementation
|
||||
|
||||
---
|
||||
|
||||
## Primitives — `ui/src/components/ui/` (shadcn, 22)
|
||||
|
||||
All 22 shadcn primitives, by file name. These are the non-negotiable UI vocabulary — composites should consume these before reaching for custom markup.
|
||||
|
||||
| Component | Path | Uses | Pages / Comps | Story |
|
||||
|---|---|---|---|---|
|
||||
| 📗 [Button](./Button.md) | `ui/src/components/ui/button.tsx` | 81 | 41 / 38 | ✓ |
|
||||
| 📗 [Popover](./Popover.md) | `ui/src/components/ui/popover.tsx` | 27 | 6 / 20 | ✓ |
|
||||
| 📗 [Dialog](./Dialog.md) | `ui/src/components/ui/dialog.tsx` | 21 | 7 / 14 | ✓ |
|
||||
| 📗 [Input](./Input.md) | `ui/src/components/ui/input.tsx` | 20 | 10 / 10 | ✓ |
|
||||
| 📗 [Badge](./Badge.md) | `ui/src/components/ui/badge.tsx` | 18 | 11 / 7 | ✓ |
|
||||
| 📗 [Card](./Card.md) | `ui/src/components/ui/card.tsx` | 18 | 10 / 8 | ✓ |
|
||||
| 📗 [Tabs](./Tabs.md) | `ui/src/components/ui/tabs.tsx` | 15 | 13 / 2 | ✓ |
|
||||
| 📗 [Separator](./Separator.md) | `ui/src/components/ui/separator.tsx` | 11 | 7 / 4 | ✓ |
|
||||
| 📗 [Tooltip](./Tooltip.md) | `ui/src/components/ui/tooltip.tsx` | 11 | 3 / 7 | ✓ |
|
||||
| 📗 [Select](./Select.md) | `ui/src/components/ui/select.tsx` | 10 | 5 / 5 | ✓ |
|
||||
| 📗 [Textarea](./Textarea.md) | `ui/src/components/ui/textarea.tsx` | 9 | 4 / 5 | ✓ |
|
||||
| 📗 [Collapsible](./Collapsible.md) | `ui/src/components/ui/collapsible.tsx` | 8 | 4 / 4 | — |
|
||||
| 📗 [DropdownMenu](./DropdownMenu.md) | `ui/src/components/ui/dropdown-menu.tsx` | 8 | 3 / 5 | — |
|
||||
| 📗 [Label](./Label.md) | `ui/src/components/ui/label.tsx` | 8 | 5 / 3 | ✓ |
|
||||
| 📗 [ToggleSwitch](./ToggleSwitch.md) | `ui/src/components/ui/toggle-switch.tsx` | 8 | 5 / 3 | ✓ |
|
||||
| 📗 [Avatar](./Avatar.md) | `ui/src/components/ui/avatar.tsx` | 7 | 3 / 4 | — |
|
||||
| 📗 [Checkbox](./Checkbox.md) | `ui/src/components/ui/checkbox.tsx` | 6 | 4 / 2 | ✓ |
|
||||
| 📗 [Skeleton](./Skeleton.md) | `ui/src/components/ui/skeleton.tsx` | 6 | 3 / 3 | — |
|
||||
| 📗 [ScrollArea](./ScrollArea.md) | `ui/src/components/ui/scroll-area.tsx` | 3 | 2 / 1 | — |
|
||||
| 📘 Breadcrumb | `ui/src/components/ui/breadcrumb.tsx` | 2 | 1 / 1 | — |
|
||||
| 📘 Command | `ui/src/components/ui/command.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 Sheet | `ui/src/components/ui/sheet.tsx` | 2 | 2 / 0 | — |
|
||||
|
||||
---
|
||||
|
||||
## Composites (64)
|
||||
|
||||
Components that import 1+ other component from `@/components/*`. Application-level feature UI.
|
||||
|
||||
| Component | Path | Uses | Pages / Comps | Story |
|
||||
|---|---|---|---|---|
|
||||
| 📗 [PageSkeleton](./PageSkeleton.md) | `ui/src/components/PageSkeleton.tsx` | 23 | 22 / 1 | ✓ |
|
||||
| 📗 [EmptyState](./EmptyState.md) | `ui/src/components/EmptyState.tsx` | 20 | 19 / 1 | ✓ |
|
||||
| 📗 [agent-config-primitives](./agent-config-primitives.md) | `ui/src/components/agent-config-primitives.tsx` | 19 | 4 / 3 | ✓ |
|
||||
| 📗 [Identity](./Identity.md) | `ui/src/components/Identity.tsx` | 19 | 7 / 12 | ✓ |
|
||||
| 📗 [StatusIcon](./StatusIcon.md) | `ui/src/components/StatusIcon.tsx` | 14 | 5 / 9 | — |
|
||||
| 📗 [AgentIconPicker](./AgentIconPicker.md) | `ui/src/components/AgentIconPicker.tsx` | 13 | 4 / 9 | ✓ |
|
||||
| 📗 [PathInstructionsModal](./PathInstructionsModal.md) | `ui/src/components/PathInstructionsModal.tsx` | 12 | 2 / 3 | ✓ |
|
||||
| 📗 [PageTabBar](./PageTabBar.md) | `ui/src/components/PageTabBar.tsx` | 10 | 9 / 1 | ✓ |
|
||||
| 📗 [InlineEntitySelector](./InlineEntitySelector.md) | `ui/src/components/InlineEntitySelector.tsx` | 8 | 2 / 4 | ✓ |
|
||||
| 📗 [IssuesList](./IssuesList.md) | `ui/src/components/IssuesList.tsx` | 6 | 5 / 1 | ✓ |
|
||||
| 📗 [AgentConfigForm](./AgentConfigForm.md) | `ui/src/components/AgentConfigForm.tsx` | 5 | 3 / 0 | ✓ |
|
||||
| 📗 [PriorityIcon](./PriorityIcon.md) | `ui/src/components/PriorityIcon.tsx` | 5 | 2 / 3 | ✓ |
|
||||
| 📗 [IssueChatThread](./IssueChatThread.md) | `ui/src/components/IssueChatThread.tsx` | 4 | 2 / 2 | ✓ |
|
||||
| 📗 [ApprovalCard](./ApprovalCard.md) | `ui/src/components/ApprovalCard.tsx` | 3 | 2 / 1 | ✓ |
|
||||
| 📗 [BudgetPolicyCard](./BudgetPolicyCard.md) | `ui/src/components/BudgetPolicyCard.tsx` | 3 | 3 / 0 | ✓ |
|
||||
| 📗 [IssueFiltersPopover](./IssueFiltersPopover.md) | `ui/src/components/IssueFiltersPopover.tsx` | 3 | 1 / 2 | ✓ |
|
||||
| 📗 [IssueLinkQuicklook](./IssueLinkQuicklook.md) | `ui/src/components/IssueLinkQuicklook.tsx` | 3 | 0 / 2 | ✓ |
|
||||
| 📗 [IssueWorkspaceCard](./IssueWorkspaceCard.md) | `ui/src/components/IssueWorkspaceCard.tsx` | 3 | 1 / 2 | ✓ |
|
||||
| 📗 [RoutineRunVariablesDialog](./RoutineRunVariablesDialog.md) | `ui/src/components/RoutineRunVariablesDialog.tsx` | 3 | 2 / 1 | ✓ |
|
||||
| 📗 [WorkspaceRuntimeControls](./WorkspaceRuntimeControls.md) | `ui/src/components/WorkspaceRuntimeControls.tsx` | 3 | 2 / 1 | ✓ |
|
||||
| 📘 AgentActionButtons | `ui/src/components/AgentActionButtons.tsx` | 2 | 2 / 0 | ✓ |
|
||||
| 📘 CommandPalette | `ui/src/components/CommandPalette.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 IssueColumns | `ui/src/components/IssueColumns.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 IssueContinuationHandoff | `ui/src/components/IssueContinuationHandoff.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 IssueDocumentsSection | `ui/src/components/IssueDocumentsSection.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 IssueProperties | `ui/src/components/IssueProperties.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 NewIssueDialog | `ui/src/components/NewIssueDialog.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 OutputFeedbackButtons | `ui/src/components/OutputFeedbackButtons.tsx` | 2 | 0 / 2 | — |
|
||||
| 📘 ProjectWorkspaceSummaryCard | `ui/src/components/ProjectWorkspaceSummaryCard.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 ReportsToPicker | `ui/src/components/ReportsToPicker.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 RoutineVariablesEditor | `ui/src/components/RoutineVariablesEditor.tsx` | 2 | 2 / 0 | ✓ |
|
||||
| 📘 Sidebar | `ui/src/components/Sidebar.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 SidebarAccountMenu | `ui/src/components/SidebarAccountMenu.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 SidebarCompanyMenu | `ui/src/components/SidebarCompanyMenu.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 CompanySettingsNav | `ui/src/components/access/CompanySettingsNav.tsx` | 1 | 0 / 1 | — |
|
||||
| 📘 ModeBadge | `ui/src/components/access/ModeBadge.tsx` | 1 | 1 / 0 | — |
|
||||
| 📘 BillerSpendCard | `ui/src/components/BillerSpendCard.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 BreadcrumbBar | `ui/src/components/BreadcrumbBar.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 BudgetIncidentCard | `ui/src/components/BudgetIncidentCard.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 CommentThread | `ui/src/components/CommentThread.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 CompanyRail | `ui/src/components/CompanyRail.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 DocumentDiffModal | `ui/src/components/DocumentDiffModal.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 FilterBar | `ui/src/components/FilterBar.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 FinanceBillerCard | `ui/src/components/FinanceBillerCard.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 FinanceKindCard | `ui/src/components/FinanceKindCard.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 FinanceTimelineCard | `ui/src/components/FinanceTimelineCard.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 GoalProperties | `ui/src/components/GoalProperties.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 IssuesQuicklook | `ui/src/components/IssuesQuicklook.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 JsonSchemaForm | `ui/src/components/JsonSchemaForm.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 KeyboardShortcutsCheatsheet | `ui/src/components/KeyboardShortcutsCheatsheet.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 NewAgentDialog | `ui/src/components/NewAgentDialog.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 NewGoalDialog | `ui/src/components/NewGoalDialog.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 NewProjectDialog | `ui/src/components/NewProjectDialog.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 OnboardingWizard | `ui/src/components/OnboardingWizard.tsx` | 1 | 0 / 0 | ✓ |
|
||||
| 📘 ProjectProperties | `ui/src/components/ProjectProperties.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 PropertiesPanel | `ui/src/components/PropertiesPanel.tsx` | 1 | 0 / 1 | — |
|
||||
| 📘 ProviderQuotaCard | `ui/src/components/ProviderQuotaCard.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 ScheduleEditor | `ui/src/components/ScheduleEditor.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 SidebarAgents | `ui/src/components/SidebarAgents.tsx` | 1 | 0 / 1 | — |
|
||||
| 📘 SidebarProjects | `ui/src/components/SidebarProjects.tsx` | 1 | 0 / 1 | — |
|
||||
| 📙 AccountingModelCard | `ui/src/components/AccountingModelCard.tsx` | 0 | 0 / 0 | ✓ |
|
||||
| 📙 AgentProperties | `ui/src/components/AgentProperties.tsx` | 0 | 0 / 0 | ✓ |
|
||||
| 📙 CompanySwitcher | `ui/src/components/CompanySwitcher.tsx` | 0 | 0 / 0 | ✓ |
|
||||
| 📙 ExecutionParticipantPicker | `ui/src/components/ExecutionParticipantPicker.tsx` | 0 | 0 / 0 | ✓ |
|
||||
|
||||
---
|
||||
|
||||
## Standalones (47)
|
||||
|
||||
Components that import no other `@/components/*`. Usually: icons, self-contained widgets, components that only depend on radix / lucide / local libs. The fact that they import zero composites or primitives is itself a data point — some of these probably should be using primitives.
|
||||
|
||||
| Component | Path | Uses | Pages / Comps | Story |
|
||||
|---|---|---|---|---|
|
||||
| 📗 [StatusBadge](./StatusBadge.md) | `ui/src/components/StatusBadge.tsx` | 19 | 12 / 7 | ✓ |
|
||||
| 📗 [MarkdownEditor](./MarkdownEditor.md) | `ui/src/components/MarkdownEditor.tsx` | 16 | 5 / 9 | ✓ |
|
||||
| 📗 [MarkdownBody](./MarkdownBody.md) | `ui/src/components/MarkdownBody.tsx` | 11 | 5 / 6 | ✓ |
|
||||
| 📗 [EntityRow](./EntityRow.md) | `ui/src/components/EntityRow.tsx` | 6 | 6 / 0 | ✓ |
|
||||
| 📗 [InlineEditor](./InlineEditor.md) | `ui/src/components/InlineEditor.tsx` | 6 | 4 / 2 | ✓ |
|
||||
| 📗 [ApprovalPayload](./ApprovalPayload.md) | `ui/src/components/ApprovalPayload.tsx` | 4 | 2 / 2 | — |
|
||||
| 📗 [CompanyPatternIcon](./CompanyPatternIcon.md) | `ui/src/components/CompanyPatternIcon.tsx` | 4 | 3 / 1 | ✓ |
|
||||
| 📗 [IssueReferencePill](./IssueReferencePill.md) | `ui/src/components/IssueReferencePill.tsx` | 4 | 1 / 3 | — |
|
||||
| 📗 [ActivityCharts](./ActivityCharts.md) | `ui/src/components/ActivityCharts.tsx` | 3 | 2 / 1 | ✓ |
|
||||
| 📗 [CopyText](./CopyText.md) | `ui/src/components/CopyText.tsx` | 3 | 2 / 1 | ✓ |
|
||||
| 📗 [IssueRow](./IssueRow.md) | `ui/src/components/IssueRow.tsx` | 3 | 1 / 2 | ✓ |
|
||||
| 📗 [PackageFileTree](./PackageFileTree.md) | `ui/src/components/PackageFileTree.tsx` | 3 | 3 / 0 | ✓ |
|
||||
| 📗 [SidebarNavItem](./SidebarNavItem.md) | `ui/src/components/SidebarNavItem.tsx` | 3 | 0 / 3 | — |
|
||||
| 📗 [RunTranscriptView](./RunTranscriptView.md) | `ui/src/components/transcript/RunTranscriptView.tsx` | 3 | 2 / 1 | — |
|
||||
| 📘 ActivityRow | `ui/src/components/ActivityRow.tsx` | 2 | 2 / 0 | ✓ |
|
||||
| 📘 AsciiArtAnimation | `ui/src/components/AsciiArtAnimation.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 BudgetSidebarMarker | `ui/src/components/BudgetSidebarMarker.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 CloudAccessGate | `ui/src/components/CloudAccessGate.tsx` | 2 | 0 / 0 | — |
|
||||
| 📘 CompanySettingsSidebar | `ui/src/components/CompanySettingsSidebar.tsx` | 2 | 0 / 2 | — |
|
||||
| 📘 EnvVarEditor | `ui/src/components/EnvVarEditor.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 ExecutionWorkspaceCloseDialog | `ui/src/components/ExecutionWorkspaceCloseDialog.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 GoalTree | `ui/src/components/GoalTree.tsx` | 2 | 2 / 0 | ✓ |
|
||||
| 📘 IssueGroupHeader | `ui/src/components/IssueGroupHeader.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 IssueReferenceActivitySummary | `ui/src/components/IssueReferenceActivitySummary.tsx` | 2 | 1 / 1 | — |
|
||||
| 📘 IssueRelatedWorkPanel | `ui/src/components/IssueRelatedWorkPanel.tsx` | 2 | 1 / 1 | — |
|
||||
| 📘 IssueRunLedger | `ui/src/components/IssueRunLedger.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 Layout | `ui/src/components/Layout.tsx` | 2 | 0 / 1 | — |
|
||||
| 📘 MetricCard | `ui/src/components/MetricCard.tsx` | 2 | 2 / 0 | ✓ |
|
||||
| 📘 OpenCodeLogoIcon | `ui/src/components/OpenCodeLogoIcon.tsx` | 2 | 0 / 1 | — |
|
||||
| 📘 ProjectWorkspacesContent | `ui/src/components/ProjectWorkspacesContent.tsx` | 2 | 2 / 0 | ✓ |
|
||||
| 📘 QuotaBar | `ui/src/components/QuotaBar.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 RunChatSurface | `ui/src/components/RunChatSurface.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 ScrollToBottom | `ui/src/components/ScrollToBottom.tsx` | 2 | 2 / 0 | — |
|
||||
| 📘 SwipeToArchive | `ui/src/components/SwipeToArchive.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 ActiveAgentsPanel | `ui/src/components/ActiveAgentsPanel.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 ClaudeSubscriptionPanel | `ui/src/components/ClaudeSubscriptionPanel.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 CodexSubscriptionPanel | `ui/src/components/CodexSubscriptionPanel.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 DevRestartBanner | `ui/src/components/DevRestartBanner.tsx` | 1 | 0 / 1 | — |
|
||||
| 📘 HermesIcon | `ui/src/components/HermesIcon.tsx` | 1 | 0 / 0 | — |
|
||||
| 📘 ImageGalleryModal | `ui/src/components/ImageGalleryModal.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 InstanceSidebar | `ui/src/components/InstanceSidebar.tsx` | 1 | 0 / 1 | — |
|
||||
| 📘 KanbanBoard | `ui/src/components/KanbanBoard.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 LiveRunWidget | `ui/src/components/LiveRunWidget.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 MobileBottomNav | `ui/src/components/MobileBottomNav.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 SidebarSection | `ui/src/components/SidebarSection.tsx` | 1 | 0 / 1 | — |
|
||||
| 📘 ToastViewport | `ui/src/components/ToastViewport.tsx` | 1 | 0 / 1 | — |
|
||||
| 📘 WorktreeBanner | `ui/src/components/WorktreeBanner.tsx` | 1 | 0 / 1 | ✓ |
|
||||
|
||||
---
|
||||
|
||||
## Non-component files (2)
|
||||
|
||||
These live in `ui/src/components/` by convention but don't export React components.
|
||||
|
||||
| File | Path | Role |
|
||||
|---|---|---|
|
||||
| `agent-config-defaults` | `ui/src/components/agent-config-defaults.ts` | module with shared constants/defaults |
|
||||
| `useLiveRunTranscripts` | `ui/src/components/transcript/useLiveRunTranscripts.ts` | React hook |
|
||||
|
||||
---
|
||||
|
||||
## Plugin SDK contracts (11)
|
||||
|
||||
Ambient component declarations from [`packages/plugins/sdk/src/ui/components.ts`](../../../packages/plugins/sdk/src/ui/components.ts). These are types-only; the host provides implementations at runtime via `renderSdkUiComponent(name, props)`.
|
||||
|
||||
> **Hybrid status is intentional (2026-04-21 decision).** Two components are implemented by the host. The other nine are **contract-only** — the types exist so plugin authors can code against them, but rendering today will fail at runtime. The 9 contract-only components carry a `@status contract-only` JSDoc tag in the SDK source, which appears in IDE tooltips at call sites. Prioritization of which to implement first is a separate plugin-SDK roadmap conversation, not a DS decision. See [components-review.md §Plugin SDK hybrid status](./components-review.md#plugin-sdk-hybrid-status-prioritization-deferred).
|
||||
|
||||
| SDK Component | Implementation | Status |
|
||||
|---|---|---|
|
||||
| `MetricCard` | [`ui/src/components/MetricCard.tsx`](../../../ui/src/components/MetricCard.tsx) | 📗 **implemented** |
|
||||
| `StatusBadge` | [`ui/src/components/StatusBadge.tsx`](../../../ui/src/components/StatusBadge.tsx) | 📗 **implemented** |
|
||||
| `DataTable` | — | 🔌 **contract-only** |
|
||||
| `TimeseriesChart` | — | 🔌 **contract-only** (distinct from `ActivityCharts.tsx`, which has a different API) |
|
||||
| `MarkdownBlock` | — | 🔌 **contract-only** (`MarkdownBody.tsx` is the host's markdown renderer, name differs) |
|
||||
| `KeyValueList` | — | 🔌 **contract-only** |
|
||||
| `ActionBar` | — | 🔌 **contract-only** (`AgentActionButtons.tsx` is role-specific, not a match) |
|
||||
| `LogView` | — | 🔌 **contract-only** |
|
||||
| `JsonTree` | — | 🔌 **contract-only** |
|
||||
| `Spinner` | — | 🔌 **contract-only** |
|
||||
| `ErrorBoundary` | — | 🔌 **contract-only** |
|
||||
|
||||
All 9 contract-only entries carry `@status contract-only` in their JSDoc block (see [`packages/plugins/sdk/src/ui/components.ts`](../../../packages/plugins/sdk/src/ui/components.ts) lines 253–316).
|
||||
69
doc/design-system/patterns/detail-page.md
Normal file
69
doc/design-system/patterns/detail-page.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Detail Page
|
||||
|
||||
Full-page layout for viewing and editing a single entity (agent, issue, project, goal, routine, approval, or execution/project workspace).
|
||||
|
||||
**Instances: 8.** `AgentDetail`, `IssueDetail`, `ProjectDetail`, `GoalDetail`, `RoutineDetail`, `ApprovalDetail`, `ExecutionWorkspaceDetail`, `ProjectWorkspaceDetail`.
|
||||
|
||||
## Composition (shared baseline)
|
||||
|
||||
Measured across the 8 instances by import intersection:
|
||||
|
||||
- **`button`** — 8/8 (every detail page has actions in its header)
|
||||
- **`tabs`** — 6/8 (detail pages split sub-views by tab)
|
||||
- **`PageSkeleton`** — 5/8 (loading state while the entity is being fetched)
|
||||
- **`StatusBadge`** — 4/8 (status is surfaced in the header area)
|
||||
- **`separator`** — 4/8
|
||||
- Breadcrumb context (`useBreadcrumbs`) — all 8 set a breadcrumb trail for the entity.
|
||||
|
||||
[INFER] Structural template, from reading AgentDetail and IssueDetail:
|
||||
|
||||
```
|
||||
<PageSkeleton or <ContentLoaded>
|
||||
<Breadcrumb / back-nav>
|
||||
<Header>
|
||||
<Title> — entity name + identifier
|
||||
<StatusBadge> — where applicable (issue, run, approval, agent)
|
||||
<Actions> — edit, archive, more-menu
|
||||
</Header>
|
||||
<Tabs> — 2–5 tabs (overview, config, activity, …)
|
||||
<TabContent>
|
||||
… entity-specific body (properties, related work, charts, transcripts)
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
</>
|
||||
```
|
||||
|
||||
## Canonical instance
|
||||
|
||||
`ui/src/pages/IssueDetail.tsx` is the most mature and most-cross-referenced implementation. `ui/src/pages/AgentDetail.tsx` is second and shows the tab-bar with many sub-surfaces.
|
||||
|
||||
## Variance across instances
|
||||
|
||||
Observed differences that may be intentional (different entity domain) or may be drift:
|
||||
|
||||
| Instance | Breadcrumbs | Tabs | Status element | Loading state | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| `IssueDetail` | yes | yes | `StatusIcon` + `StatusBadge` | custom | largest file; widely-referenced |
|
||||
| `AgentDetail` | yes | yes | `StatusBadge` + `agentStatusDot` | `PageSkeleton` | composes `AgentConfigForm`, `ActivityCharts`, `PackageFileTree`, `RunTranscriptView` |
|
||||
| `ProjectDetail` | yes | yes | `StatusBadge` | `PageSkeleton` | |
|
||||
| `GoalDetail` | yes | (unconfirmed) | `StatusBadge` | `PageSkeleton` | |
|
||||
| `RoutineDetail` | yes | yes | (unconfirmed) | `PageSkeleton` | |
|
||||
| `ApprovalDetail` | yes | (none) | tone-coded via `ApprovalCard` | `PageSkeleton` | diverges — simpler shape |
|
||||
| `ExecutionWorkspaceDetail` | yes | — | — | — | newer; diverges |
|
||||
| `ProjectWorkspaceDetail` | yes | — | — | — | newer; diverges |
|
||||
|
||||
- **3 of 8 don't use `PageSkeleton`** — worth confirming each has an equivalent loading state.
|
||||
- **`ApprovalDetail` skips tabs** — likely correct for its single-surface nature.
|
||||
- **Status element is inconsistent** — `StatusBadge` alone, `StatusIcon + StatusBadge`, `StatusBadge + agentStatusDot` (separate dot helper), `ApprovalCard` tone encoding. Tied to [status-display.md](./status-display.md) and [../tokens/tokens-review.md §4](../tokens/tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds).
|
||||
|
||||
## Related components and patterns
|
||||
|
||||
- Loading state: [`PageSkeleton`](../components/index.md) (documented as a component — used in 22+ places)
|
||||
- Empty state: [`EmptyState`](../components/index.md) (mostly used on list pages)
|
||||
- Status badge: [`StatusBadge`](../components/StatusBadge.md), [`StatusIcon`](../components/index.md) — see [status-display.md](./status-display.md)
|
||||
- Tab bar: shadcn `tabs`, and a custom [`PageTabBar`](../components/PageTabBar.md) used on some pages
|
||||
|
||||
## Open questions / risks
|
||||
|
||||
- Whether to codify a `<DetailPageHeader>` composite (title + status + actions block) to reduce per-page drift. Four detail pages already diverge on which status element they use.
|
||||
- The new `*WorkspaceDetail` pages do not yet share much structure with the older four. Check before Stage-4 pattern extraction runs again in a future quarter.
|
||||
51
doc/design-system/patterns/entity-creation-dialog.md
Normal file
51
doc/design-system/patterns/entity-creation-dialog.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Entity-Creation Dialog
|
||||
|
||||
Dialog surface for creating a new entity (agent, goal, issue, project).
|
||||
|
||||
**Instances: 4.** `NewAgentDialog`, `NewGoalDialog`, `NewIssueDialog`, `NewProjectDialog`.
|
||||
|
||||
> **Extraction-only pass.** This pattern document records the family as it exists today. It does not prescribe a merge into a single generic `NewEntityDialog`. See [components-review.md §Likely duplicates #1](../components/components-review.md#1-entity-creation-dialog-family-newagentdialog--newgoaldialog--newissuedialog--newprojectdialog) for the open-question treatment.
|
||||
|
||||
## Instances
|
||||
|
||||
| File | Lines | Uses | Opened via |
|
||||
|---|---|---|---|
|
||||
| `ui/src/components/NewAgentDialog.tsx` | 210 | 1 | `useDialog().newAgentOpen` |
|
||||
| `ui/src/components/NewGoalDialog.tsx` | (unread) | 1 | `useDialog()` |
|
||||
| `ui/src/components/NewIssueDialog.tsx` | 1699 | 2 | `useDialog()` |
|
||||
| `ui/src/components/NewProjectDialog.tsx` | (unread) | 1 | `useDialog()` |
|
||||
|
||||
## Composition (shared)
|
||||
|
||||
All four:
|
||||
|
||||
- Import `Dialog` + `DialogContent` from `@/components/ui/dialog` (primitive).
|
||||
- Consume a central `useDialog()` context from `ui/src/context/DialogContext` that exposes open/close flags per entity type.
|
||||
- Call an entity-specific API on submit (`agentsApi.create`, `issuesApi.create`, …) via `useMutation`.
|
||||
- Dismiss via `closeNewX()` from the same context.
|
||||
|
||||
## Shape divergence
|
||||
|
||||
The instances are **not structurally equivalent.** Line counts alone:
|
||||
|
||||
- `NewAgentDialog` = 210 lines (adapter picker → create stub)
|
||||
- `NewIssueDialog` = 1699 lines (rich form: assignees, projects, policies, mentions, dragdrop, advanced panel)
|
||||
|
||||
Other divergence indicators from imports:
|
||||
|
||||
- `NewIssueDialog` imports `agent-config-primitives` (`DraftInput`, `ChoosePathButton`, etc.), `ToggleSwitch`, `Popover`, large piece of `@dnd-kit`, markdown editors — i.e. a full inline form.
|
||||
- `NewAgentDialog` imports `Dialog`, `Button`, adapter-registry helpers — a chooser, not a full form.
|
||||
- `NewGoalDialog` and `NewProjectDialog` not examined in detail here; their size is likely between the two extremes.
|
||||
|
||||
## Open questions / risks
|
||||
|
||||
- Is `NewIssueDialog` intended to be "the" form and the others are just chooser-stubs that redirect to a detail page? That shape would be load-bearing. Currently unclear from static reading.
|
||||
- Without a generic base, adding a fifth entity (e.g. `NewRoutineDialog`) means another copy of the dialog-open-context wiring. The `useDialog()` context already carries the per-entity open/close flags — it would be the natural integration point if consolidation is pursued.
|
||||
|
||||
## Pattern use in Stage 4 analysis
|
||||
|
||||
If Stage 4 were to name a composition "new-entity-dialog" for future reference, the canonical definition would be:
|
||||
|
||||
> A `<Dialog>` opened from the `useDialog()` context, closed via a per-entity handler, containing an entity-specific body that submits through the matching API and invalidates the matching `queryKeys` on success.
|
||||
|
||||
Not yet codified as code. Documented here only.
|
||||
62
doc/design-system/patterns/entity-properties-panel.md
Normal file
62
doc/design-system/patterns/entity-properties-panel.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Entity Properties Panel
|
||||
|
||||
Side-panel content that shows an entity's metadata, lets the user edit inline, and drives per-field save state.
|
||||
|
||||
**Instances: 4 entity-specific panels + 1 generic panel.**
|
||||
`AgentProperties`, `GoalProperties`, `IssueProperties`, `ProjectProperties` + `PropertiesPanel`.
|
||||
|
||||
> **Extraction-only pass.** This pattern does not prescribe a merge. The open question about whether `PropertiesPanel` composes or duplicates the four entity-specific panels is surfaced — not resolved. See [components-review.md §Likely duplicates #2](../components/components-review.md#2-properties-panel-family-agentproperties--goalproperties--issueproperties--projectproperties--generic-propertiespanel).
|
||||
|
||||
## Instances
|
||||
|
||||
| Component | Lines | Uses | Role |
|
||||
|---|---|---|---|
|
||||
| `PropertiesPanel.tsx` | 29 | 1 | **Generic chrome.** A slide-in `<aside>` that reads `panelContent` from `usePanel()` context and renders whatever the caller has set. |
|
||||
| `AgentProperties.tsx` | (unread) | **0** | Entity-specific body (currently unused in production — Storybook-only). |
|
||||
| `GoalProperties.tsx` | (unread) | 1 | Entity-specific body. |
|
||||
| `IssueProperties.tsx` | 1370 | 2 | Entity-specific body; imports `StatusIcon`, `PriorityIcon`, `Identity`, `IssueReferencePill`, plus form primitives. |
|
||||
| `ProjectProperties.tsx` | 1140 | 1 | Entity-specific body; imports `StatusBadge`, status-colors consumers, `InlineEditor`, `EnvVarEditor`. |
|
||||
|
||||
## Relationship: chrome vs content
|
||||
|
||||
Reading `PropertiesPanel.tsx` (29 lines):
|
||||
|
||||
```tsx
|
||||
export function PropertiesPanel() {
|
||||
const { panelContent, panelVisible, setPanelVisible } = usePanel();
|
||||
if (!panelContent) return null;
|
||||
return (
|
||||
<aside className="… bg-card …">
|
||||
<div className="… flex flex-col …">
|
||||
<Header><Button icon="X" onClick={close} /></Header>
|
||||
<ScrollArea><div className="p-4">{panelContent}</div></ScrollArea>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
So `PropertiesPanel` is a **slot**, not a content template. The four `*Properties` components are **contents** that get passed into that slot via `usePanel().setPanelContent(...)`.
|
||||
|
||||
## Open question (not resolved here)
|
||||
|
||||
Does `PropertiesPanel` already compose the four entity-specific panels, or do the four duplicate work that `PropertiesPanel` could own?
|
||||
|
||||
Evidence either way from a static read:
|
||||
|
||||
- **For "composes"**: the four entity-specific panels don't render a dialog/drawer wrapper themselves — each emits just the body content. They rely on some parent (either `PropertiesPanel` or a page-owned slot) to provide the outer container.
|
||||
- **For "duplicates"**: the header layout (`Properties` title + close button) is in `PropertiesPanel`, but each of the four is 1100+ lines of its own scaffolding (section headers, separators, form fields, save-state handling) that *could* be factored into a `<PropertiesPanelBody>` helper.
|
||||
|
||||
**Not a call to make in this extraction.** The founder should open `IssueProperties.tsx` and `ProjectProperties.tsx` side-by-side and judge.
|
||||
|
||||
## Also noted
|
||||
|
||||
- `AgentProperties.tsx` has **0 production uses** (Storybook-only). Either abandoned or waiting for a page. See [components-review.md §Unused components](../components/components-review.md#unused--low-signal-components).
|
||||
- Each entity-specific panel consumes `status-colors.ts` for status-color rendering (directly or via `StatusBadge`/`StatusIcon`), inheriting the token drift from [tokens-review.md §4](../tokens/tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds).
|
||||
- File sizes (1100–1370 lines each) suggest each panel handles its own save pipeline, field-level error states, mutations, and recent-selection tracking (indirectly observed via imports from `recent-assignees`, `recent-projects`, etc.). Whether that logic is shareable is the real design question underneath the styling question.
|
||||
|
||||
## Related components and patterns
|
||||
|
||||
- Chrome slot: `PropertiesPanel` and the [`usePanel()` context](../../../ui/src/context/PanelContext.tsx)
|
||||
- Status coloring: [status-display.md](./status-display.md)
|
||||
- Inline field editing primitives in [`agent-config-primitives.tsx`](../components/agent-config-primitives.md) (`DraftInput`, `InlineField`, `ToggleField`, `ToggleWithNumber`)
|
||||
80
doc/design-system/patterns/entity-row.md
Normal file
80
doc/design-system/patterns/entity-row.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Entity Row
|
||||
|
||||
Row element for listing items in a scrollable collection (inbox, activity feed, list pages).
|
||||
|
||||
**Instances: 3.** `ActivityRow`, `EntityRow`, `IssueRow`.
|
||||
|
||||
> **Extraction-only pass.** Documents the family; does not prescribe the merge suggested in components-review. See [components-review.md §Likely duplicates #6](../components/components-review.md#6-row-family-activityrow--entityrow--issuerow).
|
||||
|
||||
## Instances
|
||||
|
||||
| Component | Lines | Uses | Role |
|
||||
|---|---|---|---|
|
||||
| `EntityRow.tsx` | 69 | 6 | Generic slot-based row (`leading` / `identifier` / `title` / `subtitle` / `trailing`) |
|
||||
| `ActivityRow.tsx` | 92 | 2 | Activity-event-specific — renders an `ActivityEvent` with actor identity + action verb + entity link |
|
||||
| `IssueRow.tsx` | 168 | 3 | Issue-specific — renders an `Issue` with `StatusIcon`, mobile/desktop slot variants, unread state, archive action |
|
||||
|
||||
## Composition
|
||||
|
||||
**`EntityRow`** — truly generic:
|
||||
|
||||
```tsx
|
||||
interface EntityRowProps {
|
||||
leading?: ReactNode;
|
||||
identifier?: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
trailing?: ReactNode;
|
||||
selected?: boolean;
|
||||
to?: string;
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
```
|
||||
|
||||
Renders as `<Link>` or `<div>` depending on click-ability. No status, no unread state, no mobile/desktop split — just slots.
|
||||
|
||||
**`ActivityRow`** — imports `Identity`, `IssueReferenceActivitySummary`. Specific to activity events.
|
||||
|
||||
**`IssueRow`** — imports `StatusIcon`. Props interface has 15 fields, most of them optional `ReactNode` slots:
|
||||
|
||||
```ts
|
||||
interface IssueRowProps {
|
||||
issue: Issue;
|
||||
issueLinkState?: unknown;
|
||||
selected?: boolean;
|
||||
mobileLeading?: ReactNode; // slot
|
||||
desktopMetaLeading?: ReactNode; // slot
|
||||
desktopLeadingSpacer?: boolean;
|
||||
mobileMeta?: ReactNode; // slot
|
||||
desktopTrailing?: ReactNode; // slot
|
||||
trailingMeta?: ReactNode; // slot
|
||||
titleSuffix?: ReactNode; // slot
|
||||
unreadState?: UnreadState | null;
|
||||
onMarkRead?: () => void;
|
||||
onArchive?: () => void;
|
||||
archiveDisabled?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
```
|
||||
|
||||
Six slot props plus an untyped `issueLinkState` — this shape is nearly "`<EntityRow>` + issue-specific defaults."
|
||||
|
||||
## Observations
|
||||
|
||||
- `EntityRow` is used in 6 composites (not examined here — see composition graph), but **not used on any main list page**. Main list pages roll their own row rendering.
|
||||
- `IssueRow` is only used in 2 places: the `Inbox` page and `SwipeToArchive`. Not used on the `Issues` page (which uses `IssueColumns` + custom row rendering per column).
|
||||
- The gap is: `EntityRow` covers the "slot-based row" role generically, but the list pages don't adopt it.
|
||||
|
||||
## Variance
|
||||
|
||||
- **Mobile/desktop split lives only in `IssueRow`.** Whether other pages need it or have their own responsive handling is unknown from static analysis.
|
||||
- **Unread state lives only in `IssueRow`.** Inbox-specific; would not generalize.
|
||||
- **Activity-specific text (verb, link target) lives only in `ActivityRow`.** Legitimate domain specialization.
|
||||
|
||||
## Open questions
|
||||
|
||||
- Could `IssueRow` be expressed as `<EntityRow kind="issue" ... />`? Its slot shape already matches `EntityRow`'s role; the issue-specific bits (StatusIcon, unread state, archive) are add-ons, not structural differences.
|
||||
- Why do the main list pages (`Issues`, `Agents`, `Projects`, …) avoid `EntityRow`? If there's a good reason it should be documented; if not, adoption would retire a lot of per-page row code.
|
||||
|
||||
Answers to these are not required for this extraction. The pattern is noted as documentation-relevant.
|
||||
76
doc/design-system/patterns/finance-card.md
Normal file
76
doc/design-system/patterns/finance-card.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Finance / Accounting Card
|
||||
|
||||
Card surface for summarizing a financial or accounting slice: per-biller spend, per-kind spend, timeline totals, accounting-model totals.
|
||||
|
||||
**Instances: 5.** `BillerSpendCard`, `FinanceBillerCard`, `FinanceKindCard`, `FinanceTimelineCard`, `AccountingModelCard`.
|
||||
|
||||
> **Extraction-only pass.** Documents the family as it exists. Two specific items are flagged for the founder below; neither is auto-resolved.
|
||||
|
||||
## Instances
|
||||
|
||||
| Component | Lines | Uses | Data type (inferred) |
|
||||
|---|---|---|---|
|
||||
| `BillerSpendCard` | 145 | 1 | `CostByBiller` (+ `CostByProviderModel` breakdown) |
|
||||
| `FinanceBillerCard` | 44 | 1 | `FinanceByBiller` |
|
||||
| `FinanceKindCard` | (unread) | 1 | `FinanceByKind` (inferred) |
|
||||
| `FinanceTimelineCard` | (unread) | 1 | timeline roll-up |
|
||||
| `AccountingModelCard` | (unread) | **0** | (unknown — unused) |
|
||||
|
||||
## Composition (shared)
|
||||
|
||||
All five are composites that import the shadcn `Card` family (`Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`) and render a titled card with a body.
|
||||
|
||||
`BillerSpendCard` additionally composes `QuotaBar`. That's the richer card in the family — quota visualization + provider breakdown + billing-type breakdown.
|
||||
|
||||
`FinanceBillerCard` is a plain summary card with a three-cell metric grid (`debits` / `credits` / `estimated`).
|
||||
|
||||
## Flag 1 — Likely true duplicate: `BillerSpendCard` ↔ `FinanceBillerCard`
|
||||
|
||||
Per the founder's directive in Stage 3, this pair is flagged for diff review.
|
||||
|
||||
- Both have "Biller" in the name.
|
||||
- Both summarize per-biller financials.
|
||||
- They consume **different** data models (`CostByBiller` vs `FinanceByBiller`), which suggests either (a) two different reporting concepts the names fail to distinguish, or (b) one of them is a stale parallel implementation of the other.
|
||||
- Line counts differ significantly (145 vs 44), but that could mean `BillerSpendCard` is the richer one *and* `FinanceBillerCard` is the slimmed-down version of the same concept.
|
||||
|
||||
**Action suggested (not taken here):** open both side by side and judge whether they represent two legitimately different reports, or whether one superseded the other and the older survived.
|
||||
|
||||
## Flag 2 — `AccountingModelCard` is unused
|
||||
|
||||
Zero imports across the codebase. Storybook-only coverage. See [components-review.md §Unused](../components/components-review.md#unused--low-signal-components). Delete or adopt.
|
||||
|
||||
## Composition template (common shape)
|
||||
|
||||
[INFER] From `FinanceBillerCard` (the cleanest example):
|
||||
|
||||
```
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div flex between>
|
||||
<div>
|
||||
<CardTitle>{providerDisplayName(row.biller)}</CardTitle>
|
||||
<CardDescription>{eventCount}, {kindCount} kinds</CardDescription>
|
||||
</div>
|
||||
<div text-right>
|
||||
<div text-lg tabular-nums>{formatCents(row.netCents)}</div>
|
||||
<div uppercase tracking-wide muted>net</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<grid 3-column>
|
||||
<Cell label="debits" value={formatCents(...)} />
|
||||
<Cell label="credits" value={formatCents(...)} />
|
||||
<Cell label="estimated" value={formatCents(...)} />
|
||||
</grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
```
|
||||
|
||||
The recurring sub-element — a small metric cell with uppercase-tracked muted label + tabular-num value — is a micro-pattern worth noting. It appears here and (less formally) on list pages. Candidate for a `<MetricCell>` helper.
|
||||
|
||||
## Variance
|
||||
|
||||
- `BillerSpendCard` composes `QuotaBar`; the others don't.
|
||||
- Different data models (`CostByBiller` vs `FinanceByBiller` vs `FinanceByKind`) — confirm whether the shared shape is intentional convergence or a sign that they should share a common `FinanceCardRow` interface.
|
||||
- Per-card formatting helpers (`formatCents`, `formatTokens`, `providerDisplayName`) live in `@/lib/utils` — shared. Good.
|
||||
43
doc/design-system/patterns/index.md
Normal file
43
doc/design-system/patterns/index.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Patterns — Index
|
||||
|
||||
- **Generated:** 2026-04-21
|
||||
- **Repo SHA:** a26e1288b627e82c554445732c7d844648e6b5e1
|
||||
- **Scope:** `ui/` (@paperclipai/ui) — pages + components
|
||||
- **Review:** [patterns-review.md](./patterns-review.md)
|
||||
|
||||
## What a pattern is
|
||||
|
||||
A *pattern* is a composition of components that recurs across pages or across composites — something that has a shape, not just a component name. Pattern documents describe the shape and list the current instances. They do not prescribe refactors.
|
||||
|
||||
Patterns were identified by:
|
||||
1. Reading `_pages.json` and `_composition-graph.json` (Stage 2 scratch).
|
||||
2. Looking for import-set intersections across pages or composition-graph neighborhoods.
|
||||
3. Cross-referencing the duplicate families surfaced in [components-review.md §Likely duplicates](../components/components-review.md#likely-duplicates).
|
||||
4. Checking the Paperclip-domain checklist from the extraction skill (heartbeat, run-transcript row, agent card, approval gate, cost display, metadata grid).
|
||||
|
||||
## Pattern inventory
|
||||
|
||||
Sorted by instance count. Patterns with ≥3 instances get their own detail doc; pairs below the threshold are included per directive but called out.
|
||||
|
||||
| Pattern | Instances | Doc |
|
||||
|---|---|---|
|
||||
| [List page](./list-page.md) | 12 | ✓ |
|
||||
| [Detail page](./detail-page.md) | 8 | ✓ |
|
||||
| [Sidebar chrome](./sidebar-chrome.md) | 6 outer + 2 menus | ✓ |
|
||||
| [Finance / accounting card](./finance-card.md) | 5 | ✓ |
|
||||
| [Entity properties panel](./entity-properties-panel.md) | 4 entity-specific + 1 generic chrome | ✓ |
|
||||
| [Entity-creation dialog](./entity-creation-dialog.md) | 4 | ✓ |
|
||||
| [Status display](./status-display.md) | 3 components + 1 catalog | ✓ |
|
||||
| [Entity row](./entity-row.md) | 3 | ✓ |
|
||||
| [Subscription panel](./subscription-panel.md) | 2 (below threshold — documented) | ✓ |
|
||||
| [Quota display](./quota-display.md) | 2 (below threshold — documented) | ✓ |
|
||||
|
||||
See [patterns-review.md](./patterns-review.md) for:
|
||||
- Pattern opportunities that don't yet meet the threshold but are domain-relevant (heartbeat, run-transcript row, agent card, approval gate, cost display, metric cell, severity indicator).
|
||||
- Variance analysis across patterns.
|
||||
- Which patterns are safe to codify and which are blocked on upstream token or naming decisions.
|
||||
|
||||
## Scope notes
|
||||
|
||||
- **Out of this pass:** deep pattern extraction from the UX Lab pages (`IssueChatUxLab`, `RunTranscriptUxLab`, `InviteUxLab`). Those are acknowledged prototypes — pattern work there should follow explicit founder direction, not auto-extraction.
|
||||
- **Out of this pass:** the plugin SDK contract surface. Patterns emerge from the host `ui/`; if the SDK contract is fulfilled (see [components-review.md §Plugin SDK contract gap](../components/components-review.md#plugin-sdk-contract-gap)), those host implementations become additional pattern instances and this doc will need a re-run.
|
||||
60
doc/design-system/patterns/list-page.md
Normal file
60
doc/design-system/patterns/list-page.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# List Page
|
||||
|
||||
Full-page layout for browsing a collection of entities (agents, issues, projects, goals, routines, approvals, workspaces).
|
||||
|
||||
**Instances: 12.** `Activity`, `Agents`, `Approvals`, `Companies`, `Goals`, `Inbox`, `Issues`, `MyIssues`, `Projects`, `Routines`, `Workspaces`, `JoinRequestQueue`.
|
||||
|
||||
## Composition (shared baseline)
|
||||
|
||||
Measured across 12 instances by import intersection:
|
||||
|
||||
- **`PageSkeleton`** — 9/12 (the loading shell)
|
||||
- **`EmptyState`** — 8/12 (shown when the collection is empty)
|
||||
- **`button`** — 7/12 (action buttons: new-X, filters, view-toggle)
|
||||
- **`PageTabBar`** — 4/12 (views split into named lists)
|
||||
- **`tabs`** — 4/12
|
||||
|
||||
[INFER] Structural template, from reading Issues / Agents / Projects:
|
||||
|
||||
```
|
||||
<Page>
|
||||
<Header>
|
||||
<Title> — "Issues", "Agents", …
|
||||
<FilterOrTabBar> — PageTabBar or tabs for sub-collections
|
||||
<ActionButton> — "New Issue", "Invite", etc.
|
||||
</Header>
|
||||
<Body>
|
||||
{loading ? <PageSkeleton />
|
||||
empty ? <EmptyState title message action />
|
||||
: <List / Grid / Kanban>
|
||||
{items.map(item =>
|
||||
<IssueRow or EntityRow or domain-specific row />)}
|
||||
</>
|
||||
}
|
||||
</Body>
|
||||
</Page>
|
||||
```
|
||||
|
||||
## Canonical instance
|
||||
|
||||
No single clean instance — `Issues.tsx` and `Agents.tsx` are both representative but each mixes in substantial custom logic. `Agents.tsx` shows the pattern with the least domain-specific clutter.
|
||||
|
||||
## Variance across instances
|
||||
|
||||
- **Row rendering diverges.** `Issues` uses `IssueRow`, `Agents` builds a grid of cards, `Approvals` uses a list of cards, `Goals` uses `GoalTree`. Different collections legitimately need different affordances, but none of them use the generic `EntityRow` — which is 6 uses spread across feature subsurfaces, not pages.
|
||||
- **Empty-state content is ad-hoc.** No shared "empty collection" copy or illustration; each page passes its own strings to `EmptyState`.
|
||||
- **Sorting / filtering surface is not shared.** Some pages have a `FilterBar`, some have in-header popovers, some have `IssueFiltersPopover` specifically for Issues. See also [components-review.md §Naming inconsistencies](../components/components-review.md#naming-inconsistencies) on `*Bar` vs popover filters.
|
||||
- **`Activity` is borderline.** It's a chronological feed rather than a collection — composes `ActivityRow` only.
|
||||
- **Loading state absent on 3 pages** — confirm each has its own mechanism.
|
||||
|
||||
## Related components and patterns
|
||||
|
||||
- [`EmptyState`](../components/index.md) (used in 19 places across the app)
|
||||
- [`PageSkeleton`](../components/index.md) (22 places)
|
||||
- [`PageTabBar`](../components/PageTabBar.md) (10 uses)
|
||||
- [entity-row pattern](./entity-row.md) — generic row, currently underused on list pages
|
||||
|
||||
## Open questions / risks
|
||||
|
||||
- `EntityRow` (6 uses, generic) is never used on the main list pages. The main list pages roll their own row. Worth asking whether that's by choice or by miss. See also [entity-row.md](./entity-row.md).
|
||||
- The mobile list experience is handled per-page; no shared mobile list pattern found. See [`SwipeToArchive.tsx`](../components/index.md) which only `Inbox` uses.
|
||||
138
doc/design-system/patterns/patterns-review.md
Normal file
138
doc/design-system/patterns/patterns-review.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# Patterns Review
|
||||
|
||||
Cross-cutting findings from Stage 4. Ordered by expected human value.
|
||||
|
||||
- **Generated:** 2026-04-21
|
||||
- **Inventory:** [index.md](./index.md)
|
||||
- **Upstream dependencies:** [../tokens/tokens-review.md](../tokens/tokens-review.md), [../components/components-review.md](../components/components-review.md)
|
||||
|
||||
---
|
||||
|
||||
## Variance across documented patterns (what's inconsistent between instances)
|
||||
|
||||
### Status-element variance in detail pages ([detail-page.md](./detail-page.md))
|
||||
|
||||
Four different ways detail pages render the entity's status:
|
||||
|
||||
| Detail page | Status treatment |
|
||||
|---|---|
|
||||
| `IssueDetail` | `StatusIcon` + `StatusBadge` |
|
||||
| `AgentDetail` | `StatusBadge` + `agentStatusDot` (a helper in `status-colors.ts`, not a component) |
|
||||
| `ProjectDetail`, `GoalDetail` | `StatusBadge` alone |
|
||||
| `ApprovalDetail` | Tone encoding inside `ApprovalCard`, no badge |
|
||||
|
||||
That's four variations across eight pages. Tied to the broader [status-display.md](./status-display.md) and [tokens-review.md §4](../tokens/tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds) issues.
|
||||
|
||||
### Loading-state variance across list and detail pages
|
||||
|
||||
`PageSkeleton` is used in 22 places, but 3 of 8 detail pages and 3 of 12 list pages skip it. Each of those pages has its own ad-hoc loading handling (or doesn't render until data arrives). Worth auditing for consistency.
|
||||
|
||||
### Row variance across list pages ([list-page.md](./list-page.md) + [entity-row.md](./entity-row.md))
|
||||
|
||||
`EntityRow` (generic, 6 uses) is never used on any main list page. Every list page rolls its own row: `IssueRow` for Inbox/Swipe, grid cards for Agents, Kanban columns for Issues, `GoalTree` for Goals, etc. Either the list pages should adopt `EntityRow` or `EntityRow` has a gap that keeps it from fitting (status/unread/mobile-responsive).
|
||||
|
||||
---
|
||||
|
||||
## Paperclip-domain patterns worth calling out (opportunities, not ratified patterns)
|
||||
|
||||
These are the checklist patterns from the extraction skill — run-transcript row, heartbeat indicator, agent card, cost display, approval gate, metric cell. Each is examined for whether it currently exists as a 3+ pattern, a thinner-than-threshold pattern, or a clear opportunity.
|
||||
|
||||
### 1. Run transcript row
|
||||
|
||||
**Status:** a single parent component (`RunTranscriptView.tsx`, 3 uses) owns the rendering. Not a cross-cutting pattern at the instance level.
|
||||
|
||||
- `RunTranscriptView` lives in `ui/src/components/transcript/`.
|
||||
- Used by `AgentDetail`, `IssueDetail`, `RunTranscriptUxLab`.
|
||||
- Has its own test (`RunTranscriptView.test.tsx`) and a sibling hook (`useLiveRunTranscripts.ts`).
|
||||
- No Storybook story — see [components-review.md §Story coverage gaps](../components/components-review.md#story-coverage-gaps).
|
||||
|
||||
**Opportunity:** if transcript rows (agent action + timestamp + tool call summary) were exposed as a `<TranscriptRow>` primitive, downstream surfaces (embedded transcripts, search results, activity feed) could reuse it. Currently the rendering is internal to `RunTranscriptView`.
|
||||
|
||||
### 2. Heartbeat / liveness indicator
|
||||
|
||||
**Status:** one component (`LiveRunWidget`, 1 use) plus scattered inline usage in `AgentDetail` (imports `heartbeatsApi`). Not a pattern.
|
||||
|
||||
- `LiveRunWidget.tsx` is a "live" indicator with cyan border glow; 90 lines; only used on Dashboard.
|
||||
- `AgentDetail` renders its own live-status dot (`border-cyan-500/30 shadow-[0_0_12px_rgba(6,182,212,0.08)]` hardcoded).
|
||||
- `ActiveAgentsPanel` also renders live markers.
|
||||
|
||||
**Opportunity:** extract a `<Heartbeat live={bool} lastSeenAt={Date}>` primitive. Today the three surfaces each reinvent the visual treatment.
|
||||
|
||||
### 3. Agent card
|
||||
|
||||
**Status:** fragmented across `ActiveAgentsPanel` (1 use, composite), `Agents` page (rolls its own), and `SidebarAgents` (1 use). No shared `AgentCard`.
|
||||
|
||||
**Opportunity:** unify into a single card pattern if Stage 3's "Card vs Panel vs Widget" naming question gets resolved.
|
||||
|
||||
### 4. Approval gate UI
|
||||
|
||||
**Status:** `ApprovalCard` (3 uses), `ApprovalPayload` (4 uses), `OutputFeedbackButtons` (2 uses). Three components, all in the approval flow, but each a single surface — not repeated across three independent contexts.
|
||||
|
||||
- `ApprovalCard` and `ApprovalPayload` appear in `ApprovalDetail` page and in `Approvals` list. `OutputFeedbackButtons` appears in run detail surfaces.
|
||||
|
||||
**Status call:** not enough cross-cutting instance count to formalize as a pattern today. The trio is **already** the approval-gate pattern — just not repeated enough to name.
|
||||
|
||||
### 5. Cost / budget display
|
||||
|
||||
**Status:** six related components (`BudgetPolicyCard`, `BudgetIncidentCard`, `BudgetSidebarMarker`, `QuotaBar`, `ProviderQuotaCard`, `BillerSpendCard`) span two adjacent pattern docs ([finance-card.md](./finance-card.md), [quota-display.md](./quota-display.md)).
|
||||
|
||||
**Not merged here** — the finance / accounting slice documents the "reporting card" shape; the quota slice documents the "used vs budget" shape; the budget slice hand-rolls its own 3-level severity indicator (see §6). These three related but distinct domains would benefit from a shared glossary before pattern consolidation.
|
||||
|
||||
### 6. Severity indicator (3-level health display) — **pattern opportunity**
|
||||
|
||||
[Not documented as a dedicated pattern file in this pass; surfaced here as the most impactful opportunity.]
|
||||
|
||||
The app has **four distinct systems** for encoding "healthy / warning / critical":
|
||||
|
||||
| System | Location | Example |
|
||||
|---|---|---|
|
||||
| `status-colors.ts` | `issueStatusText.todo = "text-blue-600 dark:text-blue-400"` etc. | 11 hues, -600/-400 weights |
|
||||
| `ActivityCharts.tsx` hardcoded hex | `critical: "#ef4444"` | Raw hex, no dark-mode variant |
|
||||
| `BudgetPolicyCard` / `BudgetIncidentCard` | `"text-red-300 border-red-500/30 bg-red-500/10"` | 3-level with 300/500/10% alpha |
|
||||
| `BudgetSidebarMarker` | `"bg-emerald-500/90 text-white"` | 3-level with 500/90% alpha |
|
||||
| `QuotaBar` | `"bg-red-400"` etc. | 3-level solid 400 |
|
||||
|
||||
Each encodes the same *concept* (red/amber/green severity), each chooses different *Tailwind utility classes*. None of them reference the DS tokens — because there are no DS tokens for this. See [tokens-review.md §4 — status-colors.ts is a canonical semantic-color catalog that bypasses the DS](../tokens/tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds).
|
||||
|
||||
**Prerequisite:** a `--signal-*` token family (`--signal-success`, `--signal-warning`, `--signal-error`, each with `-bg`/`-text`/`-border`/`-subtle` variants). Once tokens exist, a `<SeverityIndicator level="ok|warn|critical">` primitive becomes writable and all four sites collapse onto it.
|
||||
|
||||
**Why this is the highest-leverage pattern opportunity:** it sits downstream of Stage 1's biggest finding (§4) and unblocks at least three patterns at once (status-display, quota-display, cost-display). Do not try to codify those three separately first.
|
||||
|
||||
### 7. Metric cell (small uppercase label + tabular-num value)
|
||||
|
||||
Recurring micro-pattern spotted in `FinanceBillerCard`:
|
||||
|
||||
```tsx
|
||||
<div className="border border-border p-3">
|
||||
<div className="text-[11px] uppercase tracking-[0.14em] text-muted-foreground">debits</div>
|
||||
<div className="mt-1 font-medium tabular-nums">{formatCents(...)}</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Appears inline in `FinanceBillerCard`, `BillerSpendCard`, `ProviderQuotaCard`, and (with small variations) in some detail-page headers and dashboard surfaces. Used ad hoc rather than as a reusable `<MetricCell>` primitive. Pattern exists informally; could be extracted to ~15 lines.
|
||||
|
||||
### 8. Metadata grid (label-value pairs, common in detail views)
|
||||
|
||||
Per the skill checklist. Not surfaced as a recurring shape in the current codebase's detail pages — each detail page renders its metadata inline. `KeyValueList` is contracted in the plugin SDK but not implemented. Weak pattern; opportunity.
|
||||
|
||||
---
|
||||
|
||||
## Candidates for future documentation (below threshold today)
|
||||
|
||||
Patterns glimpsed at 1–2 instances. If usage grows, they'd warrant dedicated docs:
|
||||
|
||||
- **Toolbar / filter bar** — `FilterBar` (1), `IssueFiltersPopover` (2), inline filter popovers on several list pages. No shared shape yet.
|
||||
- **Inline editor** — `InlineEditor` (6 uses) plus the `DraftInput` / `DraftTextarea` primitives from `agent-config-primitives`. The inline-edit interaction has a shape; not yet abstracted as a named pattern.
|
||||
- **Modal image / file viewer** — `ImageGalleryModal`, `DocumentDiffModal` (and `PathInstructionsModal`). Three modals with different content, possibly sharing chrome.
|
||||
- **Kanban column** — `KanbanBoard` (2 uses) + `IssueColumns` (4 uses) + `IssueGroupHeader` (1). Issue-specific; pattern lives inside the issue domain.
|
||||
|
||||
---
|
||||
|
||||
## What to resolve before Stage 4 would run cleanly on re-extraction
|
||||
|
||||
Pattern extraction is limited by upstream ambiguity. The following decisions, if made, would let the next pattern re-run produce materially tighter docs:
|
||||
|
||||
1. **Signal token family** ([tokens-review.md §4](../tokens/tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds)). Blocks [status-display.md](./status-display.md), part of [quota-display.md](./quota-display.md), and §6 above.
|
||||
2. **Radius scale decision** ([tokens-review.md §Radius scale](../tokens/tokens-review.md#radius-scale--under-founder-review)). Once `rounded-lg` / `rounded-xl` behave predictably, the UxLab pages can be brought into the main DS conversation instead of ignored.
|
||||
3. **Plugin SDK contract disposition** ([components-review.md §Plugin SDK contract gap](../components/components-review.md#plugin-sdk-contract-gap)). Dictates whether `KeyValueList` / `LogView` / `ActionBar` / `Spinner` / `DataTable` / `TimeseriesChart` become host patterns or stay aspirational.
|
||||
4. **Naming vocabulary** ([components-review.md §Naming inconsistencies](../components/components-review.md#naming-inconsistencies)). Not strictly required for patterns to exist, but the docs here have to use neutral language (_"dialog pattern — some implementations named *Modal, same primitive"_) because the vocabulary is unresolved. A canonical decision would tighten pattern prose.
|
||||
71
doc/design-system/patterns/quota-display.md
Normal file
71
doc/design-system/patterns/quota-display.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Quota Display
|
||||
|
||||
Visual representation of "used vs budget" for provider quotas, subscription quotas, or billing windows.
|
||||
|
||||
**Instances: 2 (below the 3+ threshold, documented per directive).**
|
||||
`QuotaBar` (2 uses), `ProviderQuotaCard` (1 use).
|
||||
|
||||
> **Extraction-only pass.** Documents the pair; does not prescribe a merge. See [components-review.md §Likely duplicates #9](../components/components-review.md#9-quota-display-providerquotacard--quotabar).
|
||||
|
||||
## Instances
|
||||
|
||||
| Component | Lines | Uses | Role |
|
||||
|---|---|---|---|
|
||||
| `QuotaBar.tsx` | 65 | 2 | Single horizontal bar — % used with optional deficit notch |
|
||||
| `ProviderQuotaCard.tsx` | 416 | 1 | Full per-provider card — composes `QuotaBar` + `ClaudeSubscriptionPanel`/`CodexSubscriptionPanel` |
|
||||
|
||||
## Composition
|
||||
|
||||
`QuotaBar`:
|
||||
|
||||
```ts
|
||||
interface QuotaBarProps {
|
||||
label: string;
|
||||
percentUsed: number; // 0–100
|
||||
leftLabel: string;
|
||||
rightLabel?: string;
|
||||
showDeficitNotch?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
```
|
||||
|
||||
- Single horizontal bar with a filled portion.
|
||||
- Fill color is computed from a threshold function:
|
||||
- `>90% → bg-red-400`
|
||||
- `>70% → bg-yellow-400`
|
||||
- `else → bg-green-400`
|
||||
|
||||
`ProviderQuotaCard`:
|
||||
|
||||
- Composes `QuotaBar` (multiple times for different windows: 5h / 24h / 7d rolling + period budget), plus `ClaudeSubscriptionPanel` / `CodexSubscriptionPanel` for vendor-native quota windows.
|
||||
- Three-level wrapper around `QuotaBar`.
|
||||
|
||||
## Token drift inherent to this pattern
|
||||
|
||||
`QuotaBar` hardcodes `bg-red-400`, `bg-yellow-400`, `bg-green-400` — raw Tailwind palette. This is a **fourth** place where the app encodes three-level severity:
|
||||
|
||||
1. `status-colors.ts` — red-600/500/400, amber-400, yellow-400, etc.
|
||||
2. `ActivityCharts.tsx` — direct hex values
|
||||
3. `BudgetPolicyCard` / `BudgetIncidentCard` / `BudgetSidebarMarker` — 3-level severity with 500/90% alpha
|
||||
4. `QuotaBar` — 3-level severity with solid 400s
|
||||
|
||||
All four encode "health / warn / hard-stop" but none share a token. See [tokens-review.md §1, §4](../tokens/tokens-review.md) for the token-drift picture and [patterns-review.md](./patterns-review.md) for the severity-indicator pattern opportunity.
|
||||
|
||||
## Different affordances, not a duplicate
|
||||
|
||||
The pair is **not** a duplicate in the literal sense — `QuotaBar` is a rendering primitive, `ProviderQuotaCard` is a composer. The naming suggests parallels (`-Bar` vs `-Card`) but their roles are distinct:
|
||||
|
||||
- Use `QuotaBar` for a single measured ratio.
|
||||
- Use `ProviderQuotaCard` for a full provider's worth of quotas.
|
||||
|
||||
What the name spread **does** flag: there's no shared concept of "quota display primitive" — `QuotaBar` is one, but the finance/accounting cards use their own bar-less metric cells (`<Cell label value />` triples), and `BudgetPolicyCard` uses hand-rolled borders. The family would benefit from either promoting `QuotaBar` into a shared DS primitive for all "used vs budget" visualizations, or acknowledging that each surface wants a different look.
|
||||
|
||||
## Scale caveat
|
||||
|
||||
Only 2 instances — the pattern is thinly attested. Listed here per founder directive; a real "quota display" DS pattern would need a third independent caller before it's real.
|
||||
|
||||
## Related
|
||||
|
||||
- [finance-card.md](./finance-card.md) — `BillerSpendCard` also composes `QuotaBar`.
|
||||
- [subscription-panel.md](./subscription-panel.md) — `ProviderQuotaCard` composes both subscription panels.
|
||||
- [patterns-review.md §Severity indicator](./patterns-review.md) — the wider family of 3-level severity displays.
|
||||
63
doc/design-system/patterns/sidebar-chrome.md
Normal file
63
doc/design-system/patterns/sidebar-chrome.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Sidebar Chrome
|
||||
|
||||
Left-rail and settings-sidebar UI that wraps the main app surface.
|
||||
|
||||
**Instances: 6 "outer" + 2 sidebar menus = 8 components in the family.**
|
||||
`Sidebar`, `InstanceSidebar`, `CompanySettingsSidebar`, `CompanyRail`, `access/CompanySettingsNav`, `MobileBottomNav` + `SidebarAccountMenu`, `SidebarCompanyMenu`.
|
||||
|
||||
> **Extraction-only pass.** Does not resolve the naming inconsistencies (Sidebar vs Rail vs Nav). See [components-review.md §Naming inconsistencies — Sidebar / Rail / Nav](../components/components-review.md#sidebar--rail--nav) and [§Likely duplicates #4 and #7](../components/components-review.md#4-sidebar-menu-pair-sidebaraccountmenu--sidebarcompanymenu).
|
||||
|
||||
## Instances
|
||||
|
||||
**Outer chrome (6):**
|
||||
|
||||
| Component | Lines | Uses | Role (inferred) |
|
||||
|---|---|---|---|
|
||||
| `Sidebar.tsx` | (unread) | ≥3 | Main app navigation sidebar |
|
||||
| `InstanceSidebar.tsx` | (unread) | 1 | Instance-settings scope |
|
||||
| `CompanySettingsSidebar.tsx` | (unread) | 2 | Company-settings scope |
|
||||
| `CompanyRail.tsx` | 260 | 1 | Narrow vertical rail of sortable companies (dnd-kit) |
|
||||
| `access/CompanySettingsNav.tsx` | (unread) | 1 | Tab-bar-style nav at the top of a settings surface |
|
||||
| `MobileBottomNav.tsx` | (unread) | ≥3 | Mobile-bottom-tab alternative to the desktop sidebar |
|
||||
|
||||
**Sidebar menus (2):**
|
||||
|
||||
| Component | Uses | Role (inferred) |
|
||||
|---|---|---|
|
||||
| `SidebarAccountMenu.tsx` | 2 | Account dropdown anchored to the sidebar |
|
||||
| `SidebarCompanyMenu.tsx` | 2 | Company dropdown anchored to the sidebar |
|
||||
|
||||
## Composition
|
||||
|
||||
Each of the six outer-chrome components solves a different surface problem (main nav vs settings nav vs rail vs bottom-bar), so the set is not simply a duplicate family. What they share is:
|
||||
|
||||
- They all attach to an app-level layout slot (`Layout.tsx`).
|
||||
- They all render navigation items (`SidebarNavItem` is a shared primitive, 3 uses).
|
||||
- Most compose `SidebarSection` as a grouping primitive.
|
||||
- `Sidebar` + `InstanceSidebar` + `CompanySettingsSidebar` share the "aside" shape; `CompanyRail` is a narrower 3rd-dimension variant; `MobileBottomNav` is a horizontal mobile variant; `CompanySettingsNav` is a top-of-page tab bar and arguably belongs in a different family ("page tab nav") rather than "sidebar chrome."
|
||||
|
||||
## Token note — none of these consume `sidebar-*` tokens
|
||||
|
||||
From [tokens-review.md §3](../tokens/tokens-review.md#3-sidebar--tokens-are-dead): the 8 `sidebar-*` color tokens defined in `index.css` (`--sidebar`, `--sidebar-foreground`, `--sidebar-primary`, `--sidebar-primary-foreground`, `--sidebar-accent`, `--sidebar-accent-foreground`, `--sidebar-border`, `--sidebar-ring`) have **0 code usages**. All six chrome components consume the general semantic tokens (`background`, `accent`, `border`) directly.
|
||||
|
||||
Either the `sidebar-*` family should be adopted (to enable theming the sidebar independently of the main surface), or deleted (since nothing uses it).
|
||||
|
||||
## The sidebar-menu pair
|
||||
|
||||
`SidebarAccountMenu` and `SidebarCompanyMenu` are the dropdown menus triggered from the sidebar (account actions, company switching). Both used exactly twice. Per the duplicate directive, documented as a pair here, not auto-merged. If merged, the natural shape would be `<SidebarMenu kind="account" | "company">` or `<SidebarMenu>` with `children` slots.
|
||||
|
||||
## Vocabulary observation (not resolved)
|
||||
|
||||
The chrome family uses three different names for the same category of affordance:
|
||||
|
||||
- **Sidebar** (3 of the 6): the persistent rail
|
||||
- **Rail** (1): `CompanyRail` — narrower / sortable
|
||||
- **Nav** (2): `MobileBottomNav` + `CompanySettingsNav`
|
||||
|
||||
Whether these reflect real category distinctions or casual naming is a call for the founder. Pattern extraction records the observation and moves on.
|
||||
|
||||
## Related components and patterns
|
||||
|
||||
- `SidebarNavItem` — shared nav-item primitive (3 uses; no story — see [components-review.md §Story gaps](../components/components-review.md#story-coverage-gaps))
|
||||
- `SidebarSection` — shared grouping primitive (1 use; below detail-file threshold)
|
||||
- [entity-row.md](./entity-row.md) — tangential, since the item in a sidebar is closer to a nav-item than an entity row
|
||||
105
doc/design-system/patterns/status-display.md
Normal file
105
doc/design-system/patterns/status-display.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Status Display
|
||||
|
||||
How the app renders entity status (for issues, runs, agents, goals, approvals, projects) and priority (for issues).
|
||||
|
||||
**Instances: 3 components + 1 module.**
|
||||
`StatusIcon` (14 uses), `StatusBadge` (19 uses), `PriorityIcon` (5 uses), and `status-colors.ts` (the canonical catalog that all three consume).
|
||||
|
||||
> **Pattern shape pending signal-token scoping** — see [tokens-review.md §4](../tokens/tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds). Any pattern codification that fixes the current prop shape or color-binding will likely change once the signal-token family lands.
|
||||
|
||||
## Instances
|
||||
|
||||
### `StatusIcon` (14 uses, `ui/src/components/StatusIcon.tsx`)
|
||||
|
||||
Small circular status indicator for issue status.
|
||||
|
||||
```ts
|
||||
interface StatusIconProps {
|
||||
status: string; // untyped — see Open questions
|
||||
onChange?: (status: string) => void;
|
||||
className?: string;
|
||||
showLabel?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
- Renders a `<span>` shaped as a circle with `border-2` and a hue from `issueStatusIcon[status]`.
|
||||
- Includes a special "done" variant that fills the circle.
|
||||
- If `onChange` is provided, wraps in a `<Popover>` with a picker of all statuses.
|
||||
- Used across issue lists, properties panels, inbox, issue detail.
|
||||
|
||||
### `StatusBadge` (19 uses, `ui/src/components/StatusBadge.tsx`)
|
||||
|
||||
Pill-shaped status badge for any entity type.
|
||||
|
||||
```ts
|
||||
interface Props {
|
||||
status: string; // untyped — see Open questions
|
||||
}
|
||||
```
|
||||
|
||||
- 15-line component — wraps a `<span>` with `statusBadge[status]` classes.
|
||||
- Uses `rounded-full px-2.5 py-0.5 text-xs` — a fixed shape across all statuses.
|
||||
- `status` prop accepts an open string. The `statusBadge` record in `status-colors.ts` has 24 known keys covering agent / goal / run / approval / issue domains.
|
||||
|
||||
### `PriorityIcon` (5 uses, `ui/src/components/PriorityIcon.tsx`)
|
||||
|
||||
Small priority indicator (up/down/flat arrow or warning triangle).
|
||||
|
||||
```ts
|
||||
interface PriorityIconProps {
|
||||
priority: string; // untyped — see Open questions
|
||||
onChange?: (priority: string) => void;
|
||||
className?: string;
|
||||
showLabel?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
- Renders one of four lucide icons (`ArrowUp`, `ArrowDown`, `Minus`, `AlertTriangle`) per priority level.
|
||||
- Same popover-when-onChange pattern as `StatusIcon`.
|
||||
- Priority values: `critical`, `high`, `medium`, `low`.
|
||||
|
||||
### `status-colors.ts` (`ui/src/lib/status-colors.ts`)
|
||||
|
||||
Canonical status-and-priority color catalog:
|
||||
|
||||
```ts
|
||||
export const issueStatusIcon: Record<string, string> = { … };
|
||||
export const issueStatusText: Record<string, string> = { … };
|
||||
export const statusBadge: Record<string, string> = { … };
|
||||
export const agentStatusDot: Record<string, string> = { … };
|
||||
export const priorityColor: Record<string, string> = { … };
|
||||
```
|
||||
|
||||
The file's header says _"Every component that renders a status indicator should import from here so colors stay consistent."_ — the three components above do. So does `AgentActionButtons`, `ProjectProperties`, `AgentDetail`, and a handful of pages.
|
||||
|
||||
## Composition (shared pattern)
|
||||
|
||||
```
|
||||
<statusValue-prop> → <record lookup in status-colors.ts> → <raw Tailwind-palette classes> → <render>
|
||||
```
|
||||
|
||||
All three components follow this template. The pattern is correct in spirit — there is a central catalog. But the catalog contains raw Tailwind palette values (`text-blue-600 dark:text-blue-400`, `bg-green-100 text-green-700`, etc.), not DS tokens.
|
||||
|
||||
## Untokenized colors
|
||||
|
||||
Per [tokens-review.md §4](../tokens/tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds), this entire pattern bypasses the DS token layer. The 11 hues currently in play (`blue`, `cyan`, `sky`, `green`, `emerald`, `amber`, `orange`, `yellow`, `violet`, `red`, `neutral`) across 24+ status keys could consolidate into a `--signal-*` token family:
|
||||
|
||||
- `--signal-success`, `--signal-warning`, `--signal-error`, `--signal-info`, `--signal-in-progress`, `--signal-in-review`, `--signal-neutral` — each with `-bg`, `-text`, `-border` variants.
|
||||
|
||||
Not proposed as a concrete change here. Flagged as the token gap that blocks this pattern from being codifiable.
|
||||
|
||||
## Props are untyped strings
|
||||
|
||||
Both `StatusIcon.status: string` and `PriorityIcon.priority: string` are open strings — TypeScript doesn't prevent callers from passing `"banana"`. The records in `status-colors.ts` fall through to a `*Default` class when a key is missing, so bad input degrades silently.
|
||||
|
||||
Once signal tokens exist, these props should be typed enums whose members match the signal-token keys. Any callers passing arbitrary strings will then light up at compile time.
|
||||
|
||||
## Related patterns and token drift
|
||||
|
||||
- Page-level: [detail-page.md](./detail-page.md) (status appears in detail-page headers)
|
||||
- The "severity indicator" family — `BudgetPolicyCard`, `BudgetIncidentCard`, `BudgetSidebarMarker`, `QuotaBar` — uses a **third** color system (hand-picked red/amber/emerald at varying opacities), distinct from `status-colors.ts` and from the dead `--chart-*` tokens. See [patterns-review.md §Candidates](./patterns-review.md) for notes.
|
||||
- `AgentActionButtons` and `ActivityCharts` also consume `status-colors.ts` / hardcoded hex — see [components-review.md §Token non-compliance](../components/components-review.md#token-non-compliance).
|
||||
|
||||
## Do-not-codify (yet)
|
||||
|
||||
Do not propose concrete refactors of `StatusIcon` / `StatusBadge` / `PriorityIcon` shape before the signal-token work. The prop API (typed enums), class naming, and default-fallback behavior all depend on what signal tokens look like.
|
||||
56
doc/design-system/patterns/subscription-panel.md
Normal file
56
doc/design-system/patterns/subscription-panel.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Subscription Panel
|
||||
|
||||
Panel summarizing a per-vendor subscription quota (rolling windows, reset times, session vs weekly).
|
||||
|
||||
**Instances: 2 (below the 3+ threshold, documented per directive).**
|
||||
`ClaudeSubscriptionPanel`, `CodexSubscriptionPanel`.
|
||||
|
||||
> **Extraction-only pass.** Documents the pair as it exists; does not prescribe a collapse to a single `SubscriptionPanel({ vendor })`. See [components-review.md §Likely duplicates #3](../components/components-review.md#3-subscription-panel-pair-claudesubscriptionpanel--codexsubscriptionpanel).
|
||||
|
||||
## Instances
|
||||
|
||||
| Component | Lines | Uses | Vendor |
|
||||
|---|---|---|---|
|
||||
| `ClaudeSubscriptionPanel.tsx` | 140 | 1 | Anthropic Claude |
|
||||
| `CodexSubscriptionPanel.tsx` | (unread) | 1 | OpenAI Codex |
|
||||
|
||||
## Composition
|
||||
|
||||
`ClaudeSubscriptionPanel` signature:
|
||||
|
||||
```ts
|
||||
interface ClaudeSubscriptionPanelProps {
|
||||
windows: QuotaWindow[];
|
||||
source?: string | null;
|
||||
error?: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
- Takes an array of `QuotaWindow` (from `@paperclipai/shared`).
|
||||
- Renders ordered windows (session, week-all-models, week-sonnet-only, week-opus-only, extra-usage).
|
||||
- Shows a reset timestamp per window using `toLocaleString`.
|
||||
|
||||
`CodexSubscriptionPanel` is parallel — same shape of inputs, same conceptual layout, different window ordering and different label normalization presumably.
|
||||
|
||||
Both are composed by `ProviderQuotaCard` — the host that decides "this is a Claude provider, render `ClaudeSubscriptionPanel`; this is a Codex provider, render `CodexSubscriptionPanel`."
|
||||
|
||||
## Variance
|
||||
|
||||
- **Window keys differ** (`currentsession`, `currentweekallmodels`, `currentweeksonnetonly`, … in Claude; likely a different set in Codex).
|
||||
- **Label rules differ** — each has its own `normalizeLabel`.
|
||||
- **Reset-time rendering is identical pattern** (`window.resetsAt → toLocaleString`).
|
||||
- **Error surface is the same prop**.
|
||||
|
||||
## Scale caveat
|
||||
|
||||
Only 2 instances. The "pattern" shape is strong because the components look parallel, but there's no third point to triangulate from. If a third vendor is added (Gemini? Cursor?), the pattern becomes real; until then, two parallel files that share ~60% structure.
|
||||
|
||||
## Open questions
|
||||
|
||||
- Should `ProviderQuotaCard` own the vendor-specific rendering directly, or should the pair stay as separate files the card dispatches to?
|
||||
- Are the two files diffable down to a `windowsConfig` data shape + a shared renderer? This would be the path if consolidation is pursued.
|
||||
|
||||
## Related
|
||||
|
||||
- [finance-card.md](./finance-card.md) — `BillerSpendCard` and `ProviderQuotaCard` are the orchestrator cards that consume these subscription panels.
|
||||
- [tokens-review.md §4](../tokens/tokens-review.md) — subscription panels may consume status-color logic for "over-quota" state; confirm during any future consolidation.
|
||||
402
doc/design-system/tokens/tokens-review.md
Normal file
402
doc/design-system/tokens/tokens-review.md
Normal file
@@ -0,0 +1,402 @@
|
||||
# Token Review
|
||||
|
||||
This is the high-value artifact from Stage 1. It lists what looks wrong, inconsistent, or underdetermined about the token set. Ordered by expected human value, not by stage.
|
||||
|
||||
- **Generated:** 2026-04-21
|
||||
- **Repo SHA:** a26e1288b627e82c554445732c7d844648e6b5e1
|
||||
- **Scope:** `ui/` (`@paperclipai/ui`)
|
||||
- **Inventory:** [tokens.md](./tokens.md), [tokens.json](./tokens.json)
|
||||
|
||||
---
|
||||
|
||||
## High-confidence drift (likely should be fixed)
|
||||
|
||||
### 1. `chart-*` tokens — reserved
|
||||
|
||||
**Status: RECLASSIFIED (2026-04-21).** Tokens preserved as **reserved** for the future chart-tokenization project. Not considered drift; do not consume today. Comment added above the `@theme` block in `ui/src/index.css`. Original finding retained below for history.
|
||||
|
||||
**Original finding:** `--chart-1` through `--chart-5` have **0 code usages** (excluding their definitions and the `DesignGuide.tsx` swatch showcase). Yet the app renders charts — `ui/src/components/ActivityCharts.tsx` and `ui/src/pages/OrgChart.tsx` carry the chart color logic.
|
||||
|
||||
**What they use instead:** raw Tailwind-palette hex values, typed directly into TSX.
|
||||
|
||||
```
|
||||
ui/src/components/ActivityCharts.tsx:125-128
|
||||
critical: "#ef4444", high: "#f97316", medium: "#eab308", low: "#6b7280",
|
||||
|
||||
ui/src/components/ActivityCharts.tsx:178-184
|
||||
todo: "#3b82f6", in_progress: "#8b5cf6", in_review: "#a855f7",
|
||||
done: "#10b981", blocked: "#ef4444", cancelled: "#6b7280", backlog: "#64748b",
|
||||
|
||||
ui/src/components/ActivityCharts.tsx:258
|
||||
color = rate >= 0.8 ? "#10b981" : rate >= 0.5 ? "#eab308" : "#ef4444";
|
||||
|
||||
ui/src/pages/OrgChart.tsx:162-169
|
||||
running: "#22d3ee", active: "#4ade80", paused: "#facc15",
|
||||
idle: "#facc15", error: "#f87171", terminated: "#a3a3a3",
|
||||
```
|
||||
|
||||
**Why it matters:** `--chart-1..5` were reserved by the shadcn default theme for "data visualization tokens", but the actual chart layer never migrated onto them. Chart color is currently a parallel system, bound to Tailwind palette + status semantics rather than to the DS.
|
||||
|
||||
**Suggested direction (not decided here):** Replace `chart-1..5` with a semantic status-token family (see §4) and have `ActivityCharts` / `OrgChart` consume those. Or: delete `chart-1..5` if there's no chart design language yet and note the gap.
|
||||
|
||||
### 2. `destructive-foreground` has a wrong light-mode value AND is unused
|
||||
|
||||
**Status: RESOLVED (2026-04-21).** Light-mode value corrected to `oklch(0.985 0 0)` to match the dark-mode pattern. `ui/src/index.css:63`. Zero-risk change (0 production consumers confirmed). Original finding retained below for history.
|
||||
|
||||
**Original finding:** The light-mode value of `--destructive-foreground` was `oklch(0.577 0.245 27.325)` — the same red as `--destructive` itself. That means light-mode destructive text rendered over a destructive background would be invisible. The dark-mode value `oklch(0.985 0 0)` (white) is correct.
|
||||
|
||||
```
|
||||
ui/src/index.css:62 --destructive: oklch(0.577 0.245 27.325);
|
||||
ui/src/index.css:63 --destructive-foreground: oklch(0.577 0.245 27.325); ← likely bug
|
||||
ui/src/index.css:97 --destructive: oklch(0.637 0.237 25.331);
|
||||
ui/src/index.css:98 --destructive-foreground: oklch(0.985 0 0); ← correct
|
||||
```
|
||||
|
||||
**Why no one has noticed:** `--destructive-foreground` has **0 code usages**. Components using destructive color use `bg-destructive` with `text-destructive` or implicit foreground, never `text-destructive-foreground` on a destructive-filled surface. So the bug is masked by non-adoption.
|
||||
|
||||
**Suggested direction:** Either delete the token, or fix its light value (likely `oklch(0.985 0 0)`) and actually use it on destructive buttons / filled badges.
|
||||
|
||||
### 3. `sidebar-*` tokens — reserved
|
||||
|
||||
**Status: RECLASSIFIED (2026-04-21).** Tokens preserved as **reserved** for shadcn sidebar primitive compatibility in case that primitive is reintroduced. Not considered drift; do not consume today. Comment added above the `@theme` block in `ui/src/index.css`. Original finding retained below for history.
|
||||
|
||||
**Original finding:** All 8 sidebar tokens (`sidebar`, `sidebar-foreground`, `sidebar-primary`, `sidebar-primary-foreground`, `sidebar-accent`, `sidebar-accent-foreground`, `sidebar-border`, `sidebar-ring`) have **0 code usages**. Only references are the `@theme inline` alias block and the `DesignGuide.tsx` swatch showcase.
|
||||
|
||||
```
|
||||
rg -P "bg-sidebar|text-sidebar|border-sidebar|--sidebar" ui/src
|
||||
→ only hits in ui/pages/DesignGuide.tsx and ui/src/index.css
|
||||
```
|
||||
|
||||
**Why:** The shadcn `Sidebar` primitive was not installed (no `ui/src/components/ui/sidebar.tsx`). The app has a custom `ui/src/components/Sidebar.tsx` that consumes the general semantic tokens (`background`, `accent`, `border`, etc.) directly. The `sidebar-*` family came from the shadcn default `components.json` theme generation and was never adopted.
|
||||
|
||||
**Suggested direction:** Either (a) delete the `sidebar-*` family from `index.css`, or (b) refactor `Sidebar.tsx` to consume them so the sidebar can be themed independently of the main surface. Status quo is dead code.
|
||||
|
||||
Note: in light mode, every `sidebar-*` value equals a semantic-surface value except the first — `sidebar` = `oklch(0.985 0 0)` vs `background` = `oklch(1 0 0)`. So consolidation would be nearly free if option (a) is chosen.
|
||||
|
||||
### 4. `status-colors.ts` is a canonical semantic-color catalog that bypasses the DS
|
||||
|
||||
**Status: PARTIALLY ADDRESSED (2026-04-21).** Two new action-severity tokens added — `--signal-success` and `--signal-success-foreground` — paired with the existing `--destructive` / `--destructive-foreground` as the DS's solid-accent severity vocabulary. `status-colors.ts` itself is **not touched** in this pass; tokenizing its entity-state coloring as a `--status-*` family is a deferred future project. `--signal-warning` and `--signal-info` are intentionally not added (see §Deferred signal variants below). Original finding retained below for history.
|
||||
|
||||
**Separation of concerns now documented.** `destructive` / `signal-success` = **action severity** (solid buttons, toasts). `status-colors.ts` = **entity state** (issue status, agent status, priority) — stays as a TypeScript catalog. `--chart-*` and `--sidebar-*` remain reserved (see §1, §3).
|
||||
|
||||
**Original finding:**
|
||||
|
||||
**Finding:** `ui/src/lib/status-colors.ts` defines the color language for all entity statuses and priorities (issues, agents, goals, runs, approvals) and then renders them with **raw Tailwind palette classes**. Every one of ~24 status/priority entries looks like:
|
||||
|
||||
```ts
|
||||
// ui/src/lib/status-colors.ts (excerpt)
|
||||
issueStatusIcon.todo = "text-blue-600 border-blue-600 dark:text-blue-400 dark:border-blue-400"
|
||||
statusBadge.running = "bg-cyan-100 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300"
|
||||
statusBadge.pending_approval = "bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300"
|
||||
priorityColor.critical = "text-red-600 dark:text-red-400"
|
||||
agentStatusDot.running = "bg-cyan-400 animate-pulse"
|
||||
```
|
||||
|
||||
The file header says _"Every component that renders a status indicator … should import from here so colors stay consistent"_ — i.e. status color IS treated as part of the design system, but it lives in TypeScript, not in CSS tokens, and uses raw Tailwind scales rather than semantic tokens.
|
||||
|
||||
**Hues in active status use:** blue, cyan, sky, green, emerald, amber, orange, yellow, violet, red, neutral. That's 11 hues — more than the shadcn default palette anticipates.
|
||||
|
||||
**Why it matters:** This is the single largest DS gap found in Stage 1. A token family like `--signal-success`, `--signal-warning`, `--signal-error`, `--signal-info`, `--signal-in-progress`, `--signal-review`, `--signal-neutral` (each with `-bg`, `-text`, `-border` variants) would unify the status color language, make dark-mode pairing automatic, and close the drift feeding into `ActivityCharts.tsx` (§1).
|
||||
|
||||
**Suggested direction:** Design a signal/status token family and migrate `status-colors.ts` onto it. Do this **before** Stage 3 extracts `StatusBadge`, `StatusIcon`, `PriorityIcon`, `AgentStatusDot` — otherwise those component docs will bake in the raw-palette drift.
|
||||
|
||||
### 5. Theme-color meta tag uses hardcoded hex
|
||||
|
||||
```
|
||||
ui/src/context/ThemeContext.tsx:20 const DARK_THEME_COLOR = "#18181b";
|
||||
ui/src/context/ThemeContext.tsx:21 const LIGHT_THEME_COLOR = "#ffffff";
|
||||
```
|
||||
|
||||
These drive the browser's `<meta name="theme-color">` tag so the mobile browser chrome matches the app. They're sort-of the inverse of `--background`:
|
||||
|
||||
- Light: `--background = oklch(1 0 0) = #ffffff` — the hardcoded value matches.
|
||||
- Dark: `--background = oklch(0.145 0 0) ≈ #252525`, but the hardcoded is `#18181b` (zinc-900). **Mismatch.**
|
||||
|
||||
**Suggested direction:** Compute from `--background` at runtime, or keep the hardcoded values but make them match `--background` exactly. Currently the mobile chrome is a different dark than the app's background.
|
||||
|
||||
---
|
||||
|
||||
## Medium-confidence drift
|
||||
|
||||
### 6. Raw Tailwind palette usage: 659 occurrences across 83 files
|
||||
|
||||
The codebase bypasses semantic tokens for a large number of styling decisions:
|
||||
|
||||
```
|
||||
rg -P "\b(bg|text|border|...)-(neutral|gray|zinc|slate|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(50|100|…|950)\b" ui/src
|
||||
→ 659 occurrences across 83 files
|
||||
```
|
||||
|
||||
**Heavy offenders (ranked):**
|
||||
|
||||
| File | Hits | Notes |
|
||||
|---|---|---|
|
||||
| `ui/src/pages/AgentDetail.tsx` | 75 | Production page — worth migrating. |
|
||||
| `ui/src/pages/InviteLanding.tsx` | 58 | Auth surface with zinc-based dark palette; deliberately different visual language than the app. |
|
||||
| `ui/src/pages/InviteUxLab.tsx` | 55 | UX lab prototype. Acceptable scratch. |
|
||||
| `ui/src/lib/status-colors.ts` | 47 | See §4. |
|
||||
| `ui/src/components/transcript/RunTranscriptView.tsx` | 47 | Feature uses palette for agent action differentiation — tokenize. |
|
||||
| `ui/src/components/IssueChatThread.tsx` | 22 | Production component. |
|
||||
| `ui/src/pages/Inbox.tsx` | 16 | |
|
||||
| `ui/src/pages/DesignGuide.tsx` | 13 | Swatch page — presentational, acceptable. |
|
||||
| `ui/src/pages/IssueChatUxLab.tsx` | 6 | UX lab. |
|
||||
| `ui/src/pages/RunTranscriptUxLab.tsx` | 10 | UX lab. |
|
||||
|
||||
**Buckets:**
|
||||
- **Clearly intentional scratch / auth surface:** UX Labs (`IssueChatUxLab`, `InviteUxLab`, `RunTranscriptUxLab`), `InviteLanding` — probably should stay.
|
||||
- **Status color catalog:** `status-colors.ts` + `ActivityCharts.tsx` — covered by §4, §1.
|
||||
- **Production surfaces that silently diverge:** `AgentDetail.tsx`, `RunTranscriptView.tsx`, `IssueChatThread.tsx`, `Inbox.tsx` — highest ROI to migrate.
|
||||
|
||||
### 7. Arbitrary radius values bypass the scale (18 occurrences)
|
||||
|
||||
```
|
||||
ui/src/pages/InviteUxLab.tsx rounded-[28px], rounded-[24px], rounded-[32px]
|
||||
ui/src/pages/IssueChatUxLab.tsx rounded-[28px], rounded-[32px]
|
||||
ui/src/pages/RunTranscriptUxLab.tsx rounded-xl (via theme), rounded-2xl (via TW default)
|
||||
ui/src/pages/ProfileSettings.tsx rounded-[28px], rounded-[24px]
|
||||
ui/src/pages/CompanySettings.tsx rounded-[14px]
|
||||
ui/src/components/CompanyRail.tsx rounded-[14px], rounded-[22px]
|
||||
```
|
||||
|
||||
These are almost all in pages that already opt out of the default radius scale. See §Radius scale below for the likely reason.
|
||||
|
||||
### 8. Project/label color fallbacks fragmented across 10+ files
|
||||
|
||||
Two default fallback hexes for user-picked project/label colors:
|
||||
|
||||
- `#6366f1` (indigo-500) — in `IssueProperties.tsx`, `SidebarProjects.tsx`, `NewIssueDialog.tsx`, `ProjectDetail.tsx`, `CompanySettings.tsx`
|
||||
- `#64748b` (slate-500) — in `IssueColumns.tsx`, `RoutineRunVariablesDialog.tsx`, `RoutineDetail.tsx`, `Routines.tsx`, `MarkdownEditor.tsx`
|
||||
|
||||
Not strictly drift — these are fallbacks for a user-supplied field, not the DS. But they disagree with each other, and every caller has duplicated the literal. A single `const DEFAULT_PROJECT_COLOR` (or better, a DS token + utility) would fix it.
|
||||
|
||||
### 9. Arbitrary shadow values in production surfaces
|
||||
|
||||
```
|
||||
shadow-[0_24px_60px_rgba(15,23,42,0.08)] UxLab pages, ProfileSettings
|
||||
shadow-[0_20px_80px_-40px_rgba(0,0,0,0.55)] BudgetPolicyCard
|
||||
shadow-[0_0_12px_rgba(6,182,212,0.08)] AgentDetail (live indicator)
|
||||
shadow-[0_18px_50px_rgba(6,182,212,0.08)] LiveRunWidget
|
||||
```
|
||||
|
||||
No `--shadow-*` tokens exist, so there's nothing to migrate _to_, but these are the signal that a shadow/elevation token family would pay for itself quickly.
|
||||
|
||||
---
|
||||
|
||||
## Low-confidence drift (candidates for new tokens)
|
||||
|
||||
### 10. Code block theme is a hardcoded Catppuccin Mocha palette
|
||||
|
||||
```
|
||||
ui/src/index.css:553 background: #1e1e2e; ← code bg
|
||||
ui/src/index.css:554 color: #cdd6f4; ← code fg
|
||||
ui/src/index.css:567 background-color: #181825; ← gutter bg
|
||||
ui/src/index.css:568 color: #585b70; ← gutter fg
|
||||
ui/src/index.css:569 border-right: 1px solid #313244;
|
||||
ui/src/index.css:614 background-color: #313244; ← language selector bg
|
||||
ui/src/index.css:616 border-color: #45475a;
|
||||
ui/src/index.css:586 color-mix(…, #89b4fa 25%, transparent) ← selection
|
||||
```
|
||||
|
||||
This is a deliberate choice (Catppuccin Mocha is a widely-loved dev theme and it's consistent across MDXEditor + rendered markdown + CodeMirror). But the choice is buried in selectors and spread across 30+ lines of `index.css`. Candidate `--code-bg`, `--code-fg`, `--code-gutter-bg`, `--code-gutter-fg`, `--code-border`, `--code-selection` tokens would:
|
||||
|
||||
- Make the choice legible.
|
||||
- Make "port to Catppuccin Latte for light mode" a one-line change.
|
||||
- Let MDXEditor and the `.paperclip-markdown` renderer reference the same tokens rather than copy the values.
|
||||
|
||||
### 11. GitHub link colors hardcoded
|
||||
|
||||
```
|
||||
ui/src/index.css:429 color: color-mix(in oklab, var(--foreground) 76%, #0969da 24%);
|
||||
ui/src/index.css:436 color: color-mix(in oklab, var(--foreground) 80%, #58a6ff 20%); (.dark)
|
||||
ui/src/index.css:756 (duplicated for .paperclip-markdown)
|
||||
ui/src/index.css:772 (duplicated for .paperclip-markdown dark)
|
||||
```
|
||||
|
||||
Candidate `--link` / `--link-foreground` tokens.
|
||||
|
||||
### 12. MDXEditor editor text colors hardcoded
|
||||
|
||||
The CodeMirror-inside-MDXEditor rules (`index.css:560–618`) hardcode `#cdd6f4`, `#89b4fa`, `#313244` etc. for cursor, selection, gutter. Covered by §10 if `--code-*` tokens are added.
|
||||
|
||||
---
|
||||
|
||||
## Reserved & near-dead tokens
|
||||
|
||||
Summary of tokens whose production usage is at or near zero. **As of 2026-04-21, the 13 previously-flagged "dead" tokens are reclassified as Reserved.** They remain defined in `ui/src/index.css` with explanatory comments; they are not a consolidation opportunity.
|
||||
|
||||
| Token | Code usage | Disposition |
|
||||
|---|---|---|
|
||||
| `chart-1..5` | 0 each | **Reserved** for future chart tokenization (see §1) |
|
||||
| `sidebar`, `sidebar-foreground`, `sidebar-primary`, `sidebar-primary-foreground`, `sidebar-accent`, `sidebar-accent-foreground`, `sidebar-border`, `sidebar-ring` | 0 each | **Reserved** for shadcn sidebar compatibility (see §3) |
|
||||
| `destructive-foreground` | 0 | Unused but light value corrected on 2026-04-21 (see §2). Paired with `--destructive` and with the new `--signal-success-foreground`; kept as part of the action-severity vocabulary. |
|
||||
| `card-foreground` | 1 | Near-dead. Not necessarily wrong — `card-foreground` equals `foreground` in both modes, so `text-foreground` inside a card is sufficient. |
|
||||
| `secondary-foreground` | 2 | Near-dead |
|
||||
| `secondary` | 3 | Near-dead |
|
||||
| `popover-foreground` | 5 | Low use — likely limited to `popover.tsx` primitive |
|
||||
|
||||
No longer flagged as a consolidation opportunity. The 34-token color set is the intended surface.
|
||||
|
||||
---
|
||||
|
||||
## Duplicate values
|
||||
|
||||
These groups of tokens share identical values. Most are classic shadcn-default structural overlaps (intentional, so `*-foreground` patterns match across surfaces). A few are suspicious.
|
||||
|
||||
**Light mode:**
|
||||
|
||||
| Value | Tokens |
|
||||
|---|---|
|
||||
| `oklch(1 0 0)` | `background`, `card`, `popover` |
|
||||
| `oklch(0.145 0 0)` | `foreground`, `card-foreground`, `popover-foreground`, `sidebar-foreground` |
|
||||
| `oklch(0.205 0 0)` | `primary`, `secondary-foreground`, `accent-foreground`, `sidebar-primary` |
|
||||
| `oklch(0.985 0 0)` | `primary-foreground`, `sidebar`, `sidebar-primary-foreground`, `sidebar-accent-foreground` |
|
||||
| `oklch(0.97 0 0)` | `secondary`, `muted`, `accent`, `sidebar-accent` |
|
||||
| `oklch(0.922 0 0)` | `border`, `input`, `sidebar-border` |
|
||||
| `oklch(0.708 0 0)` | `ring`, `sidebar-ring` |
|
||||
| `oklch(0.577 0.245 27.325)` | `destructive`, `destructive-foreground` ⚠ (see §2) |
|
||||
|
||||
**Dark mode** (analogous structure — all sidebar tokens match their non-sidebar counterparts except `sidebar-primary = oklch(0.488 0.243 264.376)`, a distinct blue).
|
||||
|
||||
**Takeaway:** In light mode, every `sidebar-*` value equals its non-sidebar counterpart; in dark mode, `sidebar-primary` drifts (blue) from `primary` (white). Per the 2026-04-21 decision, the sidebar family is **preserved as reserved** (see §3) rather than collapsed — the identity with non-sidebar tokens is fine; the value of reserving is keeping the option of theming the sidebar independently if that need emerges.
|
||||
|
||||
---
|
||||
|
||||
## Non-semantic color usage
|
||||
|
||||
(Covered by §6 above and enumerated there with offender files and hit counts.)
|
||||
|
||||
---
|
||||
|
||||
## Radius scale
|
||||
|
||||
**Status: RESOLVED (2026-04-21).** Scale restored to monotonic `sm=6px, md=8px, lg=10px, xl=12px`. 226 call sites in `ui/src/` migrated from `rounded-lg` / `rounded-xl` → `rounded-none` to preserve the existing flat-Swiss aesthetic on dashboard surfaces. Shadcn primitives (`ui/src/components/ui/**`) excluded from migration — `dialog.tsx` retains `rounded-lg` so `DialogContent` now renders with real 10px-rounded corners (the first observable visual change from the radius work, intentional). One test assertion updated in lockstep: `ProjectWorkspaceSummaryCard.test.tsx:135` (`rounded-lg` → `rounded-none`).
|
||||
|
||||
Original Stage 1 observations retained below for history.
|
||||
|
||||
### What's defined
|
||||
|
||||
```
|
||||
ui/src/index.css:39 --radius-sm: 0.375rem; (6px) @theme inline
|
||||
ui/src/index.css:40 --radius-md: 0.5rem; (8px) @theme inline
|
||||
ui/src/index.css:41 --radius-lg: 0px; @theme inline
|
||||
ui/src/index.css:42 --radius-xl: 0px; @theme inline
|
||||
ui/src/index.css:47 --radius: 0; :root (not @theme)
|
||||
```
|
||||
|
||||
### What's non-standard
|
||||
|
||||
1. **Non-monotonic progression.** Almost every design-system radius scale is `sm ≤ md ≤ lg ≤ xl`. Here it's `sm=6, md=8, lg=0, xl=0`. Two possibilities:
|
||||
- Intentional flattening at the outer scale (keep small things rounded for friendliness, keep large surfaces square for an editorial look).
|
||||
- Mid-migration artifact from flipping the scale to 0 from the shadcn defaults (`lg=0.5rem`, `xl=0.75rem`) and not yet reconciling.
|
||||
|
||||
2. **Two coexisting radius bases.** `--radius` (value 0) lives at `:root` and is referenced by `index.css`'s own `calc(var(--radius) - 2px)` expressions and by the MDXEditor bridge (`--baseRadius: var(--radius)`). But the Tailwind-visible scale is the `@theme`-scoped `--radius-sm/md/lg/xl`. These never cross-reference. An editor reading the file might reasonably expect `--radius-md` to be derived from `--radius`, but they're independent.
|
||||
|
||||
3. **Heavy use of utilities that resolve to 0.**
|
||||
|
||||
| Utility | Value | Uses |
|
||||
|---|---|---|
|
||||
| `rounded-sm` | 0.375rem | 49 |
|
||||
| `rounded-md` | 0.5rem | 310 |
|
||||
| `rounded-lg` | **0px** | **146** |
|
||||
| `rounded-xl` | **0px** | **81** |
|
||||
| `rounded-2xl` | (Tailwind default, not themed) | 21 |
|
||||
| `rounded-full` | full | 208 |
|
||||
| `rounded-none` | 0 | 36 |
|
||||
| `rounded` (no suffix) | (Tailwind default) | 127 |
|
||||
|
||||
So **227 places** in the codebase write `rounded-lg` or `rounded-xl` and get square corners. Without context on whether that's the intended look, this is ambiguous — if intentional, all those sites are fine; if unintentional, every one of them is a small visual bug.
|
||||
|
||||
4. **Arbitrary radius values (see §7).** 18 occurrences of `rounded-[14px]`, `rounded-[22px]`, `rounded-[24px]`, `rounded-[28px]`, `rounded-[32px]` — concentrated in pages that want actually-rounded corners. If `rounded-lg` resolved to ~12px, some of these would go away.
|
||||
|
||||
### Open question for the founder
|
||||
|
||||
_Is the radius scale intentionally flattening at the top (lg/xl = 0), or is that a stale state from earlier shadcn defaults that should be replaced with a monotonic scale?_
|
||||
|
||||
**Resolved 2026-04-21.** Hybrid: dashboard surfaces stay flat (`rounded-none`), the scale is restored monotonically for newer surfaces and for the `dialog.tsx` primitive. 226 host-code call sites migrated.
|
||||
|
||||
---
|
||||
|
||||
### Radius workaround audit: 18 occurrences, all retained
|
||||
|
||||
Per the decision to audit each `rounded-[Npx]` workaround: none match the new scale (6 / 8 / 10 / 12 px). All are intentional. Table below captures the disposition.
|
||||
|
||||
| File | Line | Value | Context | Disposition |
|
||||
|---|---|---|---|---|
|
||||
| `ui/src/pages/IssueChatUxLab.tsx` | 52 | `rounded-[28px]` | Chat-surface outer container (prototype) | **Keep** — editorial pill > xl scale; UX Lab prototype |
|
||||
| `ui/src/pages/IssueChatUxLab.tsx` | 139 | `rounded-[32px]` | Hero container with layered gradients (prototype) | **Keep** — ditto |
|
||||
| `ui/src/pages/InviteUxLab.tsx` | 112 | `rounded-[28px]` | Invite-panel outer (prototype) | **Keep** — UX Lab |
|
||||
| `ui/src/pages/InviteUxLab.tsx` | 149 | `rounded-[24px]` | Tone card (prototype) | **Keep** — UX Lab |
|
||||
| `ui/src/pages/InviteUxLab.tsx` | 171 | `rounded-[28px]` | Dark invite capsule (prototype) | **Keep** — UX Lab |
|
||||
| `ui/src/pages/InviteUxLab.tsx` | 454 | `rounded-[28px]` | Alt invite panel (prototype) | **Keep** — UX Lab |
|
||||
| `ui/src/pages/InviteUxLab.tsx` | 529 | `rounded-[28px]` | Tone card variant (prototype) | **Keep** — UX Lab |
|
||||
| `ui/src/pages/InviteUxLab.tsx` | 612 | `rounded-[28px]` | Tone card variant (prototype) | **Keep** — UX Lab |
|
||||
| `ui/src/pages/InviteUxLab.tsx` | 700 | `rounded-[32px]` | Hero gradient container (prototype) | **Keep** — UX Lab |
|
||||
| `ui/src/pages/ProfileSettings.tsx` | 159 | `rounded-[28px]` | Profile header hero card | **Keep** — intentional oversize hero radius |
|
||||
| `ui/src/pages/ProfileSettings.tsx` | 163 | `rounded-[24px]` | Profile identity panel | **Keep** — matches hero language |
|
||||
| `ui/src/pages/CompanySettings.tsx` | 296 | `rounded-[14px]` | Brand-color preview swatch | **Keep** — matches CompanyRail icon scale |
|
||||
| `ui/src/components/CompanyRail.tsx` | 99 | `rounded-[14px]` | Company icon (selected state) | **Keep** — iOS-icon-scale, morphing-radius pattern |
|
||||
| `ui/src/components/CompanyRail.tsx` | 100 | `rounded-[22px]` | Company icon (unselected state) | **Keep** — part of the hover-morph pair |
|
||||
| `ui/src/components/CompanyRail.tsx` | 247 | `rounded-[22px]` → `[14px]` on hover | "New company" button | **Keep** — same morph pattern |
|
||||
| `ui/src/components/ui/checkbox.tsx` | 17 | `rounded-[4px]` | Checkbox control | **Keep** — shadcn primitive; excluded from migration |
|
||||
| `ui/src/components/ui/tooltip.tsx` | 51 | `rounded-[2px]` | Tooltip arrow | **Keep** — shadcn primitive; excluded from migration |
|
||||
| `ui/src/components/ui/scroll-area.tsx` | 19 | `rounded-[inherit]` | Scroll viewport | **Keep** — non-numeric special value; can't match the scale by construction |
|
||||
|
||||
Summary:
|
||||
- **9 in UX Lab pages** (`InviteUxLab`, `IssueChatUxLab`) — prototypes, not pursued.
|
||||
- **2 in ProfileSettings** — intentional editorial hero radii (24px / 28px).
|
||||
- **1 in CompanySettings** — brand-swatch match to the rail.
|
||||
- **3 in CompanyRail** — morphing-radius iOS-icon pattern (22px ↔ 14px on hover).
|
||||
- **3 in shadcn primitives** — out of scope per the migration rule.
|
||||
|
||||
No rewrites. No consolidations. All values sit outside the new 6-8-10-12 scale either because they're editorial (20-30px range), because they're part of a morphing-radius interaction (14/22), or because they're shadcn-internal micro-radii (2/4 px, or `inherit`).
|
||||
|
||||
---
|
||||
|
||||
## Deferred signal variants
|
||||
|
||||
Captured here so they don't get forgotten. **Intentionally not added** in the 2026-04-21 pass:
|
||||
|
||||
- **`--signal-success-soft` / `--signal-success-subtle`** — a lower-weight variant for use on toast surfaces and inline success banners. Current soft-success rendering uses `bg-emerald-50 text-emerald-900` (light) and `bg-emerald-950/60 text-emerald-100` (dark) in [`ToastViewport.tsx`](../../../ui/src/components/ToastViewport.tsx) — a different hue family (emerald) than the new solid `--signal-success` (green-700/600). A soft variant would let the toast adopt the DS token without shifting visual weight. Add when a concrete refactor needs it.
|
||||
- **`--signal-warning` / `--signal-info`** — not defined. No current consumer that couldn't go on using its local palette. Add when a real use case appears (e.g., a standardized warning toast variant).
|
||||
- **Green vs emerald reconciliation.** Approve buttons use `green-700/600`; soft success surfaces use `emerald-50/900`. `--signal-success` sourced from the button family. If a soft-variant lands, the emerald surfaces become the migration candidate — visible as a small hue shift on the toast success background. Document the shift when migrating.
|
||||
|
||||
## Integration layer (not drift)
|
||||
|
||||
### MDXEditor CSS-variable bridge
|
||||
|
||||
**Lines:** `ui/src/index.css:332–361`. **24 variables**, scoped to `.paperclip-mdxeditor-scope, .paperclip-mdxeditor`.
|
||||
|
||||
These are not tokens. They are an explicit bridge between Paperclip's DS tokens and MDXEditor's internal token vocabulary. Every one of the 24 values is a `var(--host-token)` reference or a `color-mix()` over host tokens — no hardcoded color values. Documenting the mapping for traceability:
|
||||
|
||||
| MDXEditor var | Maps to |
|
||||
|---|---|
|
||||
| `--baseBase` | `var(--background)` |
|
||||
| `--baseBg` | `transparent` |
|
||||
| `--baseBgSubtle` | `color-mix(in oklab, var(--accent) 35%, transparent)` |
|
||||
| `--baseLine` | `var(--border)` |
|
||||
| `--baseSolid` | `var(--muted-foreground)` |
|
||||
| `--baseSolidHover` | `var(--foreground)` |
|
||||
| `--baseText` | `var(--muted-foreground)` |
|
||||
| `--baseBorderColor` | `var(--border)` |
|
||||
| `--baseBorder` | `var(--border)` |
|
||||
| `--baseBorderHover` | `var(--ring)` |
|
||||
| `--baseTextContrast` | `var(--foreground)` |
|
||||
| `--baseTextContrastMuted` | `var(--muted-foreground)` |
|
||||
| `--baseTextEmphasis` | `var(--foreground)` |
|
||||
| `--basePageBg` | `var(--background)` |
|
||||
| `--baseRadius` | `var(--radius)` (the `:root`-scoped 0-value, not a `@theme` radius) |
|
||||
| `--baseLineHeight` | `1.5` (hardcoded numeric — the only non-bridge value in the block) |
|
||||
| `--accentBorder` | `color-mix(in oklab, var(--primary) 35%, var(--border))` |
|
||||
| `--accentSolid` | `var(--primary)` |
|
||||
| `--accentSolidHover` | `var(--primary)` |
|
||||
| `--accentLine` | `color-mix(in oklab, var(--primary) 20%, transparent)` |
|
||||
| `--accentBg` | `var(--accent)` |
|
||||
| `--accentBgHover` | `color-mix(in oklab, var(--accent) 80%, var(--background))` |
|
||||
| `--accentBgActive` | `color-mix(in oklab, var(--accent) 72%, var(--background))` |
|
||||
| `--accentText` | `var(--accent-foreground)` |
|
||||
|
||||
The bridge is sound. If DS tokens move, the editor moves with them. Nothing to do.
|
||||
|
||||
### Scrollbar oklch values
|
||||
|
||||
`index.css:172–219`. Hand-picked greys for light/dark scrollbar tracks and thumbs. Not tokens, but candidates for tokenization if scrollbar theming becomes a DS concern.
|
||||
765
doc/design-system/tokens/tokens.json
Normal file
765
doc/design-system/tokens/tokens.json
Normal file
@@ -0,0 +1,765 @@
|
||||
{
|
||||
"generated_at": "2026-04-21T00:00:00Z",
|
||||
"repo_sha": "a26e1288b627e82c554445732c7d844648e6b5e1",
|
||||
"scope": "ui/",
|
||||
"authoritative_source": "ui/src/index.css",
|
||||
"tailwind_version": "v4",
|
||||
"usage_count_method": "Sum of (a) Tailwind utility occurrences \\b(bg|text|border|ring|fill|stroke|from|to|via|outline|decoration|placeholder|caret|accent|shadow|divide)-<token>(?![\\w-]) and (b) literal var(--<token>) references, both EXCLUDING the definition file ui/src/index.css. Story coverage is deferred to Stage 2 (covered_by_story=null until then).",
|
||||
"tokens": [
|
||||
{
|
||||
"name": "background",
|
||||
"category": "color",
|
||||
"value": "oklch(1 0 0)",
|
||||
"dark_value": "oklch(0.145 0 0)",
|
||||
"defined_at": "ui/src/index.css:57 (light) / ui/src/index.css:96 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:7",
|
||||
"usage_count": 183,
|
||||
"used_in_components": 36,
|
||||
"used_in_pages": 20,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--background",
|
||||
"--color-background",
|
||||
"bg-background",
|
||||
"text-background",
|
||||
"border-background"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "foreground",
|
||||
"category": "color",
|
||||
"value": "oklch(0.145 0 0)",
|
||||
"dark_value": "oklch(0.985 0 0)",
|
||||
"defined_at": "ui/src/index.css:58 (light) / ui/src/index.css:97 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:8",
|
||||
"usage_count": 372,
|
||||
"used_in_components": 57,
|
||||
"used_in_pages": 31,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--foreground",
|
||||
"--color-foreground"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "card",
|
||||
"category": "color",
|
||||
"value": "oklch(1 0 0)",
|
||||
"dark_value": "oklch(0.205 0 0)",
|
||||
"defined_at": "ui/src/index.css:59 (light) / ui/src/index.css:98 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:9",
|
||||
"usage_count": 52,
|
||||
"used_in_components": 9,
|
||||
"used_in_pages": 17,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--card",
|
||||
"--color-card"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "card-foreground",
|
||||
"category": "color",
|
||||
"value": "oklch(0.145 0 0)",
|
||||
"dark_value": "oklch(0.985 0 0)",
|
||||
"defined_at": "ui/src/index.css:60 (light) / ui/src/index.css:99 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:10",
|
||||
"usage_count": 1,
|
||||
"used_in_components": 1,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--card-foreground",
|
||||
"--color-card-foreground"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "popover",
|
||||
"category": "color",
|
||||
"value": "oklch(1 0 0)",
|
||||
"dark_value": "oklch(0.205 0 0)",
|
||||
"defined_at": "ui/src/index.css:61 (light) / ui/src/index.css:100 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:11",
|
||||
"usage_count": 11,
|
||||
"used_in_components": 7,
|
||||
"used_in_pages": 2,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--popover",
|
||||
"--color-popover"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "popover-foreground",
|
||||
"category": "color",
|
||||
"value": "oklch(0.145 0 0)",
|
||||
"dark_value": "oklch(0.985 0 0)",
|
||||
"defined_at": "ui/src/index.css:62 (light) / ui/src/index.css:101 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:12",
|
||||
"usage_count": 5,
|
||||
"used_in_components": 4,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--popover-foreground",
|
||||
"--color-popover-foreground"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "primary",
|
||||
"category": "color",
|
||||
"value": "oklch(0.205 0 0)",
|
||||
"dark_value": "oklch(0.985 0 0)",
|
||||
"defined_at": "ui/src/index.css:63 (light) / ui/src/index.css:102 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:13",
|
||||
"usage_count": 35,
|
||||
"used_in_components": 19,
|
||||
"used_in_pages": 5,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--primary",
|
||||
"--color-primary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "primary-foreground",
|
||||
"category": "color",
|
||||
"value": "oklch(0.985 0 0)",
|
||||
"dark_value": "oklch(0.205 0 0)",
|
||||
"defined_at": "ui/src/index.css:64 (light) / ui/src/index.css:103 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:14",
|
||||
"usage_count": 10,
|
||||
"used_in_components": 8,
|
||||
"used_in_pages": 2,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--primary-foreground",
|
||||
"--color-primary-foreground"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "secondary",
|
||||
"category": "color",
|
||||
"value": "oklch(0.97 0 0)",
|
||||
"dark_value": "oklch(0.269 0 0)",
|
||||
"defined_at": "ui/src/index.css:65 (light) / ui/src/index.css:104 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:15",
|
||||
"usage_count": 3,
|
||||
"used_in_components": 3,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--secondary",
|
||||
"--color-secondary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "secondary-foreground",
|
||||
"category": "color",
|
||||
"value": "oklch(0.205 0 0)",
|
||||
"dark_value": "oklch(0.985 0 0)",
|
||||
"defined_at": "ui/src/index.css:66 (light) / ui/src/index.css:105 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:16",
|
||||
"usage_count": 2,
|
||||
"used_in_components": 2,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--secondary-foreground",
|
||||
"--color-secondary-foreground"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "muted",
|
||||
"category": "color",
|
||||
"value": "oklch(0.97 0 0)",
|
||||
"dark_value": "oklch(0.269 0 0)",
|
||||
"defined_at": "ui/src/index.css:67 (light) / ui/src/index.css:106 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:17",
|
||||
"usage_count": 90,
|
||||
"used_in_components": 31,
|
||||
"used_in_pages": 15,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--muted",
|
||||
"--color-muted"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "muted-foreground",
|
||||
"category": "color",
|
||||
"value": "oklch(0.556 0 0)",
|
||||
"dark_value": "oklch(0.708 0 0)",
|
||||
"defined_at": "ui/src/index.css:68 (light) / ui/src/index.css:107 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:18",
|
||||
"usage_count": 1540,
|
||||
"used_in_components": 98,
|
||||
"used_in_pages": 48,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--muted-foreground",
|
||||
"--color-muted-foreground"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "accent",
|
||||
"category": "color",
|
||||
"value": "oklch(0.97 0 0)",
|
||||
"dark_value": "oklch(0.269 0 0)",
|
||||
"defined_at": "ui/src/index.css:69 (light) / ui/src/index.css:108 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:19",
|
||||
"usage_count": 340,
|
||||
"used_in_components": 57,
|
||||
"used_in_pages": 21,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--accent",
|
||||
"--color-accent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "accent-foreground",
|
||||
"category": "color",
|
||||
"value": "oklch(0.205 0 0)",
|
||||
"dark_value": "oklch(0.985 0 0)",
|
||||
"defined_at": "ui/src/index.css:70 (light) / ui/src/index.css:109 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:20",
|
||||
"usage_count": 14,
|
||||
"used_in_components": 5,
|
||||
"used_in_pages": 1,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--accent-foreground",
|
||||
"--color-accent-foreground"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "destructive",
|
||||
"category": "color",
|
||||
"value": "oklch(0.577 0.245 27.325)",
|
||||
"dark_value": "oklch(0.637 0.237 25.331)",
|
||||
"defined_at": "ui/src/index.css:71 (light) / ui/src/index.css:110 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:21",
|
||||
"usage_count": 160,
|
||||
"used_in_components": 28,
|
||||
"used_in_pages": 43,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--destructive",
|
||||
"--color-destructive"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "destructive-foreground",
|
||||
"category": "color",
|
||||
"value": "oklch(0.985 0 0)",
|
||||
"dark_value": "oklch(0.985 0 0)",
|
||||
"defined_at": "ui/src/index.css:72 (light) / ui/src/index.css:111 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:22",
|
||||
"usage_count": 0,
|
||||
"used_in_components": 0,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--destructive-foreground",
|
||||
"--color-destructive-foreground"
|
||||
],
|
||||
"review_flag": null,
|
||||
"history": "Light-mode value corrected from oklch(0.577 0.245 27.325) (equalled --destructive) to oklch(0.985 0 0) on 2026-04-21."
|
||||
},
|
||||
{
|
||||
"name": "signal-success",
|
||||
"category": "color",
|
||||
"value": "oklch(0.527 0.154 150.069)",
|
||||
"dark_value": "oklch(0.627 0.194 149.214)",
|
||||
"defined_at": "ui/src/index.css:73 (light) / ui/src/index.css:112 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:23",
|
||||
"usage_count": 0,
|
||||
"used_in_components": 0,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--signal-success",
|
||||
"--color-signal-success"
|
||||
],
|
||||
"review_flag": null,
|
||||
"notes": "Added 2026-04-21. Action-severity token paired with --destructive. Sourced from the approve-button treatment (bg-green-700 / bg-green-600) in ApprovalCard, ApprovalDetail, Inbox. No call sites migrated yet."
|
||||
},
|
||||
{
|
||||
"name": "signal-success-foreground",
|
||||
"category": "color",
|
||||
"value": "oklch(0.985 0 0)",
|
||||
"dark_value": "oklch(0.985 0 0)",
|
||||
"defined_at": "ui/src/index.css:74 (light) / ui/src/index.css:113 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:24",
|
||||
"usage_count": 0,
|
||||
"used_in_components": 0,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--signal-success-foreground",
|
||||
"--color-signal-success-foreground"
|
||||
],
|
||||
"review_flag": null,
|
||||
"notes": "Added 2026-04-21. Text-on-signal-success-surface (white)."
|
||||
},
|
||||
{
|
||||
"name": "border",
|
||||
"category": "color",
|
||||
"value": "oklch(0.922 0 0)",
|
||||
"dark_value": "oklch(0.269 0 0)",
|
||||
"defined_at": "ui/src/index.css:75 (light) / ui/src/index.css:114 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:25",
|
||||
"usage_count": 701,
|
||||
"used_in_components": 72,
|
||||
"used_in_pages": 43,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--border",
|
||||
"--color-border"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "input",
|
||||
"category": "color",
|
||||
"value": "oklch(0.922 0 0)",
|
||||
"dark_value": "oklch(0.269 0 0)",
|
||||
"defined_at": "ui/src/index.css:76 (light) / ui/src/index.css:115 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:26",
|
||||
"usage_count": 8,
|
||||
"used_in_components": 6,
|
||||
"used_in_pages": 1,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--input",
|
||||
"--color-input"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ring",
|
||||
"category": "color",
|
||||
"value": "oklch(0.708 0 0)",
|
||||
"dark_value": "oklch(0.439 0 0)",
|
||||
"defined_at": "ui/src/index.css:77 (light) / ui/src/index.css:116 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:27",
|
||||
"usage_count": 26,
|
||||
"used_in_components": 17,
|
||||
"used_in_pages": 2,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--ring",
|
||||
"--color-ring"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "chart-1",
|
||||
"category": "color",
|
||||
"value": "oklch(0.646 0.222 41.116)",
|
||||
"dark_value": "oklch(0.488 0.243 264.376)",
|
||||
"defined_at": "ui/src/index.css:79 (light) / ui/src/index.css:118 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:32",
|
||||
"usage_count": 0,
|
||||
"used_in_components": 0,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--chart-1",
|
||||
"--color-chart-1"
|
||||
],
|
||||
"review_flag": "RESERVED \u2014 reserved for future chart tokenization"
|
||||
},
|
||||
{
|
||||
"name": "chart-2",
|
||||
"category": "color",
|
||||
"value": "oklch(0.6 0.118 184.704)",
|
||||
"dark_value": "oklch(0.696 0.17 162.48)",
|
||||
"defined_at": "ui/src/index.css:80 (light) / ui/src/index.css:119 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:33",
|
||||
"usage_count": 0,
|
||||
"used_in_components": 0,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--chart-2",
|
||||
"--color-chart-2"
|
||||
],
|
||||
"review_flag": "RESERVED \u2014 reserved for future chart tokenization"
|
||||
},
|
||||
{
|
||||
"name": "chart-3",
|
||||
"category": "color",
|
||||
"value": "oklch(0.398 0.07 227.392)",
|
||||
"dark_value": "oklch(0.769 0.188 70.08)",
|
||||
"defined_at": "ui/src/index.css:81 (light) / ui/src/index.css:120 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:34",
|
||||
"usage_count": 0,
|
||||
"used_in_components": 0,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--chart-3",
|
||||
"--color-chart-3"
|
||||
],
|
||||
"review_flag": "RESERVED \u2014 reserved for future chart tokenization"
|
||||
},
|
||||
{
|
||||
"name": "chart-4",
|
||||
"category": "color",
|
||||
"value": "oklch(0.828 0.189 84.429)",
|
||||
"dark_value": "oklch(0.627 0.265 303.9)",
|
||||
"defined_at": "ui/src/index.css:82 (light) / ui/src/index.css:121 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:35",
|
||||
"usage_count": 0,
|
||||
"used_in_components": 0,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--chart-4",
|
||||
"--color-chart-4"
|
||||
],
|
||||
"review_flag": "RESERVED \u2014 reserved for future chart tokenization"
|
||||
},
|
||||
{
|
||||
"name": "chart-5",
|
||||
"category": "color",
|
||||
"value": "oklch(0.769 0.188 70.08)",
|
||||
"dark_value": "oklch(0.645 0.246 16.439)",
|
||||
"defined_at": "ui/src/index.css:83 (light) / ui/src/index.css:122 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:36",
|
||||
"usage_count": 0,
|
||||
"used_in_components": 0,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--chart-5",
|
||||
"--color-chart-5"
|
||||
],
|
||||
"review_flag": "RESERVED \u2014 reserved for future chart tokenization"
|
||||
},
|
||||
{
|
||||
"name": "sidebar",
|
||||
"category": "color",
|
||||
"value": "oklch(0.985 0 0)",
|
||||
"dark_value": "oklch(0.145 0 0)",
|
||||
"defined_at": "ui/src/index.css:85 (light) / ui/src/index.css:124 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:40",
|
||||
"usage_count": 0,
|
||||
"used_in_components": 0,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--sidebar",
|
||||
"--color-sidebar"
|
||||
],
|
||||
"review_flag": "RESERVED \u2014 reserved for shadcn sidebar primitive compatibility"
|
||||
},
|
||||
{
|
||||
"name": "sidebar-foreground",
|
||||
"category": "color",
|
||||
"value": "oklch(0.145 0 0)",
|
||||
"dark_value": "oklch(0.985 0 0)",
|
||||
"defined_at": "ui/src/index.css:86 (light) / ui/src/index.css:125 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:41",
|
||||
"usage_count": 0,
|
||||
"used_in_components": 0,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--sidebar-foreground",
|
||||
"--color-sidebar-foreground"
|
||||
],
|
||||
"review_flag": "RESERVED \u2014 reserved for shadcn sidebar primitive compatibility"
|
||||
},
|
||||
{
|
||||
"name": "sidebar-primary",
|
||||
"category": "color",
|
||||
"value": "oklch(0.205 0 0)",
|
||||
"dark_value": "oklch(0.488 0.243 264.376)",
|
||||
"defined_at": "ui/src/index.css:87 (light) / ui/src/index.css:126 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:42",
|
||||
"usage_count": 0,
|
||||
"used_in_components": 0,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--sidebar-primary",
|
||||
"--color-sidebar-primary"
|
||||
],
|
||||
"review_flag": "RESERVED \u2014 reserved for shadcn sidebar primitive compatibility"
|
||||
},
|
||||
{
|
||||
"name": "sidebar-primary-foreground",
|
||||
"category": "color",
|
||||
"value": "oklch(0.985 0 0)",
|
||||
"dark_value": "oklch(0.985 0 0)",
|
||||
"defined_at": "ui/src/index.css:88 (light) / ui/src/index.css:127 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:43",
|
||||
"usage_count": 0,
|
||||
"used_in_components": 0,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--sidebar-primary-foreground",
|
||||
"--color-sidebar-primary-foreground"
|
||||
],
|
||||
"review_flag": "RESERVED \u2014 reserved for shadcn sidebar primitive compatibility"
|
||||
},
|
||||
{
|
||||
"name": "sidebar-accent",
|
||||
"category": "color",
|
||||
"value": "oklch(0.97 0 0)",
|
||||
"dark_value": "oklch(0.269 0 0)",
|
||||
"defined_at": "ui/src/index.css:89 (light) / ui/src/index.css:128 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:44",
|
||||
"usage_count": 0,
|
||||
"used_in_components": 0,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--sidebar-accent",
|
||||
"--color-sidebar-accent"
|
||||
],
|
||||
"review_flag": "RESERVED \u2014 reserved for shadcn sidebar primitive compatibility"
|
||||
},
|
||||
{
|
||||
"name": "sidebar-accent-foreground",
|
||||
"category": "color",
|
||||
"value": "oklch(0.205 0 0)",
|
||||
"dark_value": "oklch(0.985 0 0)",
|
||||
"defined_at": "ui/src/index.css:90 (light) / ui/src/index.css:129 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:45",
|
||||
"usage_count": 0,
|
||||
"used_in_components": 0,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--sidebar-accent-foreground",
|
||||
"--color-sidebar-accent-foreground"
|
||||
],
|
||||
"review_flag": "RESERVED \u2014 reserved for shadcn sidebar primitive compatibility"
|
||||
},
|
||||
{
|
||||
"name": "sidebar-border",
|
||||
"category": "color",
|
||||
"value": "oklch(0.922 0 0)",
|
||||
"dark_value": "oklch(0.269 0 0)",
|
||||
"defined_at": "ui/src/index.css:91 (light) / ui/src/index.css:130 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:46",
|
||||
"usage_count": 0,
|
||||
"used_in_components": 0,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--sidebar-border",
|
||||
"--color-sidebar-border"
|
||||
],
|
||||
"review_flag": "RESERVED \u2014 reserved for shadcn sidebar primitive compatibility"
|
||||
},
|
||||
{
|
||||
"name": "sidebar-ring",
|
||||
"category": "color",
|
||||
"value": "oklch(0.708 0 0)",
|
||||
"dark_value": "oklch(0.439 0 0)",
|
||||
"defined_at": "ui/src/index.css:92 (light) / ui/src/index.css:131 (dark)",
|
||||
"tailwind_alias_at": "ui/src/index.css:47",
|
||||
"usage_count": 0,
|
||||
"used_in_components": 0,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--sidebar-ring",
|
||||
"--color-sidebar-ring"
|
||||
],
|
||||
"review_flag": "RESERVED \u2014 reserved for shadcn sidebar primitive compatibility"
|
||||
},
|
||||
{
|
||||
"name": "radius",
|
||||
"category": "radius",
|
||||
"value": "0",
|
||||
"dark_value": null,
|
||||
"defined_at": "ui/src/index.css:56",
|
||||
"tailwind_alias_at": null,
|
||||
"scope": ":root (not in @theme)",
|
||||
"usage_count": 127,
|
||||
"used_in_components": 24,
|
||||
"used_in_pages": 11,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--radius"
|
||||
],
|
||||
"review_flag": null,
|
||||
"usage_note": "Tailwind `rounded` utility (no suffix) resolves via Tailwind defaults. This :root token is also consumed by index.css calc() expressions and the MDXEditor bridge --baseRadius."
|
||||
},
|
||||
{
|
||||
"name": "radius-sm",
|
||||
"category": "radius",
|
||||
"value": "0.375rem",
|
||||
"dark_value": null,
|
||||
"defined_at": "ui/src/index.css:48",
|
||||
"tailwind_alias_at": "ui/src/index.css:48",
|
||||
"scope": "@theme inline",
|
||||
"usage_count": 49,
|
||||
"used_in_components": 20,
|
||||
"used_in_pages": 5,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--radius-sm",
|
||||
"rounded-sm"
|
||||
],
|
||||
"review_flag": null
|
||||
},
|
||||
{
|
||||
"name": "radius-md",
|
||||
"category": "radius",
|
||||
"value": "0.5rem",
|
||||
"dark_value": null,
|
||||
"defined_at": "ui/src/index.css:49",
|
||||
"tailwind_alias_at": "ui/src/index.css:49",
|
||||
"scope": "@theme inline",
|
||||
"usage_count": 310,
|
||||
"used_in_components": 43,
|
||||
"used_in_pages": 31,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--radius-md",
|
||||
"rounded-md"
|
||||
],
|
||||
"review_flag": null
|
||||
},
|
||||
{
|
||||
"name": "radius-lg",
|
||||
"category": "radius",
|
||||
"value": "0.625rem",
|
||||
"dark_value": null,
|
||||
"defined_at": "ui/src/index.css:50",
|
||||
"tailwind_alias_at": "ui/src/index.css:50",
|
||||
"scope": "@theme inline",
|
||||
"usage_count": 1,
|
||||
"used_in_components": 1,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--radius-lg",
|
||||
"rounded-lg"
|
||||
],
|
||||
"review_flag": null,
|
||||
"history": "Value restored from 0px to 0.625rem (10px) on 2026-04-21 as part of the monotonic scale restore."
|
||||
},
|
||||
{
|
||||
"name": "radius-xl",
|
||||
"category": "radius",
|
||||
"value": "0.75rem",
|
||||
"dark_value": null,
|
||||
"defined_at": "ui/src/index.css:51",
|
||||
"tailwind_alias_at": "ui/src/index.css:51",
|
||||
"scope": "@theme inline",
|
||||
"usage_count": 0,
|
||||
"used_in_components": 0,
|
||||
"used_in_pages": 0,
|
||||
"covered_by_story": null,
|
||||
"aliases": [
|
||||
"--radius-xl",
|
||||
"rounded-xl"
|
||||
],
|
||||
"review_flag": null,
|
||||
"history": "Value restored from 0px to 0.75rem (12px) on 2026-04-21. No code consumers; opt-in for new surfaces."
|
||||
}
|
||||
],
|
||||
"tokens_by_category_count": {
|
||||
"color": 34,
|
||||
"radius": 5,
|
||||
"spacing": 0,
|
||||
"type": 0,
|
||||
"motion": 0,
|
||||
"elevation": 0
|
||||
},
|
||||
"excluded": {
|
||||
"mdxeditor_bridge": {
|
||||
"scope_selector": ".paperclip-mdxeditor-scope, .paperclip-mdxeditor",
|
||||
"line_range": "ui/src/index.css:332-361",
|
||||
"variable_count": 24,
|
||||
"reason": "Integration-layer aliases that map host DS tokens to MDXEditor internal token names. Every value is `var(--host-token)` or color-mix() over host tokens. Not authoritative DS tokens. Documented as a bridge in tokens-review.md, not as drift.",
|
||||
"bridge_mapping_sample": [
|
||||
{
|
||||
"mdx_var": "--baseBase",
|
||||
"maps_to": "var(--background)"
|
||||
},
|
||||
{
|
||||
"mdx_var": "--baseLine",
|
||||
"maps_to": "var(--border)"
|
||||
},
|
||||
{
|
||||
"mdx_var": "--baseSolid",
|
||||
"maps_to": "var(--muted-foreground)"
|
||||
},
|
||||
{
|
||||
"mdx_var": "--baseText",
|
||||
"maps_to": "var(--muted-foreground)"
|
||||
},
|
||||
{
|
||||
"mdx_var": "--baseBorderHover",
|
||||
"maps_to": "var(--ring)"
|
||||
},
|
||||
{
|
||||
"mdx_var": "--baseTextContrast",
|
||||
"maps_to": "var(--foreground)"
|
||||
},
|
||||
{
|
||||
"mdx_var": "--baseRadius",
|
||||
"maps_to": "var(--radius)"
|
||||
},
|
||||
{
|
||||
"mdx_var": "--accentSolid",
|
||||
"maps_to": "var(--primary)"
|
||||
},
|
||||
{
|
||||
"mdx_var": "--accentBg",
|
||||
"maps_to": "var(--accent)"
|
||||
},
|
||||
{
|
||||
"mdx_var": "--accentText",
|
||||
"maps_to": "var(--accent-foreground)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scrollbar_oklch": {
|
||||
"line_range": "ui/src/index.css:172-219",
|
||||
"reason": "Intentional scrollbar styling using raw oklch values (oklch(0.205 0 0), oklch(0.4 0 0), oklch(0.5 0 0), oklch(0.92 0 0), oklch(0.7 0 0), oklch(0.6 0 0)). Candidate for tokenization but currently out of scope."
|
||||
},
|
||||
"shimmer_component_vars": {
|
||||
"scope_selector": ".shimmer-text",
|
||||
"line_range": "ui/src/index.css:305-306",
|
||||
"reason": "Component-local CSS variables (--shimmer-base, --shimmer-highlight)."
|
||||
},
|
||||
"code_block_hex_palette": {
|
||||
"reason": "Hardcoded Catppuccin Mocha hex values for code-block theming (#1e1e2e, #cdd6f4, #181825, #585b70, #313244, #45475a, #89b4fa). Flagged as drift candidate in tokens-review.md; not extracted as tokens yet."
|
||||
}
|
||||
},
|
||||
"keyframes": [
|
||||
{
|
||||
"name": "dashboard-activity-enter",
|
||||
"defined_at": "ui/src/index.css:228",
|
||||
"used_by": ".activity-row-enter (520ms cubic-bezier(0.16, 1, 0.3, 1))"
|
||||
},
|
||||
{
|
||||
"name": "dashboard-activity-highlight",
|
||||
"defined_at": "ui/src/index.css:246",
|
||||
"used_by": ".activity-row-enter (920ms cubic-bezier(0.16, 1, 0.3, 1))"
|
||||
},
|
||||
{
|
||||
"name": "cot-line-slide-in",
|
||||
"defined_at": "ui/src/index.css:272",
|
||||
"used_by": ".cot-line-enter (300ms cubic-bezier(0.4, 0, 0.2, 1))"
|
||||
},
|
||||
{
|
||||
"name": "cot-line-slide-out",
|
||||
"defined_at": "ui/src/index.css:277",
|
||||
"used_by": ".cot-line-exit (300ms cubic-bezier(0.4, 0, 0.2, 1))"
|
||||
},
|
||||
{
|
||||
"name": "shimmer-text-slide",
|
||||
"defined_at": "ui/src/index.css:298",
|
||||
"used_by": ".shimmer-text (2.5s linear infinite)"
|
||||
}
|
||||
]
|
||||
}
|
||||
157
doc/design-system/tokens/tokens.md
Normal file
157
doc/design-system/tokens/tokens.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# Paperclip Design System — Tokens
|
||||
|
||||
- **Generated:** 2026-04-21
|
||||
- **Repo SHA:** a26e1288b627e82c554445732c7d844648e6b5e1
|
||||
- **Scope:** `ui/` (`@paperclipai/ui`)
|
||||
- **Authoritative source:** [`ui/src/index.css`](../../../ui/src/index.css)
|
||||
- **Review doc (drift & open questions):** [tokens-review.md](./tokens-review.md)
|
||||
|
||||
## Source & conventions
|
||||
|
||||
- **Tailwind v4** (CSS-first config). No `tailwind.config.*` file.
|
||||
- **shadcn/ui** (`new-york` style, `neutral` base, `cssVariables: true`, `iconLibrary: lucide`, non-RSC).
|
||||
- **Color space:** `oklch()` throughout.
|
||||
- **Theme scoping:** `:root` for light mode; `.dark` for dark overrides. Dark mode is opt-in via the `dark` class through the custom variant `@custom-variant dark (&:is(.dark *))`.
|
||||
- **Tailwind alias layer:** `@theme inline { --color-*: var(--<token>); }` exposes every semantic token as a Tailwind utility (`bg-background`, `text-foreground`, …). That's the mechanism — the `--<token>` at `:root` is authoritative.
|
||||
|
||||
## Token counts
|
||||
|
||||
| Category | Count | Notes |
|
||||
|------------|-------|-------|
|
||||
| Color | 34 | 19 semantic surfaces + 2 signal + 5 chart + 8 sidebar |
|
||||
| Radius | 5 | Scale under review — non-monotonic values. See [tokens-review.md](./tokens-review.md#radius-scale--under-founder-review). |
|
||||
| Spacing | 0 | Tailwind v4 defaults only. |
|
||||
| Type | 0 | Tailwind v4 defaults + `@tailwindcss/typography`. Markdown styling is hand-rolled via `.paperclip-markdown` / `.paperclip-mdxeditor-content` classes in `index.css`. |
|
||||
| Motion | 0 | No `--motion-*` / `--duration-*` variables. 5 named `@keyframes`; easing and duration expressed inline at use sites. |
|
||||
| Elevation | 0 | No `--shadow-*` tokens. Project uses borders + background shifts for elevation; occasional arbitrary `shadow-[…]` in UxLab / polished surfaces. |
|
||||
|
||||
## Color (34)
|
||||
|
||||
Each token has a light value (`:root`) and a dark value (`.dark`). Tailwind alias is `bg-<name>`, `text-<name>`, etc.
|
||||
|
||||
> **Separation of concerns.** `destructive` / `destructive-foreground` and `signal-success` / `signal-success-foreground` are **action-severity** tokens — used on buttons, toasts, and solid accent fills where the intent is "this click is destructive/successful." They are paired in `{base, -foreground}` form for solid-bg + contrasting-text use. They are **not** status-indicator colors for entity state (issue status, agent status, priority). Entity-state coloring lives in [`ui/src/lib/status-colors.ts`](../../../ui/src/lib/status-colors.ts) as a TypeScript catalog using raw Tailwind palette classes — deliberately separate from DS tokens. Tokenizing `status-colors.ts` as a `--status-*` family is a deferred future project; see [tokens-review.md §4](./tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds).
|
||||
|
||||
### Semantic surfaces (19)
|
||||
|
||||
| Token | Light | Dark | Uses |
|
||||
|---|---|---|---|
|
||||
| `background` | `oklch(1 0 0)` | `oklch(0.145 0 0)` | 183 |
|
||||
| `foreground` | `oklch(0.145 0 0)` | `oklch(0.985 0 0)` | 372 |
|
||||
| `card` | `oklch(1 0 0)` | `oklch(0.205 0 0)` | 52 |
|
||||
| `card-foreground` | `oklch(0.145 0 0)` | `oklch(0.985 0 0)` | 1 ⚠ |
|
||||
| `popover` | `oklch(1 0 0)` | `oklch(0.205 0 0)` | 11 |
|
||||
| `popover-foreground` | `oklch(0.145 0 0)` | `oklch(0.985 0 0)` | 5 |
|
||||
| `primary` | `oklch(0.205 0 0)` | `oklch(0.985 0 0)` | 35 |
|
||||
| `primary-foreground` | `oklch(0.985 0 0)` | `oklch(0.205 0 0)` | 10 |
|
||||
| `secondary` | `oklch(0.97 0 0)` | `oklch(0.269 0 0)` | 3 ⚠ |
|
||||
| `secondary-foreground` | `oklch(0.205 0 0)` | `oklch(0.985 0 0)` | 2 ⚠ |
|
||||
| `muted` | `oklch(0.97 0 0)` | `oklch(0.269 0 0)` | 90 |
|
||||
| `muted-foreground` | `oklch(0.556 0 0)` | `oklch(0.708 0 0)` | 1540 |
|
||||
| `accent` | `oklch(0.97 0 0)` | `oklch(0.269 0 0)` | 340 |
|
||||
| `accent-foreground` | `oklch(0.205 0 0)` | `oklch(0.985 0 0)` | 14 |
|
||||
| `destructive` | `oklch(0.577 0.245 27.325)` | `oklch(0.637 0.237 25.331)` | 160 |
|
||||
| `destructive-foreground` | `oklch(0.985 0 0)` | `oklch(0.985 0 0)` | 0 |
|
||||
| `border` | `oklch(0.922 0 0)` | `oklch(0.269 0 0)` | 701 |
|
||||
| `input` | `oklch(0.922 0 0)` | `oklch(0.269 0 0)` | 8 |
|
||||
| `ring` | `oklch(0.708 0 0)` | `oklch(0.439 0 0)` | 26 |
|
||||
|
||||
⚠ Flagged for review. See [tokens-review.md](./tokens-review.md).
|
||||
|
||||
### Signal (2)
|
||||
|
||||
Action-severity tokens paired with `destructive`. Intended for solid-accent fills on buttons, toasts, and confirmation surfaces where the action is semantically "success" (approve, confirm, ship).
|
||||
|
||||
| Token | Light | Dark | Uses |
|
||||
|---|---|---|---|
|
||||
| `signal-success` | `oklch(0.527 0.154 150.069)` | `oklch(0.627 0.194 149.214)` | 0 (new) |
|
||||
| `signal-success-foreground` | `oklch(0.985 0 0)` | `oklch(0.985 0 0)` | 0 (new) |
|
||||
|
||||
Sourced from the canonical approve-action button treatment (`bg-green-700 hover:bg-green-600 text-white` across `ApprovalCard`, `ApprovalDetail`, `Inbox`). Tailwind aliases are `bg-signal-success`, `text-signal-success-foreground`, etc. No call sites migrated yet — tokens land as primitives for opt-in adoption.
|
||||
|
||||
`--signal-warning` and `--signal-info` are intentionally **not** defined — defer until a real use case appears. See [tokens-review.md §Deferred variants](./tokens-review.md#deferred-signal-variants).
|
||||
|
||||
### Chart (5) — Reserved
|
||||
|
||||
> **Status: Reserved.** These five tokens are preserved in `ui/src/index.css` for the future chart-tokenization project. **Do not consume them today.** Current chart implementations (`ActivityCharts.tsx`, `OrgChart.tsx`) use hardcoded Tailwind-palette hex values directly; those call sites will migrate onto the chart tokens (or onto a `--status-*` family) in a separate future project. Keeping the tokens here is a deliberate placeholder. See [tokens-review.md §Chart tokens — reserved](./tokens-review.md#1-chart--tokens-are-dead).
|
||||
|
||||
| Token | Light | Dark | Status |
|
||||
|---|---|---|---|
|
||||
| `chart-1` | `oklch(0.646 0.222 41.116)` | `oklch(0.488 0.243 264.376)` | Reserved |
|
||||
| `chart-2` | `oklch(0.6 0.118 184.704)` | `oklch(0.696 0.17 162.48)` | Reserved |
|
||||
| `chart-3` | `oklch(0.398 0.07 227.392)` | `oklch(0.769 0.188 70.08)` | Reserved |
|
||||
| `chart-4` | `oklch(0.828 0.189 84.429)` | `oklch(0.627 0.265 303.9)` | Reserved |
|
||||
| `chart-5` | `oklch(0.769 0.188 70.08)` | `oklch(0.645 0.246 16.439)` | Reserved |
|
||||
|
||||
### Sidebar (8) — Reserved
|
||||
|
||||
> **Status: Reserved.** Preserved for shadcn `Sidebar` primitive compatibility in case that primitive is reintroduced. The current custom `Sidebar.tsx` consumes the semantic surface tokens (`background`, `foreground`, `accent`, `border`) directly. **Do not consume these tokens today** unless a sidebar variant that needs its own theming is explicitly being built. See [tokens-review.md §Sidebar tokens — reserved](./tokens-review.md#3-sidebar--tokens-are-dead).
|
||||
|
||||
| Token | Light | Dark | Status |
|
||||
|---|---|---|---|
|
||||
| `sidebar` | `oklch(0.985 0 0)` | `oklch(0.145 0 0)` | Reserved |
|
||||
| `sidebar-foreground` | `oklch(0.145 0 0)` | `oklch(0.985 0 0)` | Reserved |
|
||||
| `sidebar-primary` | `oklch(0.205 0 0)` | `oklch(0.488 0.243 264.376)` | Reserved |
|
||||
| `sidebar-primary-foreground` | `oklch(0.985 0 0)` | `oklch(0.985 0 0)` | Reserved |
|
||||
| `sidebar-accent` | `oklch(0.97 0 0)` | `oklch(0.269 0 0)` | Reserved |
|
||||
| `sidebar-accent-foreground` | `oklch(0.205 0 0)` | `oklch(0.985 0 0)` | Reserved |
|
||||
| `sidebar-border` | `oklch(0.922 0 0)` | `oklch(0.269 0 0)` | Reserved |
|
||||
| `sidebar-ring` | `oklch(0.708 0 0)` | `oklch(0.439 0 0)` | Reserved |
|
||||
|
||||
## Radius (5)
|
||||
|
||||
> **Scale resolved 2026-04-21.** Existing dashboard surfaces use `rounded-none` by intent (sharp, Swiss aesthetic) — 226 call sites in `ui/src/` migrated off `rounded-lg` / `rounded-xl`. The `lg` and `xl` values are restored to a monotonic scale for newer surfaces (dialogs, chat bubbles, card explorations) and for the one shadcn primitive that consumes them (`dialog.tsx`, which now renders with 10px-rounded corners).
|
||||
|
||||
| Token | Value | Scope | Maps to Tailwind | Uses (post-migration) |
|
||||
|---|---|---|---|---|
|
||||
| `--radius` | `0` | `:root` | `rounded` (no suffix, via default) | 127 |
|
||||
| `--radius-sm` | `0.375rem` (6px) | `@theme inline` | `rounded-sm` | 49 |
|
||||
| `--radius-md` | `0.5rem` (8px) | `@theme inline` | `rounded-md` | 310 |
|
||||
| `--radius-lg` | `0.625rem` (10px) | `@theme inline` | `rounded-lg` | 1 (dialog.tsx primitive) |
|
||||
| `--radius-xl` | `0.75rem` (12px) | `@theme inline` | `rounded-xl` | 0 (opt-in for new surfaces) |
|
||||
|
||||
`--radius` (`:root`, = 0) is the legacy shadcn base; it's consumed by the MDXEditor bridge (`--baseRadius`) and by a few `calc(var(--radius) ± Npx)` expressions inside `index.css`. The `@theme` scale is what Tailwind utilities resolve to.
|
||||
|
||||
### 18 arbitrary `rounded-[Npx]` exceptions — all intentional, preserved
|
||||
|
||||
None of the 18 values below match the new scale (6 / 8 / 10 / 12 px). Kept as-is per design intent. See [tokens-review.md §Radius workaround audit](./tokens-review.md#radius-workaround-audit-18-occurrences-all-retained) for the per-file disposition.
|
||||
|
||||
## Spacing (0)
|
||||
|
||||
No project-local spacing tokens. Uses the Tailwind v4 default scale (`p-1`, `gap-4`, etc., driven by the Tailwind-shipped `--spacing` base of 0.25rem).
|
||||
|
||||
## Type (0)
|
||||
|
||||
No project-local font-family, font-size, or line-height tokens. Typography sources:
|
||||
|
||||
- Tailwind v4 defaults (the `--text-*` family ships with `tailwindcss`).
|
||||
- `@tailwindcss/typography` plugin (prose).
|
||||
- Hand-authored rules on `.paperclip-markdown` and `.paperclip-mdxeditor-content` in `ui/src/index.css` (hardcoded `font-size`, `line-height`, `margin`). These are per-surface overrides — not tokens.
|
||||
- Code blocks use a hardcoded Catppuccin-Mocha palette in `index.css`. Flagged as drift candidate in [tokens-review.md — §Low-confidence drift](./tokens-review.md#low-confidence-drift-candidates-for-new-tokens).
|
||||
|
||||
## Motion (0)
|
||||
|
||||
No `--motion-*` or `--duration-*` variables. Motion is expressed as per-feature `@keyframes` with inline duration and `cubic-bezier()`.
|
||||
|
||||
**Keyframes defined in `ui/src/index.css`:**
|
||||
|
||||
| Name | Used by | Duration / easing |
|
||||
|---|---|---|
|
||||
| `dashboard-activity-enter` | `.activity-row-enter` | 520ms `cubic-bezier(0.16, 1, 0.3, 1)` |
|
||||
| `dashboard-activity-highlight` | `.activity-row-enter` | 920ms `cubic-bezier(0.16, 1, 0.3, 1)` |
|
||||
| `cot-line-slide-in` | `.cot-line-enter` | 300ms `cubic-bezier(0.4, 0, 0.2, 1)` |
|
||||
| `cot-line-slide-out` | `.cot-line-exit` | 300ms `cubic-bezier(0.4, 0, 0.2, 1)` |
|
||||
| `shimmer-text-slide` | `.shimmer-text` | 2.5s linear infinite |
|
||||
|
||||
Two easing curves recur (`cubic-bezier(0.16, 1, 0.3, 1)` and `cubic-bezier(0.4, 0, 0.2, 1)`). No duration pattern repeats. All animations honor `prefers-reduced-motion`.
|
||||
|
||||
## Elevation (0)
|
||||
|
||||
No `--shadow-*` tokens. The project's default visual elevation is border-based. Arbitrary `shadow-[…]` values appear in polished surfaces and UxLab prototypes; see [tokens-review.md — §Medium-confidence drift](./tokens-review.md#medium-confidence-drift).
|
||||
|
||||
## Out of scope / excluded from tokens.json
|
||||
|
||||
- **MDXEditor theme bridge** — 24 variables at `.paperclip-mdxeditor-scope, .paperclip-mdxeditor` (`index.css:332–361`). Every value is `var(--host-token)` or `color-mix(in oklab, var(--host-token) N%, …)` — a deliberate integration-layer alias, not a hardcoded theme. Documented as a bridge in [tokens-review.md — §Integration layer](./tokens-review.md#integration-layer-not-drift).
|
||||
- **Scrollbar oklch values** — `index.css:172–219`. Hand-picked greys for light/dark scrollbar track/thumb. Candidate for tokenization; not done yet.
|
||||
- **Component-local vars** — `.shimmer-text` (`--shimmer-base`, `--shimmer-highlight`) and `.paperclip-mermaid*` classes.
|
||||
- **Hardcoded chart hex palette** in `ActivityCharts.tsx` / `OrgChart.tsx` — treated as drift, not as tokens. See [tokens-review.md](./tokens-review.md).
|
||||
- **Raw Tailwind palette usage** across the codebase — treated as non-semantic drift. See [tokens-review.md — §Non-semantic color usage](./tokens-review.md#non-semantic-color-usage).
|
||||
@@ -248,6 +248,9 @@ export const StatusBadge = createSdkUiComponent<StatusBadgeProps>("StatusBadge")
|
||||
/**
|
||||
* Sortable, paginated data table.
|
||||
*
|
||||
* @status contract-only
|
||||
* Not yet implemented — will fail at runtime if rendered. See doc/design-system/components/index.md for roadmap status.
|
||||
*
|
||||
* @see PLUGIN_SPEC.md §19.6 — Shared Components
|
||||
*/
|
||||
export const DataTable = createSdkUiComponent<DataTableProps>("DataTable");
|
||||
@@ -255,6 +258,9 @@ export const DataTable = createSdkUiComponent<DataTableProps>("DataTable");
|
||||
/**
|
||||
* Line or bar chart for time-series data.
|
||||
*
|
||||
* @status contract-only
|
||||
* Not yet implemented — will fail at runtime if rendered. See doc/design-system/components/index.md for roadmap status.
|
||||
*
|
||||
* @see PLUGIN_SPEC.md §19.6 — Shared Components
|
||||
*/
|
||||
export const TimeseriesChart = createSdkUiComponent<TimeseriesChartProps>("TimeseriesChart");
|
||||
@@ -262,6 +268,9 @@ export const TimeseriesChart = createSdkUiComponent<TimeseriesChartProps>("Times
|
||||
/**
|
||||
* Renders Markdown text as HTML.
|
||||
*
|
||||
* @status contract-only
|
||||
* Not yet implemented — will fail at runtime if rendered. See doc/design-system/components/index.md for roadmap status.
|
||||
*
|
||||
* @see PLUGIN_SPEC.md §19.6 — Shared Components
|
||||
*/
|
||||
export const MarkdownBlock = createSdkUiComponent<MarkdownBlockProps>("MarkdownBlock");
|
||||
@@ -269,6 +278,9 @@ export const MarkdownBlock = createSdkUiComponent<MarkdownBlockProps>("MarkdownB
|
||||
/**
|
||||
* Renders a definition-list of label/value pairs.
|
||||
*
|
||||
* @status contract-only
|
||||
* Not yet implemented — will fail at runtime if rendered. See doc/design-system/components/index.md for roadmap status.
|
||||
*
|
||||
* @see PLUGIN_SPEC.md §19.6 — Shared Components
|
||||
*/
|
||||
export const KeyValueList = createSdkUiComponent<KeyValueListProps>("KeyValueList");
|
||||
@@ -276,6 +288,9 @@ export const KeyValueList = createSdkUiComponent<KeyValueListProps>("KeyValueLis
|
||||
/**
|
||||
* Row of action buttons wired to the plugin bridge's `performAction` handlers.
|
||||
*
|
||||
* @status contract-only
|
||||
* Not yet implemented — will fail at runtime if rendered. See doc/design-system/components/index.md for roadmap status.
|
||||
*
|
||||
* @see PLUGIN_SPEC.md §19.6 — Shared Components
|
||||
*/
|
||||
export const ActionBar = createSdkUiComponent<ActionBarProps>("ActionBar");
|
||||
@@ -283,6 +298,9 @@ export const ActionBar = createSdkUiComponent<ActionBarProps>("ActionBar");
|
||||
/**
|
||||
* Scrollable, timestamped log output viewer.
|
||||
*
|
||||
* @status contract-only
|
||||
* Not yet implemented — will fail at runtime if rendered. See doc/design-system/components/index.md for roadmap status.
|
||||
*
|
||||
* @see PLUGIN_SPEC.md §19.6 — Shared Components
|
||||
*/
|
||||
export const LogView = createSdkUiComponent<LogViewProps>("LogView");
|
||||
@@ -290,6 +308,9 @@ export const LogView = createSdkUiComponent<LogViewProps>("LogView");
|
||||
/**
|
||||
* Collapsible JSON tree for debugging or raw data inspection.
|
||||
*
|
||||
* @status contract-only
|
||||
* Not yet implemented — will fail at runtime if rendered. See doc/design-system/components/index.md for roadmap status.
|
||||
*
|
||||
* @see PLUGIN_SPEC.md §19.6 — Shared Components
|
||||
*/
|
||||
export const JsonTree = createSdkUiComponent<JsonTreeProps>("JsonTree");
|
||||
@@ -297,6 +318,9 @@ export const JsonTree = createSdkUiComponent<JsonTreeProps>("JsonTree");
|
||||
/**
|
||||
* Loading indicator.
|
||||
*
|
||||
* @status contract-only
|
||||
* Not yet implemented — will fail at runtime if rendered. See doc/design-system/components/index.md for roadmap status.
|
||||
*
|
||||
* @see PLUGIN_SPEC.md §19.6 — Shared Components
|
||||
*/
|
||||
export const Spinner = createSdkUiComponent<SpinnerProps>("Spinner");
|
||||
@@ -305,6 +329,9 @@ export const Spinner = createSdkUiComponent<SpinnerProps>("Spinner");
|
||||
* React error boundary that prevents plugin rendering errors from crashing
|
||||
* the host page.
|
||||
*
|
||||
* @status contract-only
|
||||
* Not yet implemented — will fail at runtime if rendered. See doc/design-system/components/index.md for roadmap status.
|
||||
*
|
||||
* @see PLUGIN_SPEC.md §19.7 — Error Propagation Through The Bridge
|
||||
*/
|
||||
export const ErrorBoundary = createSdkUiComponent<ErrorBoundaryProps>("ErrorBoundary");
|
||||
|
||||
88
projects/agent-profile-dashboard/brief.md
Normal file
88
projects/agent-profile-dashboard/brief.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Agent Profile Dashboard — Brief
|
||||
|
||||
## Purpose
|
||||
|
||||
Redesign the Dashboard tab of the Agent Profile page to give users clearer hierarchy and more efficient information parsing. This is Run A of a two-path experiment — Claude Code path. Run B (Paperclip agent teams) runs from this same brief and rubric.
|
||||
|
||||
## Operating principles
|
||||
|
||||
Both runs work from a shared set of UX operating principles documented in [`./operating-principles.md`](./operating-principles.md). These cover role definition, design lenses (cognition, Gestalt, usability heuristics, accessibility, etc.), the visual quality bar, the "reach for what exists first" DS discipline, and general working rules. Both runs apply these principles to the work; path-specific operational mechanics (how each path coordinates, reports, or executes internally) live outside the shared baseline.
|
||||
|
||||
## Current state
|
||||
|
||||
The Dashboard tab is rendered by `AgentOverview` (lines 1259–1335) and its inline helpers `LatestRunCard` (1170–1255) and `CostsSection` (1339–1413) inside `ui/src/pages/AgentDetail.tsx`. Effective footprint is ~245 lines across three inline functions.
|
||||
|
||||
It renders a vertically-stacked column: live/latest run card → 4-up "last 14 days" charts grid (Run Activity, Issues by Priority, Issues by Status, Success Rate) → Recent Issues list with up to 10 rows → Costs section with a KPI strip and a per-run cost table. The visual language is sharp-cornered, sparse typography, with live-run state communicated by a cyan pulse and soft glow on an otherwise neutral card.
|
||||
|
||||
See `./reference/` for screenshots of the current dashboard. Note that the full-page screenshots (`00-screenshot-idle.png`, `01-screenshot-live-run.png`) were captured at 50% zoom to fit content; real viewing scale is 2x denser per-element. Refer to close-ups (`02`–`04`) for accurate element sizing.
|
||||
|
||||
## Who uses this and when
|
||||
|
||||
The dashboard is viewed across four mental modes, **prioritized** as follows:
|
||||
|
||||
- **Monitoring (primary)** — a designer checking whether their website-builder agent is running and what task it's on; a teammate scanning an agent's in-flight work. Optimize for this mode first.
|
||||
- **Operations (secondary)** — a teammate wanting to manually override which task the agent works on next.
|
||||
- **Accountability (secondary)** — a manager checking how much a specific agent is spending on a specific task; an engineer watching token consumption.
|
||||
- **Debug (secondary)** — an engineer investigating when and why a task failed.
|
||||
|
||||
Secondary modes must be served without cluttering the primary mode. When modes conflict, monitoring wins.
|
||||
|
||||
The dashboard is a **lens** into agent state — not the canonical home for any of the data it shows. Users go to other surfaces (run detail, issues list, task workspace) for deep work. The dashboard's job is to surface what matters right now and route to detail elsewhere.
|
||||
|
||||
## Design goal
|
||||
|
||||
**A user in monitoring mode should be able to answer "is this agent healthy and what is it doing right now" without scrolling, on a 1440px-wide screen.**
|
||||
|
||||
That's the design's first obligation. Everything else — operations controls, spend visibility, debug affordances — must be present and reachable but must not interfere with this goal.
|
||||
|
||||
## Required metrics
|
||||
|
||||
The dashboard must surface both (a) **budget position** — the agent's current standing against its budget (observed vs. allowed, utilization, remaining), and (b) **session burn rate** — recent cost activity against the current session (cumulative cost and per-run cost over the recent window).
|
||||
|
||||
Budget position must fit in the monitoring-no-scrolling frame (it's the signal the primary-goal test is evaluated against). Session burn rate must also be present on the dashboard but may live in a secondary surface (for example, integrated with the costs section) rather than the top-of-page summary.
|
||||
|
||||
## Problems to solve
|
||||
|
||||
1. **No clear hierarchy.** Everything on the page reads with roughly equal weight. Users can't tell at a glance what's most important.
|
||||
2. **Uneven chart value.** The dashboard shows four charts ("last 14 days" Run Activity, Issues by Priority, Issues by Status, Success Rate). Their value to users varies — each should either earn its place against specific user decisions or be consolidated. The current layout treats all four as equally important, which obscures the ones that do real work.
|
||||
3. **Cost/token split is awkward.** Two adjacent tables (costs KPI strip + per-run costs) split what's probably one coherent accountability surface into two.
|
||||
|
||||
## Design direction
|
||||
|
||||
Favor hierarchy over comprehensiveness. Combining, merging, or consolidating modules is encouraged where it serves the monitoring goal. All current data must remain reachable — a chart can be merged, filtered, or tucked behind a secondary view, but no user can be left unable to find information they need for their mode. "Reachable" means within two interactions (click, expand, filter) from the default dashboard view.
|
||||
|
||||
## Scope — in
|
||||
|
||||
Affirmative scope — what this redesign must do:
|
||||
|
||||
- Redesign the content and structure of `AgentOverview`, `LatestRunCard`, and `CostsSection` inside `ui/src/pages/AgentDetail.tsx` (approximately lines 1170–1413)
|
||||
- **Preserve visual distinction for live-running agents.** The current cyan-pulse treatment on the Live Run card is the load-bearing signal for "this agent is alive right now." Replacing it requires an equally clear alternative signal.
|
||||
- **Surface two named action affordances on the dashboard:**
|
||||
- **Change task priority** — drag-and-drop between priority buckets or explicit priority selector, scoped to the top 5–10 in-flight tasks. Short note: the backend does not support arbitrary reordering, so the operations use case is served by priority elevation rather than position. Priority is a closed 4-value enum (`critical | high | medium | low`), so this is a promote/demote action across four buckets, not free ordering.
|
||||
- **Navigate to run/task/issue detail** — preserve current link-based navigation via cards and rows unless a better affordance is clearly warranted.
|
||||
- **Pause agent is out of scope for this redesign.** Pause is handled by the existing page-chrome control (`PauseResumeButton` in the page header), which already satisfies the single-click/reversible/no-confirmation semantics. It is not duplicated on the dashboard.
|
||||
- **Keep all current data reachable.** Merging and reorganizing is encouraged; omission is not.
|
||||
|
||||
## Scope — out
|
||||
|
||||
What this redesign does not touch:
|
||||
|
||||
- **No extraction or refactoring** of `AgentOverview` / `LatestRunCard` / `CostsSection` into separate files. Work within the inline structure.
|
||||
- **No chart tokenization.** Use hardcoded hex for chart colors, matching current behavior.
|
||||
- **No other profile tabs.** Dashboard tab only.
|
||||
- **No destructive actions** (terminate, delete, archive) on the dashboard.
|
||||
- **No approve/reject flows** on the dashboard.
|
||||
- **No changes to `status-colors.ts`.**
|
||||
- **No new tokens proposed mid-experiment.** Use only tokens shipped in Step 0.
|
||||
- **No merging of duplicate component families** flagged in `doc/design-system/components/components-review.md`. Use what exists.
|
||||
|
||||
## Design system policy
|
||||
|
||||
- **Sharp corners** (`rounded-none`) for dashboard surfaces. `rounded-lg` (0.625rem) and `rounded-xl` (0.75rem) are available for opt-in rounded surfaces if genuinely warranted (e.g., a chat-like element), but the dashboard aesthetic is sharp.
|
||||
- **`--signal-success` / `--signal-success-foreground`** for approve/confirm actions if they appear.
|
||||
- **`destructive` / `destructive-foreground`** is reserved for terminate/reject — but those are out of scope for the dashboard, so this is unlikely to apply.
|
||||
- **`status-colors.ts`** is the source of truth for entity state colors. Use as-is.
|
||||
|
||||
## Deliverables
|
||||
|
||||
A working redesigned Agent Profile Dashboard rendered at `/agents/[agentId]` default view, plus any necessary changes to the three in-scope inline functions. No new files, no component extraction, no tests of what already worked. The redesign should be reviewable against the rubric.
|
||||
70
projects/agent-profile-dashboard/operating-principles.md
Normal file
70
projects/agent-profile-dashboard/operating-principles.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# UX Designer — Operating Principles
|
||||
|
||||
Operating manual for UX design work. Both Run A (Claude Code path) and Run B (Paperclip path) work from these principles on the Agent Profile Dashboard redesign. Run-specific operational instructions (heartbeat, reporting chain, agent identity) are not included here — those belong in each run's execution environment.
|
||||
|
||||
## Role
|
||||
|
||||
Own end-to-end UX quality on work assigned to you. Translate product intent into user flows, IA, and interaction specs. Identify usability risks early and propose concrete alternatives — don't just flag problems. Evolve the design system coherently with accessibility as a first-class constraint. Partner with the product owner and engineers to ship polished, testable experiences.
|
||||
|
||||
## Design lenses
|
||||
|
||||
Apply these when evaluating or producing designs. Cite by name in comments, notes, or review docs so reasoning is traceable.
|
||||
|
||||
**Cognition & perception** — Cognitive Load, Working Memory, Miller's Law (7±2), Selective Attention, Chunking, Mental Models, Flow, Aesthetic-Usability Effect, Cognitive Bias.
|
||||
|
||||
**Gestalt** — Proximity, Similarity, Common Region, Uniform Connectedness, Prägnanz.
|
||||
|
||||
**Decision & attention** — Hick's Law, Choice Overload, Fitts's Law, Serial Position, Von Restorff, Peak-End Rule, Zeigarnik, Goal-Gradient.
|
||||
|
||||
**System & interaction** — Doherty Threshold (<400ms), Jakob's Law, Tesler's Law, Postel's Law, Occam's Razor, Pareto (80/20), Parkinson's Law, Paradox of the Active User.
|
||||
|
||||
**Usability heuristics** — Nielsen's 10, Shneiderman's 8 Golden Rules, Norman's principles (affordances, signifiers, feedback, mapping, constraints, conceptual models), Progressive Disclosure, Recognition over Recall.
|
||||
|
||||
**Behavioral science** — Loss Aversion, Anchoring, Social Proof, Endowment, Defaults, Framing, Commitment & Consistency, Reciprocity, Sunk Cost.
|
||||
|
||||
**Accessibility** — WCAG POUR, Inclusive Design (curb-cut effect), color contrast, color-independence, motor/cognitive accessibility (target size, timeouts, reading level, reduced motion).
|
||||
|
||||
**IA & content** — Information Scent, mental models of IA, F-pattern / Z-pattern scanning, Inverted Pyramid, Plain Language.
|
||||
|
||||
**Forms & errors** — Forgiveness (undo, confirm destructive, recover), inline validation, input masking, single-column layout.
|
||||
|
||||
**Motion & perceived performance** — purposeful animation (easing, duration, causality), ~100ms feedback loops, skeletons / optimistic UI / progress indicators.
|
||||
|
||||
**Emotional & trust** — trust signals, Norman's 3 levels (visceral, behavioral, reflective), Kano Model (must-have, performance, delighter).
|
||||
|
||||
**Research** — Jobs-to-Be-Done, 5 Whys, think-aloud protocol, severity ratings.
|
||||
|
||||
**Ethics** — Recognize and refuse dark patterns (roach motel, confirmshaming, sneak-into-basket, bait-and-switch). Distinguish persuasion from manipulation. Flag engagement metrics that conflict with user wellbeing.
|
||||
|
||||
**Platform & context** — mobile thumb zones, responsive principles (content-driven breakpoints), platform conventions (iOS HIG, Material).
|
||||
|
||||
## Visual quality bar
|
||||
|
||||
A functional UI is not a finished UI. If the layout looks unstyled, cramped, misaligned, or "programmer default," the work is not done — regardless of whether it technically works. Apply the same rigor to visual craft as to flows and IA.
|
||||
|
||||
- **Hierarchy is visible.** A stranger should be able to tell in two seconds what's primary, secondary, and tertiary on any screen. If everything has the same weight, nothing is emphasized.
|
||||
- **Spacing is intentional.** Use the spacing scale. No stray 7px gaps, no elements touching edges, no content crammed against siblings. Whitespace is a design element, not leftover canvas.
|
||||
- **Alignment is ruthless.** Everything aligns to a grid, a baseline, or a shared edge. Nothing floats.
|
||||
- **Type has a system.** Sizes, weights, and line-heights come from the scale — not picked per-component. Two weights, three sizes, usually enough.
|
||||
- **Density matches context.** Dashboards can be dense; marketing can breathe; forms need room. Don't ship a dashboard that looks like a landing page or a landing page that looks like a spreadsheet.
|
||||
- **Polish the defaults.** Empty states, loading states, error states, and edge cases get the same care as the happy path. A beautiful happy path with a broken empty state is a broken product.
|
||||
|
||||
If a screen looks like raw HTML, call it out and fix it — don't ship it because the flow is correct.
|
||||
|
||||
## Reach for what exists first
|
||||
|
||||
There is a design system. Before proposing anything new:
|
||||
|
||||
1. **Check the token set.** Colors, spacing, type, radii, shadows, motion — all come from tokens. Never introduce a one-off value. If the token you need doesn't exist, propose it as a system change, don't inline it.
|
||||
2. **Check the component library.** If a pattern already exists (button, modal, table, empty state, form field, toast…), use it. "Almost the same but slightly different" is the enemy — either the existing component fits, or it should be extended, or there's a genuine case for a new one. In that order.
|
||||
3. **Specify in terms of what exists.** In handoff to engineers, name the components and tokens explicitly: "use `<Modal size=\"md\">` with `space-4` padding and `text-secondary` for the helper copy" — not "make a popup that's kinda medium-sized." This is the difference between a spec and a wish.
|
||||
4. **Propose system changes deliberately.** If a new component or token is genuinely needed, call it out as a system-level proposal with rationale and where else it could be reused. Don't quietly invent.
|
||||
|
||||
The design system is the shortest path to a coherent product. Divergence should be a choice, not an accident.
|
||||
|
||||
## Working rules
|
||||
|
||||
- **Scope.** Work only on tasks explicitly in scope for the current project. Flag out-of-scope issues as findings; don't silently expand.
|
||||
- **Document reasoning.** Every significant decision gets a note — never update status silently. Include rationale, tradeoffs, and acceptance criteria.
|
||||
- **Keep work moving.** Don't let work stall. Surface blockers with specific asks rather than waiting.
|
||||
- **Done means done.** On completion, post a summary: what changed, tradeoffs made, residual risks, and acceptance criteria met.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 714 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 712 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 134 KiB |
17
projects/agent-profile-dashboard/reference/README.md
Normal file
17
projects/agent-profile-dashboard/reference/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Reference screenshots — current Agent Profile Dashboard
|
||||
|
||||
Captured from the Paperclip app in light mode at desktop width.
|
||||
|
||||
## Files
|
||||
|
||||
- `00-screenshot-idle.png` — Full dashboard, idle agent. **Captured at 50% zoom** to fit all content in a single image.
|
||||
- `01-screenshot-live-run.png` — Full dashboard, live-running agent (cyan pulse on Live Run card). **Captured at 50% zoom**.
|
||||
- `02-screenshot-latest-run.png` — Close-up of the Latest Run card, 100% zoom.
|
||||
- `03-screenshot-charts-grid.png` — Close-up of the 4-up charts grid, 100% zoom.
|
||||
- `04-screenshot-costs-section.png` — Close-up of the Costs section, 100% zoom.
|
||||
|
||||
## Important note for redesign runs
|
||||
|
||||
The two full-page screenshots (`00`, `01`) are zoomed out. **Do not use them as a fidelity reference for density or element sizing** — real viewing scale is 2x denser per-element than these images suggest. Use the close-up screenshots (`02`–`04`) for accurate visual scale.
|
||||
|
||||
The rubric's "no scrolling on 1440px" test refers to real-world viewing at 100%, not the compressed screenshot view. A design that "fits" in the zoomed-out view may not actually fit when rendered at real scale.
|
||||
107
projects/agent-profile-dashboard/rubric.md
Normal file
107
projects/agent-profile-dashboard/rubric.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Agent Profile Dashboard — Rubric
|
||||
|
||||
## How to use this rubric
|
||||
|
||||
This rubric evaluates both Run A (Claude Code) and Run B (Paperclip agents) against the brief. Each run should be evaluated independently, then compared side-by-side in the comparison writeup.
|
||||
|
||||
Pass/fail items are binary — the condition is either met or not met. DS compliance items use **"pass with explicit justification"** — a run that deviates but documents its reasoning passes; a run that deviates silently fails. Qualitative items are scored 1–5, with 3 meaning "competent" and 5 meaning "notably excellent."
|
||||
|
||||
For each item, capture a one-sentence note with the reasoning. The notes matter more than the scores.
|
||||
|
||||
**Definition of "interaction":** one interaction = one click, one keypress, or one significant mouse action (drag, hover-activated menu open). Scrolls and passive hovers do not count.
|
||||
|
||||
## Section 1 — Primary goal
|
||||
|
||||
The load-bearing test. Everything else is secondary.
|
||||
|
||||
- [ ] **Monitoring test.** On a 1440px-wide screen with no scrolling, the design communicates all four of the following:
|
||||
1. **Activity state** — whether the agent is currently running, idle, paused, or in an error state
|
||||
2. **Current work** — the task, run, or issue the agent is engaged with right now, if any
|
||||
3. **Recent health** — outcomes of recent runs (succeeded, failed, mixed). Specific count open to the run.
|
||||
4. **Live economics — budget position** (the agent's current standing against its budget) is visible in the monitoring frame. Session burn rate may be present but does not itself satisfy this requirement unless budget position is also shown.
|
||||
|
||||
Pass if all four are visible without scrolling. Fail if any one is missing or requires scrolling to locate.
|
||||
|
||||
## Section 2 — Mode coverage
|
||||
|
||||
Each secondary mode must be *served*, not just presented. Test the quality of support, not just the presence of information.
|
||||
|
||||
- [ ] **Operations.** A teammate can change the priority of the agent's in-flight tasks from the dashboard, without leaving the page. Pass/fail.
|
||||
- [ ] **Accountability (spend).** A manager can identify which of the agent's tasks are most expensive — not just total spend, but enough granularity to spot outliers. Pass/fail.
|
||||
- [ ] **Accountability (tokens).** An engineer can identify which runs burned the most tokens, with enough context to investigate (run identifier, timestamp, which task). Pass/fail.
|
||||
- [ ] **Debug.** An engineer can identify failed tasks, see when each one failed, and navigate to the failed run within one interaction from the dashboard. Pass/fail.
|
||||
|
||||
## Section 3 — Action affordances
|
||||
|
||||
The two named actions from the brief, scoped as specified.
|
||||
|
||||
- [ ] **Change task priority.** Drag-and-drop between priority buckets or explicit priority selector, scoped to the top 5–10 in-flight tasks. Pass/fail.
|
||||
- [ ] **Navigate to detail.** Link-based navigation to run/task/issue detail works. Pass/fail. (If the run introduced a different affordance, pass if it works and is at least as fast as the current link approach.)
|
||||
|
||||
## Section 4 — Data preservation
|
||||
|
||||
No user should be left unable to find the information they need for their mode.
|
||||
|
||||
- [ ] **Chart data reachable.** All four chart data surfaces (Run Activity, Issues by Priority, Issues by Status, Success Rate) are reachable within two interactions from the default view. Pass/fail.
|
||||
- [ ] **Cost + token data both present.** Cost totals and token consumption are both visible on the dashboard — not one or the other. Pass/fail.
|
||||
- [ ] **Recent issues reachable.** The list of recent issues assigned to this agent is accessible within two interactions. Pass/fail.
|
||||
|
||||
## Section 5 — DS compliance (pass with explicit justification)
|
||||
|
||||
A run that deviates and documents its reasoning passes. A run that deviates silently fails. Check `./run-notes.md` (or equivalent) for justifications.
|
||||
|
||||
- [ ] **Rounded corners.** The Paperclip dashboard aesthetic is sharp — `rounded-none` is the default. Runs may use `rounded-lg` / `rounded-xl` / `rounded-md` on specific surfaces if justified (e.g., "used `rounded-lg` on the run transcript card because chat-like surfaces read better with rounded corners"). Silent use of rounded corners without justification fails the item.
|
||||
- [ ] **No new tokens.** The run did not add any new DS tokens. If the run felt a token was needed, it used existing tokens or flagged the gap without adding it.
|
||||
- [ ] **No component extraction.** `AgentOverview` / `LatestRunCard` / `CostsSection` remain as inline functions inside `AgentDetail.tsx`. No new files created for these components.
|
||||
- [ ] **No chart tokenization.** Chart colors remain hardcoded hex. No `--chart-*` token consumption.
|
||||
- [ ] **No raw-palette drift.** The run did not introduce new raw-palette Tailwind classes (`bg-slate-500`, `text-zinc-700`, etc.) beyond what already existed in the dashboard region. Reusing existing raw-palette instances is okay; adding new ones isn't, unless flagged and justified.
|
||||
- [ ] **Pause uses neutral styling.** No amber, orange, or destructive treatment on the pause-agent action.
|
||||
- [ ] **Live-run visual distinction preserved.** The live-running state is clearly distinct from the non-running state in the redesigned dashboard. The specific mechanism (cyan pulse, different treatment, motion, typography) is open — but the distinction must exist.
|
||||
- [ ] **No out-of-scope changes.** No edits to other profile tabs, other pages, `status-colors.ts`, the DS token files, or the plugin SDK.
|
||||
|
||||
## Section 6 — Qualitative (1–5)
|
||||
|
||||
Four axes where judgment matters. 3 = competent. 5 = notably excellent. 1 = fails to meet the minimum.
|
||||
|
||||
- [ ] **Improvement over current state.** Compared to the current dashboard (see `./reference/`), is this design an improvement for the primary monitoring mode?
|
||||
- _1: regression — worse than today. 3: roughly equivalent. 5: clearly and materially better._
|
||||
- This is the most important qualitative axis. A design that complies with every other section but doesn't improve on the current dashboard is a failure of the experiment's purpose.
|
||||
|
||||
- [ ] **Hierarchy clarity.** Does the design have a clear primary, secondary, and tertiary layer? Can a user tell at a glance what's most important?
|
||||
- _1: no hierarchy, everything equal weight. 3: clear primary, rest undifferentiated. 5: three layers distinct, each earning its place._
|
||||
|
||||
- [ ] **Visual restraint.** Does the design match Paperclip's Swiss-minimal aesthetic? Sharp, sparse, intentional. Density and decorative judgment are part of this axis.
|
||||
- _1: cluttered or decorative; aesthetic mismatch. 3: neutral, doesn't violate the aesthetic. 5: actively reinforces the aesthetic with considered restraint._
|
||||
|
||||
- [ ] **Live-run signal strength.** How clearly does the design communicate "this agent is alive right now" when applicable?
|
||||
- _1: live state is indistinguishable from non-live. 3: distinct but subtle. 5: unmistakable without being obnoxious._
|
||||
|
||||
## Section 7 — Friction log (not scored)
|
||||
|
||||
Capture observations from running the experiment. These don't affect pass/fail but inform the comparison writeup.
|
||||
|
||||
- What did the run get stuck on? Any scope pressure or ambiguity?
|
||||
- What decisions got made implicitly vs. explicitly?
|
||||
- What was the run's biggest interpretation of the brief? (E.g., how did it weight monitoring vs. secondary modes?)
|
||||
- **DS awareness.** Did the run surface DS gaps, flag tensions, or make implicit DS decisions explicit in run-notes? (E.g., "pause wanted amber," "considered extracting AgentOverview but didn't because of scope," "flagged that Issues-by-Priority chart adds little value.")
|
||||
- Did the run consider mobile implications? If so, what did it decide or avoid? If not, is there anything in the redesign that would clearly degrade on mobile? (Mobile is out of scope for this experiment — this question is about awareness, not deliverables.)
|
||||
- **Rubric experience.** Was the rubric easy or hard to apply to this run? Any items that felt unclear, or any qualities of the design the rubric didn't capture well?
|
||||
- What would you add to the brief if running this again?
|
||||
|
||||
---
|
||||
|
||||
## Scoring summary template
|
||||
|
||||
When evaluating each run, produce this summary at the top of the evaluation:
|
||||
|
||||
```
|
||||
Section 1 (Primary goal): PASS / FAIL
|
||||
Section 2 (Mode coverage): X / 4 passed
|
||||
Section 3 (Actions): X / 2 passed
|
||||
Section 4 (Data): X / 3 passed
|
||||
Section 5 (DS compliance): X / 8 passed (with justifications noted)
|
||||
Section 6 (Qualitative): Improvement: X/5 | Hierarchy: X/5 | Restraint: X/5 | Live-run: X/5
|
||||
Section 7: See notes
|
||||
```
|
||||
|
||||
A run that fails Section 1 is a failed redesign regardless of how it scored elsewhere. A run that passes Section 1 but scores 1–2 on "Improvement over current state" (Section 6) is also a failed redesign — it met the letter of the rubric but missed its purpose. Sections 2–5 determine whether the run is complete. Section 6 determines whether the run is excellent.
|
||||
282
projects/agent-profile-dashboard/run-a-concept.md
Normal file
282
projects/agent-profile-dashboard/run-a-concept.md
Normal file
@@ -0,0 +1,282 @@
|
||||
# Run A — Concept
|
||||
|
||||
Paper design for the Agent Profile Dashboard redesign. Answers the seven plan questions plus the three additional items. No code.
|
||||
|
||||
Grounded in:
|
||||
- Brief (`brief.md`) and rubric (`rubric.md`) at baseline `0f5895e4`.
|
||||
- Discovery (`run-a-discovery.md`) — particularly the data-model facts about `AgentTaskSession`, `runtimeState`, `budgetOverview`, and the lack of a reorder endpoint.
|
||||
- Operating principles (`operating-principles.md`) — cited by name where they drove a call.
|
||||
|
||||
---
|
||||
|
||||
## The shape of the redesign
|
||||
|
||||
Two-zone hero band, one merged chart, one in-flight-tasks list, one unified costs module. Four elements stacked vertically; the hero is the load-bearing one.
|
||||
|
||||
```
|
||||
┌── Hero band ───────────────────────────────────────────────────────────────┐
|
||||
│ [Left zone — "what's happening now"] │ [Right zone — "how are we"] │
|
||||
│ Activity state • Current work card │ Budget position │
|
||||
│ │ Recent runs (7 dots) │
|
||||
└────────────────────────────────────────────────────────────────────────────┘
|
||||
┌── Activity over time ──────────────────────────────────────────────────────┐
|
||||
│ 14-day run activity + success rate (merged) │
|
||||
└────────────────────────────────────────────────────────────────────────────┘
|
||||
┌── In-flight tasks ─────────────────────────────────────────────────────────┐
|
||||
│ Up to 7 rows. Each row: priority icon (clickable) + title + status + link │
|
||||
└────────────────────────────────────────────────────────────────────────────┘
|
||||
┌── Costs ───────────────────────────────────────────────────────────────────┐
|
||||
│ Totals strip (token + cost) → per-run table (date, run, tokens, cost) │
|
||||
└────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Hero is the monitoring frame. Everything below is for secondary modes.
|
||||
|
||||
---
|
||||
|
||||
## 1. Hero frame — two-zone split
|
||||
|
||||
Of the three candidates from the plan (dense horizontal strip, morphing single card, left/right split), I'm committing to the **left/right split** ("now" | "how are we doing"). Rough widths on 1440px minus left-rail chrome (~220–240px): content width ~1180–1200px, split 55/45 for left/right — ~650px / ~530px. Both zones sit in one `rounded-none` bordered container or two adjacent bordered containers (decision in Phase 3a).
|
||||
|
||||
**Why this over the alternatives:**
|
||||
|
||||
- **Single dense horizontal strip:** would squeeze four signals into one horizontal band. Violates *Miller's Law* the moment we add any secondary metadata (e.g., current task title, budget amount label) — forces the user to parse four distinct cells horizontally with poor visual distance between them.
|
||||
- **Morphing single card:** elegant but pays a heavy *Mental Models* tax — user has to learn that the same card means different things in different states. Also concentrates all four Section-1 signals in one card, which makes layout brittle.
|
||||
- **Two-zone split:** applies *Common Region* (Gestalt) cleanly — "now" and "how we're doing" are cognitively separate questions. Gives each zone room for secondary metadata. Degrades gracefully on narrower viewports (zones can stack). F-pattern scanning lands eyes on the top-left first (activity state + current work), which is the most time-sensitive information.
|
||||
|
||||
**Left zone (~650px):**
|
||||
- Activity state — a compact row with the agent's state: `● Running` (cyan pulse), `○ Idle`, `⏸ Paused`, `⚠ Error`. Lives as a pill at the top of the zone. Uses `agent.status` as the source; see §6 for signal treatment.
|
||||
- Current work — a one-line summary directly under activity state: "On `SKI-142`: Add budget alerts to sidebar" when live; "Last run: `SKI-139` succeeded 2h ago" when idle. Preserves the `Link` navigation from current `LatestRunCard`. Dense, one line; the multi-line `resultJson.summary` excerpt moves into an optional expandable.
|
||||
|
||||
**Idle-state attention point for Phase 3a:** the idle treatment as written ("Last run: SKI-139 succeeded 2h ago") answers "what just happened" but not "what's supposed to happen next." A monitoring user checking in on an idle agent may benefit from a small forward-looking signal such as "Next scheduled run in 12m," "Awaiting assignment," or "No pending work." Phase 3a should consider whether to add this and what data drives it (heartbeat schedule interval, queued wakeups, assigned-but-untouched issues). Not a redesign change at the concept level — a polish decision for the module pass.
|
||||
|
||||
**Right zone (~530px):**
|
||||
- Budget position block (see §3).
|
||||
- Recent runs strip (see §7).
|
||||
|
||||
---
|
||||
|
||||
## 2. Chart consolidation — from four to one
|
||||
|
||||
**Survivors:** one chart — **Run Activity (merged with Success Rate)**. Stacked succeeded / failed / other bars per day, 14 days, with success-rate percentage rendered as a **subtitle** ("78% success · last 14 days"). Uses existing `RunActivityChart` data shape, just gets a success-rate caption added. Subtitle over line-overlay: line overlay reintroduces competing axes (count vs. percentage) in the same chart, which is the visual complexity the 4-to-1 consolidation is trying to remove. Subtitle keeps the chart single-axis and the success-rate signal still visible at a glance.
|
||||
|
||||
**Dropped and why each is safe:**
|
||||
- **Issues by Priority** — duplicates the `/issues?participantAgentId=...&priority=...` filter view. One click from the dashboard (via the existing "See All →" link on the in-flight-tasks list) reaches the canonical home for this data. Reachable within 1 interaction.
|
||||
- **Issues by Status** — same logic as Priority. The Issues list is the canonical home. Dashboard isn't the right place for issue-breakdown charts when the Issues list can answer the same question with filtering.
|
||||
- **Success Rate as its own chart** — a single number derived from the same `runs` array that feeds Run Activity. Folds into Run Activity as a caption. No information loss.
|
||||
|
||||
**What's uniquely dashboard-shaped:** temporal run activity. Every other chart duplicated a view that lives elsewhere. Following *Information Scent* principle and the brief's direction ("charts earn their place"): one chart that does its own job, not four that duplicate other surfaces.
|
||||
|
||||
**Reachability check against rubric Section 4:**
|
||||
- Priority / Status data reachable within 2 interactions: ✓ (one click to Issues list, one filter click). Both ≤2.
|
||||
- Run Activity + Success Rate data visible on-dashboard: ✓ (the one surviving chart).
|
||||
|
||||
---
|
||||
|
||||
## 3. Budget position — compact inline card
|
||||
|
||||
**Shape:** horizontal progress bar with stacked metadata. Specifically, a 4-line compact block:
|
||||
|
||||
```
|
||||
Budget — this month
|
||||
$142.50 of $500.00 29%
|
||||
▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
|
||||
$357.50 remaining · resets in 11 days
|
||||
```
|
||||
|
||||
- Line 1: small label (`text-xs text-muted-foreground`).
|
||||
- Line 2: `observedAmount / amount` in `tabular-nums`, utilization % right-aligned.
|
||||
- Line 3: progress bar — fills using existing `BudgetPolicyCard` color logic (`--signal-success` when OK, severity-appropriate tone for warn/hard_stop).
|
||||
- Line 4: remaining amount + window end.
|
||||
|
||||
**Why this shape, not alternatives:**
|
||||
- Pure utilization % — too abstract, doesn't answer "how much do I have left" without mental arithmetic.
|
||||
- `$X / $Y` alone — readable but loses the "how close to the ceiling" spatial signal that a progress bar gives.
|
||||
- Combined — single glance answers three questions (how much used, how much total, proportion).
|
||||
|
||||
**Reuse vs. inline:** concept says *inline variant* of `BudgetPolicyCard`'s visual treatment — sized for a hero zone, not a full-page card. Same color logic, same token set. No new tokens. *Reach for what exists first* (operating principles): use the existing `agentBudgetSummary` shape directly, thread it into `AgentOverview` as discovered in §5 of discovery.
|
||||
|
||||
---
|
||||
|
||||
## 4. Change-priority affordance — explicit selector
|
||||
|
||||
**Decision:** explicit priority selector per row. Not drag.
|
||||
|
||||
**Specifically:** each in-flight-tasks row has a priority icon (existing `PriorityIcon` component) on the left, acting as a button. Click opens a popover with the 4 priority options (`critical | high | medium | low`). Pick one, popover closes, row updates optimistically. Uses `issuesApi.update(id, { priority })`.
|
||||
|
||||
**Why selector over drag:**
|
||||
- **Fitts's Law** — click-target 16×16 is faster than a drag gesture across four buckets. Priority change is a frequent, low-stakes operation; should be cheap.
|
||||
- **Hick's Law** — 4 discrete choices are equivalent whether presented as drag targets or popover items. Popover is more compact.
|
||||
- **Vertical space cost** — drag-to-bucket requires a 4-column Kanban layout, eating ~250px vertically before any tasks are shown. That breaks the monitoring-no-scroll goal. The dashboard is about information density; the priority affordance must not compete with that.
|
||||
- **Keyboard accessibility out of the box** — popover with 4 `<button>` items is keyboard-navigable without the `@dnd-kit` keyboard-sensor layer. Satisfies rubric Section 3 ("drag-and-drop or keyboard-reorderable") cleanly.
|
||||
- **Precedent isn't free** — `KanbanBoard.tsx` uses drag for *status* changes across 7 columns where the visual metaphor (columns = states) is strong. Priority doesn't have the same spatial metaphor; priority is a ranking, not a location. Drag would be notation theater.
|
||||
|
||||
**Interaction detail** — after selection, the row may jump positions (sort is priority-then-updatedAt; see §Additional 1). The motion is the feedback. Optimistic update means <400ms perceived latency (*Doherty Threshold*).
|
||||
|
||||
**Flagged for run-notes:** drag was considered and rejected for vertical-space cost. If your intent in the brief was *specifically* to force a drag exploration, the selector is not what you asked for — but the rubric admits either, so this is an intent-vs-implementation call I'm making.
|
||||
|
||||
---
|
||||
|
||||
## 5. Session burn rate — inside the costs module, not as a rate
|
||||
|
||||
**Placement:** inside the unified costs module (below in-flight tasks), next to the existing totals strip. Not in the hero.
|
||||
|
||||
**What "burn rate" actually means here:** reading the brief carefully, it says "recent cost activity against the current session (cumulative cost and per-run cost over the recent window)." That's not rate — that's two concrete signals:
|
||||
|
||||
1. **Cumulative session cost** — from `runtimeState.totalCostCents` + token totals (input, output, cached). Today's KPI strip already shows this. Keep as-is.
|
||||
2. **Per-run cost over recent window** — from the heartbeats array, filtered to recent runs with nonzero cost. Today's cost table already shows this. Keep as-is.
|
||||
|
||||
The redesign's job here is *merging* them into one coherent surface, not adding a new "rate" concept. Proposed layout:
|
||||
|
||||
```
|
||||
Costs — session
|
||||
$1.42 cumulative · 142k in · 38k out · 12k cached
|
||||
|
||||
Recent runs with cost
|
||||
Date Run Input Output Cost
|
||||
…
|
||||
```
|
||||
|
||||
Totals strip collapses to a single line under a small heading; per-run table sits directly below. One visual region, two complementary signals.
|
||||
|
||||
**Flagged for run-notes:** the brief's phrasing is ambiguous between "rate" and "two signals." I'm interpreting as the latter because that's what the data actually supports without adding synthesis. If you wanted a rate (e.g., "$0.12/minute for the last hour"), I'd need to derive it from per-run `startedAt`/`finishedAt`/`cost` — doable but additive. Flagging for your call.
|
||||
|
||||
---
|
||||
|
||||
## 6. Live-run signal — cyan relocated, not cyan preserved
|
||||
|
||||
**Decision:** keep cyan as the live-agent color, but relocate it from card-level (glow + border tint) to pill-level (activity-state pill at hero top-left).
|
||||
|
||||
**What changes specifically:**
|
||||
- **Activity state pill** — when `agent.status === "running"`, the pill reads `● Running` with a cyan pulse dot, cyan text. Uses existing `agentStatusDot` from `status-colors.ts` (no new tokens).
|
||||
- **Current-work card** — no cyan border, no cyan glow. Plain `border-border rounded-none` container. Inside, the existing `StatusIcon` (cyan spinning `Loader2` when status is `running`) still appears — it sits next to the run id, providing the card-local running signal without the full-card chromatic treatment.
|
||||
|
||||
**Why this is better than cyan-preserved-as-is:**
|
||||
- Hero is denser. Budget progress bar could use `--signal-success` (green), status badges on rows have color, recent-health dots are multi-colored. Adding a cyan-glow card on top of that creates chromatic competition — the eye can't tell which colored element is the primary signal. *Von Restorff* only works when the isolated element is alone.
|
||||
- The activity-state pill is higher-signal per-pixel: one small dot at top-left says "the agent is alive right now" with less visual mass than a full-width cyan glow.
|
||||
- Preserves the app's existing cyan = liveness vocabulary (workspace-ops, header mobile indicator) without duplicating it in two places within the dashboard.
|
||||
|
||||
**Rubric Section 5 "live-run visual distinction" passes** because cyan is preserved as the color; the location is just different. The distinction between live and non-live is visible (pulse dot vs. no pulse; different activity pill text).
|
||||
|
||||
---
|
||||
|
||||
## 7. Recent health — 7 clickable dots
|
||||
|
||||
**Decisions:**
|
||||
- **Count:** 7 runs. Comfortable within *Miller's Law* (7±2), visually scannable, roughly one week for an active agent.
|
||||
- **Visual treatment:** colored status dots, inline horizontal strip, oldest → newest left to right. Colors from existing `status-colors.ts` entries (green = succeeded, red = failed, cyan = running, yellow = queued, neutral = cancelled/other). Per *WCAG color-independence*, each dot also has a distinct symbol on hover/focus (using lucide icons — `CheckCircle2`, `XCircle`, etc.), so colorblind users can still parse.
|
||||
- **Interactivity:** each dot is a `<Link>` to that run's detail (`/agents/[agent]/runs/[runId]`). Hover/focus shows a tooltip with run id, outcome, and `relativeTime` — pulls from existing `runStatusIcons` + `sourceLabels` maps.
|
||||
- **Duration:** not shown. Too compact for duration metadata. Duration lives in the costs table below.
|
||||
|
||||
**Position:** right zone of hero, directly under the budget card. Small label "Recent runs — last 7" above the dot strip.
|
||||
|
||||
---
|
||||
|
||||
## Additional 1 — "In-flight tasks" definition
|
||||
|
||||
**Filter:** issues where `assigneeAgentId === agent.id` AND `status ∈ {todo, in_progress, blocked}`. Excludes `backlog` (not yet committed to work), `in_review` (handed off), `done`, `cancelled`.
|
||||
|
||||
**Sort:** `priority` descending (critical → low), then `updatedAt` descending within each priority bucket. Ties broken by `createdAt`.
|
||||
|
||||
**Limit:** 7 rows visible by default. Matches Recent-health count for visual rhythm. A "View all (N) →" link navigates to `/issues?participantAgentId=...` for the full list, satisfying *Progressive Disclosure*.
|
||||
|
||||
**UX implication, intended:** when a user changes priority on a row, the row repositions according to the new sort. This is feedback — the repositioning confirms the action. Without it, the action is inert. Flagging as intended behavior in run-notes.
|
||||
|
||||
---
|
||||
|
||||
## Additional 2 — Per-mode serving check
|
||||
|
||||
| Mode | Served by | Crowding risk |
|
||||
|---|---|---|
|
||||
| Monitoring (primary) | Hero band: 4 Section-1 items without scroll | none — hero is the whole monitoring surface |
|
||||
| Operations (secondary) | In-flight tasks list + priority-icon popover | none — list sits below hero, no visual competition |
|
||||
| Accountability — spend | Budget in hero (position) + costs module (breakdown) | ⚠ see §6 of discovery: per-*task* spend isn't easy. `HeartbeatRun` has no `issueId` link. Can surface per-*run* cost in the table; per-task rollup would require joining runs → issues via `run.contextSnapshot` or similar, which is beyond the rubric's asks. Flagging, not blocking. |
|
||||
| Accountability — tokens | Costs module token totals + per-run breakdown | none |
|
||||
| Debug | Recent-health dots (1-click to failed run) + costs table (timestamps + per-run) | ⚠ rubric Section 2 Debug: "identify failed *tasks*, see when each one failed." Failed *runs* ≠ failed tasks per se. Close-enough for this dashboard since a task that failed to run is represented by its failed run; but flagging the terminology gap in run-notes. |
|
||||
|
||||
---
|
||||
|
||||
## Additional 3 — Operating principles cited
|
||||
|
||||
Decisions grouped by the lens that drove them:
|
||||
|
||||
- **Prägnanz / Common Region** — two-zone hero splits "now" vs "how are we doing" (§1).
|
||||
- **Miller's Law (7±2)** — 7 recent runs, 7 in-flight tasks max (§7, §Additional 1).
|
||||
- **F-pattern scanning** — activity state top-left where eyes land first (§1).
|
||||
- **Serial Position** — hero → chart → tasks → costs in order of monitoring > operations > accountability (§Shape).
|
||||
- **Fitts's Law + Hick's Law** — explicit selector over drag (§4).
|
||||
- **Von Restorff** — cyan pill is the isolated chromatic signal, not competing with cyan glow elsewhere (§6).
|
||||
- **Doherty Threshold (<400ms)** — optimistic update on priority change (§4).
|
||||
- **Progressive Disclosure** — "View all →" link for task overflow; per-run detail hidden behind run-detail navigation (§Additional 1, §7).
|
||||
- **Recognition over Recall** — reuse `PriorityIcon`, `StatusIcon`, `BudgetPolicyCard` styling; users already know these glyphs (§3, §4, §7).
|
||||
- **WCAG POUR / color-independence** — status dots and priority glyphs use shape + color, not color alone (§7).
|
||||
- **Information Scent** — dropped charts are reachable via clear scent (the `/issues` filters) within 1 click (§2).
|
||||
- **Reach for what exists first** (operating principles "DS-first discipline") — zero new components, zero new tokens; reuse `agentBudgetSummary`, `PriorityIcon`, `StatusBadge`, existing `issuesApi.update`, existing `@dnd-kit` not-used, existing `runStatusIcons` map (§3, §4, §5, §7).
|
||||
- **Postel's Law** — handle gracefully the zero-runs, zero-issues, no-budget-policy, and paused-agent states; each element has a defined empty treatment (§7, Phase 3a).
|
||||
|
||||
---
|
||||
|
||||
## What this concept is not deciding
|
||||
|
||||
Explicitly out-of-scope for this concept, per brief and operating principles:
|
||||
- Component extraction (`AgentOverview` etc. stay inline in `AgentDetail.tsx`).
|
||||
- Chart token introduction (Run Activity keeps hardcoded hex).
|
||||
- Status-color changes (`status-colors.ts` untouched).
|
||||
- New tokens (no `--signal-warning`, no chart tokens, nothing else).
|
||||
- Pause/terminate controls (pause is in page chrome, not duplicated; terminate is out of scope).
|
||||
- Other profile tabs.
|
||||
|
||||
---
|
||||
|
||||
## Findings surfaced (not decisions to pursue here)
|
||||
|
||||
These are things the design work surfaced that aren't mine to fix in this experiment. Noting for the post-experiment decision pile.
|
||||
|
||||
1. **Per-task cost attribution is weak** — `HeartbeatRun.contextSnapshot` may or may not carry an issue link reliably. A real spend-per-task view would need a first-class linkage.
|
||||
2. **Header/dashboard cyan hue drift** (header uses `bg-blue-*`, inline card uses `bg-cyan-*`) — out of scope for Run A; would be a 1-line fix in the header outside the redesign region.
|
||||
3. **`--signal-warning` is still missing** — budget warn-state will use whatever `BudgetPolicyCard` already does (probably raw-palette amber). Flagging in run-notes per the rubric's "pass with explicit justification" mechanic; not fixing.
|
||||
4. **"Failed task" vs "failed run" terminology gap** (rubric Section 2 Debug) — not a redesign decision; a brief-terminology decision for a future iteration.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
A two-zone hero carries the monitoring frame. Below it, one merged chart keeps the temporal signal without duplicating other surfaces. An in-flight-tasks list with an explicit priority selector serves operations mode without eating vertical space. A unified costs module serves both accountability modes. Cyan is preserved but relocated to the activity-state pill, not the card border. Seven clickable recent-run dots give 1-click access to debug-mode investigation.
|
||||
|
||||
Three interpretations I'm making that deserve your pushback before Phase 2:
|
||||
1. Selector over drag (§4).
|
||||
2. "Burn rate" as two complementary signals, not a rate (§5).
|
||||
3. Sort-induced repositioning on priority change as feedback, not disruption (§Additional 1).
|
||||
|
||||
Waiting on your read.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3a revision — recent-runs feed replaces icon strip
|
||||
|
||||
Post-smoke-test decision (captured after Phase 3a polish round 1). The icon-strip treatment specified in §7 proved information-sparse in practice — run id, outcome detail, and timestamp were all hidden behind hover, forcing an interaction to decode any individual run.
|
||||
|
||||
**Replaced with:** a compact prior-runs feed in the left zone, placed below the Latest Run card. Three one-line rows, each showing:
|
||||
|
||||
- Status icon (colored, matching `runStatusIcons` — shape carries outcome)
|
||||
- Abbreviated run id (`font-mono`)
|
||||
- Status label (plain text, e.g., `succeeded`, `failed`, `timed_out`)
|
||||
- Relative time (right-aligned, `tabular-nums`)
|
||||
|
||||
Each row is a `<Link>` to the run detail. Information is visible in the row, no hover required.
|
||||
|
||||
**Hero split adjusts to 75/25** — left zone expands to 75% to absorb the feed (activity pill + Latest Run card + prior-runs feed + idle hint), right zone shrinks to 25% with the budget card alone. Three-zone layout from polish round 1 collapses back to two-zone.
|
||||
|
||||
**Why this is better for the primary goal:**
|
||||
- *Jakob's Law* — users already know how to read the Latest Run card's anatomy (icon + id + status + time); the compact rows reuse that mental model at lower visual weight.
|
||||
- *Recognition over Recall* — outcomes readable at a glance, not color-only under hover.
|
||||
- *Information Scent* — failed runs announce themselves in the feed; monitoring users can spot a problematic recent pattern without interacting.
|
||||
- *Serial Position + monitoring-primary hierarchy* — Latest Run remains the dominant surface; the compact feed reinforces "right now matters most" while still delivering recent health as required by rubric Section 1 item 3.
|
||||
|
||||
**What this doesn't change:**
|
||||
- Rubric Section 1 item 3 (recent health) is still satisfied — arguably better than the icon strip.
|
||||
- Rubric Section 4 (chart data reachability) is unaffected — chart consolidation still happens in Phase 3b.
|
||||
- The "recent issues reachable" rubric item is unrelated — that's the in-flight tasks list below the hero.
|
||||
- Cyan-relocation call from §6 unchanged — activity pill still carries the live signal; no card-level cyan on Latest Run.
|
||||
|
||||
This revision supersedes §7 for implementation purposes. The original §7 is preserved above as the concept as-authored; this note records the drift.
|
||||
263
projects/agent-profile-dashboard/run-a-discovery.md
Normal file
263
projects/agent-profile-dashboard/run-a-discovery.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# Run A — Discovery (deeper pass)
|
||||
|
||||
Observed structure of the current Dashboard-tab implementation, the APIs that feed it, and a reality check on the three named action affordances. No design decisions yet — just what's on disk.
|
||||
|
||||
---
|
||||
|
||||
## Executive summary (surface these at the checkpoint)
|
||||
|
||||
Flagged in priority order; details further down.
|
||||
|
||||
1. **🔴 Scope blocker — persisted task reordering has no data model support.** No `order`, `rank`, or `position` field exists on `Issue`, `AgentTaskSession`, or any adjacent type. The only reorderable-ish dimension is `priority`, which is a closed 4-value enum (`critical|high|medium|low`). `KanbanBoard.tsx` already uses `@dnd-kit` but only persists *column* (status) changes on drop — within-column ordering is visual-only. The brief's "drag-and-drop or keyboard-reorderable list scoped to top 5–10 in-flight tasks" cannot be implemented with server persistence against the current API.
|
||||
2. **🟡 "In-flight tasks" is itself underdefined.** The data model exposes three candidate concepts — heartbeat runs (historical point-in-time), assigned issues (have status/priority, no user-specific order), task sessions (adapter-side, no ordering). None is obviously "the list of things the agent will work on next." Design needs to pick one and own the interpretation.
|
||||
3. **🟡 Pause is already fully wired in the page chrome.** `PauseResumeButton` + `agentsApi.pause/resume` render in the header at `AgentDetail.tsx:942`. It already satisfies the brief's "single-click, reversible via same button, no confirmation, neutral styling" spec. Open question: does the brief want it *duplicated* inside the dashboard, *moved* into the dashboard, or does "surface on the dashboard" count as already satisfied by the ever-present header?
|
||||
4. **🟡 `budgetOverview` is alive, not dead, but bypasses `AgentOverview`.** It's fetched for any authenticated user (not gated on `needsDashboardData`) and consumed at page-root level into `agentBudgetSummary` (line 699–729). It is *not* threaded into `AgentOverview` props. Redesign can opt to thread it through — live budget state is genuinely useful for the primary-goal "live economics" signal.
|
||||
5. **🟢 Cyan-pulse live signal has color drift across surfaces.** Inline `LatestRunCard` uses `bg-cyan-400`; the header's mobile-only live indicator uses `bg-blue-400/500`. Same concept, different hues, not coordinated. A redesign that preserves "visual distinction for live-running agents" should pick one and apply it consistently within the dashboard.
|
||||
|
||||
Items 1 and 2 together are the biggest open question. I don't want to pick an interpretation without your sign-off.
|
||||
|
||||
---
|
||||
|
||||
## 1. `LatestRunCard` (`AgentDetail.tsx:1170–1255`)
|
||||
|
||||
### Props
|
||||
```ts
|
||||
{ runs: HeartbeatRun[]; agentId: string }
|
||||
```
|
||||
`agentId` here is `canonicalAgentRef` (URL-shaped key), not the UUID.
|
||||
|
||||
### State and hooks
|
||||
- No state. One `useMemo` (line 1187) to extract a 2–3-line summary from `run.resultJson.summary ?? result ?? error`, stripping markdown headers, list marks, code fences, blockquotes, and table rows; capped at 3 lines or 280 chars.
|
||||
|
||||
### Render structure
|
||||
- Container wrapper (`div.space-y-3`)
|
||||
- Header row (h3 with conditional cyan-pulse dot + label, right-aligned link to run detail)
|
||||
- Body `Link` card (`block border rounded-none p-4 space-y-2`) — clicking anywhere opens the run
|
||||
- Metadata row: `StatusIcon` (from `runStatusIcons` lookup) + `StatusBadge` + 8-char run id + source-tag pill + right-aligned `relativeTime`
|
||||
- Conditional summary block (`MarkdownBody`, max-h-16, overflow-hidden)
|
||||
|
||||
### Styling mechanism
|
||||
Inline Tailwind classes + `cn()` for conditional live-vs-idle border/shadow.
|
||||
|
||||
### External dependencies
|
||||
- DS tokens: `border-border`, `text-muted-foreground`, `hover:bg-muted/50`, `hover:text-foreground`.
|
||||
- Raw-palette drift: `bg-cyan-400`, `border-cyan-500/30`, `shadow-[0_0_12px_rgba(6,182,212,0.08)]` for live state; source-tag pill uses `bg-blue-100/text-blue-700` (timer), `bg-violet-100/text-violet-700` (assignment), `bg-cyan-100/text-cyan-700` (on_demand).
|
||||
- Shadcn: none directly — uses `Link` from the local router shim and plain `<div>`s.
|
||||
- Status primitives: `runStatusIcons` (local `AgentDetail.tsx:103`, hex-adjacent Tailwind tones keyed by status), `StatusBadge` (from `status-colors.ts` catalog), `sourceLabels` (local `AgentDetail.tsx:168`).
|
||||
- Charts / content: `MarkdownBody` for the excerpt.
|
||||
|
||||
### Edge cases and conditionals
|
||||
- Zero runs → early `return null`. (So the card completely disappears when the agent has no runs yet — there is no "idle, never run" state today.)
|
||||
- No live run present → picks the most-recent run by `createdAt` and renders it as "Latest Run" with the neutral border.
|
||||
- `summary` empty → the excerpt div is omitted (card is still rendered with just the metadata row).
|
||||
- Agent status is **not** consulted here — `paused` agents still show a "Latest Run" with no indication the agent is currently paused.
|
||||
|
||||
---
|
||||
|
||||
## 2. `AgentOverview` (`AgentDetail.tsx:1259–1335`)
|
||||
|
||||
### Props
|
||||
```ts
|
||||
{
|
||||
agent: AgentDetailRecord;
|
||||
runs: HeartbeatRun[];
|
||||
assignedIssues: Array<{
|
||||
id: string;
|
||||
title: string;
|
||||
status: string;
|
||||
priority: string;
|
||||
identifier?: string | null;
|
||||
createdAt: Date;
|
||||
}>;
|
||||
runtimeState?: AgentRuntimeState;
|
||||
agentId: string; // UUID
|
||||
agentRouteId: string; // URL key
|
||||
}
|
||||
```
|
||||
`assignedIssues` is *not* a full `Issue` — it's a narrowed shape. The page computes it via `.sort((a,b) => b.updatedAt - a.updatedAt)` at line 695 but does not actually pick the narrowed fields — the narrowing is typed in the props interface only, which means the full `Issue` object is passed through despite the declaration. Redesign has access to every `Issue` field should it need one.
|
||||
|
||||
### State and hooks
|
||||
None. Pure render function.
|
||||
|
||||
### Render structure
|
||||
- Container (`div.space-y-8`)
|
||||
- `<LatestRunCard>` (see above)
|
||||
- Charts grid (`grid grid-cols-2 lg:grid-cols-4 gap-4`) — 4× `<ChartCard>` wrapping chart bodies
|
||||
- Recent Issues block (`div.space-y-3`)
|
||||
- Header row: h3 + right-aligned `Link` to `/issues?participantAgentId=…`
|
||||
- Either empty-state `<p>` or bordered container with up to 10 `<EntityRow>` children + optional "+N more" footer
|
||||
- Costs block (`div.space-y-3`)
|
||||
- h3
|
||||
- `<CostsSection>` (see below)
|
||||
|
||||
### Styling mechanism
|
||||
Inline Tailwind, no `cva`, no `cn()`.
|
||||
|
||||
### External dependencies
|
||||
- DS tokens: `border-border`, `text-muted-foreground`, `hover:text-foreground`.
|
||||
- Raw-palette: none directly in `AgentOverview` itself — all raw-palette usage lives in children (`LatestRunCard`, the chart components, `runStatusIcons`).
|
||||
- Shadcn primitives: none directly.
|
||||
- Project components: `ChartCard`, `RunActivityChart`, `PriorityChart`, `IssueStatusChart`, `SuccessRateChart`, `StatusBadge`, `EntityRow`.
|
||||
- Local helpers: `LatestRunCard`, `CostsSection`.
|
||||
|
||||
### Edge cases and conditionals
|
||||
- `assignedIssues.length === 0` → renders "No recent issues." paragraph, no bordered container.
|
||||
- More than 10 assigned issues → overflow footer `+N more issues`.
|
||||
- Zero runs → Charts still render their own empty states ("No runs yet"). `LatestRunCard` disappears entirely. Costs section shows just the runtimeState KPI strip (if present), or renders nothing meaningful (no table, no KPIs).
|
||||
- Agent is paused → no visual cue appears in `AgentOverview`. The page header shows the pause state via `StatusBadge`, and `PauseResumeButton` flips to "Resume," but nothing inside the dashboard area acknowledges paused state.
|
||||
- Agent is `pending_approval` → header shows a yellow banner; dashboard content still renders normally.
|
||||
|
||||
---
|
||||
|
||||
## 3. `CostsSection` (`AgentDetail.tsx:1339–1413`)
|
||||
|
||||
### Props
|
||||
```ts
|
||||
{ runtimeState?: AgentRuntimeState; runs: HeartbeatRun[] }
|
||||
```
|
||||
|
||||
### State and hooks
|
||||
None. Derives `runsWithCost` inline (filter + sort, non-memoized — recomputes every render; fine given small `runs` count).
|
||||
|
||||
### Render structure
|
||||
- Container (`div.space-y-4`)
|
||||
- Conditional KPI strip (`div.border.rounded-none.p-4` → `grid grid-cols-2 md:grid-cols-4 gap-4 tabular-nums`): Input / Output / Cached tokens + Total cost. Renders only when `runtimeState` exists.
|
||||
- Conditional per-run table (bordered wrapper → `<table>` with columns: Date / Run (8-char UUID prefix) / Input / Output / Cost). Top 10 rows by `createdAt` desc.
|
||||
|
||||
### Styling mechanism
|
||||
Inline Tailwind, no `cva`.
|
||||
|
||||
### External dependencies
|
||||
- DS tokens: `border-border`, `bg-accent/20`, `text-muted-foreground`.
|
||||
- Raw-palette: none.
|
||||
- Shadcn: none.
|
||||
- Helpers: `formatTokens`, `formatCents`, `formatDate`, `runMetrics` (local).
|
||||
|
||||
### Edge cases and conditionals
|
||||
- `!runtimeState` → KPI strip hidden.
|
||||
- `runsWithCost.length === 0` → table hidden.
|
||||
- If both are absent → the Costs block collapses to just its empty outer `<div>` (caller renders just the "Costs" h3 with nothing underneath). Worth noting: there is no "no cost data yet" empty state; it silently degrades to whitespace.
|
||||
|
||||
---
|
||||
|
||||
## 4. Data model surface (4 APIs)
|
||||
|
||||
### `agentsApi.runtimeState(agentId, companyId)` → `AgentRuntimeState`
|
||||
Relevant fields:
|
||||
- `sessionId`, `sessionDisplayId`, `stateJson` — adapter session state.
|
||||
- `lastRunId`, `lastRunStatus` — pointer to most-recent run (lets a redesign show "health" without scanning the heartbeats array).
|
||||
- `totalInputTokens`, `totalOutputTokens`, `totalCachedInputTokens` — aggregate counts for the lifetime of the current session.
|
||||
- `totalCostCents` — lifetime cost for the current session.
|
||||
- `lastError` — most-recent adapter-level error text.
|
||||
|
||||
**Important:** "total" here is session-lifetime, **not** "last 14 days," "this month," or "right now." If a redesign wants real-time burn rate, it has to derive that from per-run cost points.
|
||||
|
||||
### `heartbeatsApi.list(companyId, agentId?, limit?)` → `HeartbeatRun[]`
|
||||
Per-run fields worth naming:
|
||||
- `id`, `agentId`, `invocationSource` (`timer` | `assignment` | `on_demand` | `automation`), `triggerDetail`.
|
||||
- `status` (`succeeded` | `failed` | `running` | `queued` | `timed_out` | `cancelled` | `scheduled_retry`).
|
||||
- `startedAt`, `finishedAt`, `createdAt`, `updatedAt` (all `Date`).
|
||||
- `usageJson`, `resultJson` — unstructured records. `runMetrics()` parses `{inputTokens|input_tokens}`, `{outputTokens|output_tokens}`, `{cachedInputTokens|...}`, and `visibleRunCostUsd(usage, result)`.
|
||||
- `error`, `errorCode`, `stderrExcerpt` — failure context for debug mode.
|
||||
- `livenessState`, `livenessReason`, `continuationAttempt`, `lastUsefulActionAt`, `nextAction` — liveness signals for a running run.
|
||||
|
||||
**No field links a run to an issue** directly in the narrow `HeartbeatRun` type. (`ActiveRunForIssue` and `LiveRunForIssue` are separate helper types used on the issue side.)
|
||||
|
||||
### `issuesApi.list(companyId, filters)` → `Issue[]`
|
||||
Filter `{ participantAgentId }` is the filter used on this page. Returns full `Issue` records.
|
||||
|
||||
Relevant `Issue` fields for each mode:
|
||||
- Monitoring: `status`, `priority`, `executionRunId`, `executionLockedAt` — "is the agent actively on this one."
|
||||
- Operations: `priority` (4-bucket enum) is the *only* reorder-ish knob. No `order`, `rank`, `position`, or agent-personal ordering field.
|
||||
- Accountability: no cost rollup per-issue exists in `Issue` itself.
|
||||
- Debug: `executionRunId` lets you jump from a failed issue to the run that ran it.
|
||||
|
||||
Mutation surface: `issuesApi.update(id, data)` accepts arbitrary PATCH data. No reorder endpoint. `issuesApi.checkout(id, agentId)` assigns for execution.
|
||||
|
||||
### `budgetsApi.overview(companyId)` → `BudgetOverview`
|
||||
- `policies: BudgetPolicySummary[]` — one per scope (`company`, `agent`, `project`). Each has `amount`, `observedAmount`, `remainingAmount`, `utilizationPercent`, `warnPercent`, `status: "ok"|"warning"|"hard_stop"`, `paused`, `pauseReason`, `windowStart`, `windowEnd`.
|
||||
- `activeIncidents: BudgetIncident[]`, `pausedAgentCount`, `pausedProjectCount`, `pendingApprovalCount`.
|
||||
|
||||
`AgentDetail` already filters `policies` to find the one with `scopeType === "agent" && scopeId === agent.id` and synthesizes one from `agent.budgetMonthlyCents` / `spentMonthlyCents` as a fallback. The synthesized one is what downstream budget UI actually uses.
|
||||
|
||||
**Refetch cadence:** `budgetOverview` query has `refetchInterval: 30_000, staleTime: 5_000`. So it's genuinely live data — a redesign that uses it gets 30s-granularity updates for free.
|
||||
|
||||
---
|
||||
|
||||
## 5. Resolving the "dead code?" question on `budgetOverview`
|
||||
|
||||
**Not dead.** Used at `AgentDetail.tsx:699` to compute `agentBudgetSummary`, which is consumed by `BudgetPolicyCard` rendered inside the Budget tab (not the Dashboard tab). The Dashboard tab fetches it (via the `enabled: !!resolvedCompanyId` gate, not gated to dashboard-only) but doesn't use it in `AgentOverview`.
|
||||
|
||||
**Implications for redesign:**
|
||||
- Threading `agentBudgetSummary` into `AgentOverview` props costs one line at the call site (1091–1099) and is consistent with the "all current data must remain reachable" principle.
|
||||
- The summary exposes: `observedAmount`, `remainingAmount`, `utilizationPercent`, `status`, `paused`, `warnPercent`, `windowEnd`. All are directly useful for the primary-goal "live economics" rubric item.
|
||||
- Using it doesn't require a new token or new component — `BudgetPolicyCard` already renders this shape.
|
||||
|
||||
Not a recommendation yet — just noting the option exists.
|
||||
|
||||
---
|
||||
|
||||
## 6. Action affordance reality check
|
||||
|
||||
### Pause agent — ✅ already implemented (open interpretation question)
|
||||
- `agentsApi.pause(id, companyId)` and `agentsApi.resume(id, companyId)` exist (`agents.ts:147–148`).
|
||||
- `PauseResumeButton` component exists (`AgentActionButtons.tsx:23`) and is already wired in the page header with the exact semantics the brief specifies: single-click, reversible via same button, no confirmation dialog, neutral outline styling, `<Pause>` / `<Play>` icons from lucide.
|
||||
- `AgentStatus` enum includes `paused` (`constants.ts:16`), and `Agent.pauseReason` exists on the type.
|
||||
- **Question:** does the brief want pause *also* surfaced inside `AgentOverview`, or does it consider the header instance as already satisfying the requirement? The rubric ("Pause agent. Single-click action, reversible via the same button…") doesn't specify placement.
|
||||
|
||||
### Reorder in-flight tasks — 🔴 blocked (needs brief clarification)
|
||||
- No `order`, `rank`, `position`, `displayOrder`, or user-personal ordering field anywhere.
|
||||
- `Issue.priority` is a closed 4-value enum. Even if we reinterpret drag-reorder as "promote/demote by priority bucket," that's 4 coarse steps, not arbitrary ordering.
|
||||
- `AgentTaskSession` has no ordering field.
|
||||
- No reorder endpoint on `issuesApi`, `agentsApi`, `heartbeatsApi`, or elsewhere.
|
||||
- `@dnd-kit` is installed (`ui/package.json:32-34`) and used in `KanbanBoard.tsx` and `SidebarProjects.tsx`. **But:** `KanbanBoard.handleDragEnd` persists only *column* (status) changes on drop (line 247–249). Within-column ordering is lost on refresh. `SidebarProjects` also does not persist ordering.
|
||||
- There is no pending-tasks queue surface in the app today. The current dashboard shows "Recent Issues" (history, not queue) and that's it.
|
||||
|
||||
**What's physically possible:**
|
||||
- **(a) Local-only reorder** — UI works, but order doesn't survive refresh or cross-user. Fails the rubric's intent (a teammate can't set an order for another teammate to see).
|
||||
- **(b) Priority-bucket promote/demote** — drag into one of 4 buckets. Feasible via `issuesApi.update(id, { priority })`. Coarse and doesn't really "reorder"; more like "re-classify."
|
||||
- **(c) Flag as scope blocker** — revise the action to something achievable today, or revise the brief.
|
||||
|
||||
**My flag, not a recommendation:** "reorder in-flight tasks" as written does not correspond to a feature the data model supports. Before I design around it, I need your call on (a) / (b) / (c), or something else.
|
||||
|
||||
### Navigate to detail — ✅ already works
|
||||
- `LatestRunCard` is a `<Link>` to `/agents/[agent]/runs/[runId]`.
|
||||
- `EntityRow` in the Recent Issues list takes a `to` prop that currently points at `/issues/[id]`.
|
||||
- The source-tag pill and StatusBadge do not independently navigate (parent `<Link>` owns click).
|
||||
|
||||
No API gap. The brief's caveat "preserve current link-based navigation unless a better affordance is clearly warranted" is trivially satisfied.
|
||||
|
||||
---
|
||||
|
||||
## 7. DS observations (things I didn't expect)
|
||||
|
||||
- **Cyan-pulse color drift across live-run indicators.** Inline `LatestRunCard` uses `bg-cyan-400`; the header's mobile-only live indicator (`AgentDetail.tsx:955–956`) uses `bg-blue-400` / `bg-blue-500`. Same "agent is live right now" concept, two different hues, no coordination. Not a blocker, but if I preserve the cyan-pulse on the redesigned dashboard, the header+dashboard combination will still be inconsistent. The fix is out of scope (header is outside the redesign region).
|
||||
- **`ChartCard` placeholder uses `rounded-sm`.** In `ActivityCharts.tsx:113`, the empty-bar placeholder div has `rounded-sm`. The Step 0 radius migration swept `rounded-lg` / `rounded-xl` to `rounded-none` but did not touch `rounded-sm`. So it's consistent with Step 0 policy (pre-existing, not drift I'd be introducing), just worth noting before anyone asks "why is there a rounded corner on the charts." If the redesign avoids empty-state bars entirely, this goes away.
|
||||
- **`bg-accent/20` in `CostsSection` table header.** This is the DS `accent` token (good), just with a custom opacity suffix. Not drift — flagging because the redesign may want to match or deliberately change it.
|
||||
- **`runStatusIcons` uses raw-palette tones** (`text-green-600`, `text-red-600`, `text-cyan-600`, etc.). This is in scope per location (inside `AgentDetail.tsx:103–111`) but its deeper source is that `status-colors.ts` is the canonical catalog for entity status and is explicitly locked. If the redesign wants to change status colors, that's out of scope by policy. If it wants to *use* them consistently, it should route through `StatusBadge` / `StatusIcon` rather than the inline `runStatusIcons` map.
|
||||
|
||||
---
|
||||
|
||||
## 8. Things harder than the brief implies
|
||||
|
||||
1. **"Surface reorder on the dashboard" is meaningfully harder than "surface pause on the dashboard."** Pause is a one-line mutation with a ready-made button. Reorder is a data-model gap.
|
||||
2. **"Live economics" as a primary-goal rubric item** has two reasonable interpretations that require different data sources:
|
||||
- *Session burn rate* — derive from `runtimeState.totalCostCents` / session duration. Session-lifetime only.
|
||||
- *Budget position* — use `agentBudgetSummary.observedAmount` / `remainingAmount`. Month-to-date.
|
||||
Both are useful; only the second is genuinely "live" via 30s refetch. The first is "live" only in that `runtimeState` refreshes. The brief's wording leaves both open — I'll need to pick.
|
||||
3. **The dashboard currently doesn't acknowledge `paused` state at all.** If the primary-goal "activity state: running/idle/paused/error" item is a hard requirement, that's new content, not redesigned content. The existing `StatusBadge` in the header serves the need at page level, so the redesign has to decide whether to duplicate within the dashboard frame.
|
||||
4. **"Fit without scrolling on 1440px"** is tight once the existing data is preserved. Current dashboard is ~1,600–1,800px tall based on the screenshots. Consolidation (chart merges, cost/token unification) is mandatory, not optional, to hit the goal.
|
||||
|
||||
---
|
||||
|
||||
## Summary of what I need from you at the checkpoint
|
||||
|
||||
Explicit answers on:
|
||||
|
||||
1. **Reorder scope.** Pick: (a) local-only UI reorder, (b) priority-bucket drag, (c) revise/drop the reorder action, or (d) something else. I can sketch what each looks like if helpful.
|
||||
2. **Pause placement.** Duplicate-on-dashboard, move-into-dashboard, or header-instance-already-counts?
|
||||
3. **"Live economics" interpretation.** Session burn rate, budget position, or both?
|
||||
4. **Paused-state acknowledgement on the dashboard.** Required for Section 1 pass, or tolerated as header-only?
|
||||
5. **Anything above that doesn't match what you intended** when you wrote the brief.
|
||||
|
||||
No design work yet — waiting for your read.
|
||||
261
projects/agent-profile-dashboard/run-a-notes.md
Normal file
261
projects/agent-profile-dashboard/run-a-notes.md
Normal file
@@ -0,0 +1,261 @@
|
||||
# Run A — Notes
|
||||
|
||||
Review artifact for the Claude Code path (Run A) of the Agent Profile Dashboard redesign. Organized as decisions → deviations → findings → deferred items → rubric link, for readers who weren't present during the work.
|
||||
|
||||
---
|
||||
|
||||
## 1. Header
|
||||
|
||||
**What Run A is.** The Claude Code path of a two-path experiment redesigning the Dashboard tab of the Agent Profile page in Paperclip. Run B (not executed here) is the Paperclip-agent-team path working from the same brief + rubric + reference.
|
||||
|
||||
**What was built.** A full redesign of the three in-scope inline components inside `ui/src/pages/AgentDetail.tsx` — `AgentOverview`, `LatestRunCard`, and `CostsSection` (brief lines 1170–1413 of baseline). The redesign ships a hero-band primary-monitoring surface, a single consolidated chart, a priority-affordance on the in-flight tasks list, and a unified costs module.
|
||||
|
||||
**Branch.** `run-a-claude-code`, branched from `master` at `0f5895e4` (operating-principles commit). 12 commits across five phases (Phase 0 discovery → Phase 4 polish). Not pushed. Final commits:
|
||||
|
||||
```
|
||||
873f15d9 Phase 4 polish: suppress chart legend when there's nothing to legend
|
||||
ec37967d Phase 4 polish: normalize module heading hierarchy
|
||||
859523b5 Phase 3d: unified costs
|
||||
917b72aa Phase 3c: priority affordance
|
||||
0710f166 Phase 3b polish: chart legend and rich tooltips
|
||||
51dd51b9 Phase 3b: chart consolidation
|
||||
774679bf Phase 3a polish round 3: hero-spanning activity pill
|
||||
4996295b Phase 3a polish round 2: compact run feed replaces icon strip
|
||||
568e1c15 Phase 3a polish: 50/25/25 hero layout and shadcn tooltips
|
||||
6b0f4079 Phase 3a: hero module redesign
|
||||
389f8105 Phase 2 fix: derive dashboard activity-state from runs, complete budget window
|
||||
78eed3b1 Phase 2: Agent Profile Dashboard structural frame
|
||||
```
|
||||
|
||||
**Companion document.** Rubric self-scoring is in [`run-a-self-check.md`](./run-a-self-check.md).
|
||||
|
||||
---
|
||||
|
||||
## 2. Decisions made, with reasoning
|
||||
|
||||
### 2.1 Activity-state is derived, not read (Phase 2 fix)
|
||||
|
||||
The hero activity pill resolves state via a derived `activityStatus`: agent-level terminal states (`paused`, `pending_approval`, `terminated`) first, then run-array derivation for `running`, then `agent.status` fallback.
|
||||
|
||||
**Why.** `agent.status` drifts stale in the React Query cache — the detail query keys on the URL-ref (`conversation-tester`) while the live-event invalidation passes the UUID. Different keys, different caches, no prefix match. Symptom: dashboard activity pill reads "Idle" while the Live Run card reads "Running." Deriving running-ness from the same `runs` array the card trusts sidesteps the cache-coherence gap.
|
||||
|
||||
**Lenses.** Mental Models (pill and card must tell one story), Postel's Law (tolerate imperfect backend signals by deriving from always-fresh ones).
|
||||
|
||||
### 2.2 Cyan relocated, not preserved at card level (Phase 3a)
|
||||
|
||||
The Live Run card no longer renders `border-cyan-500/30` + glow shadow. The activity pill above carries the external liveness signal; the card's internal `StatusIcon` (cyan spinning `Loader2`) carries it locally. Eliminates the three-way cyan duplication that existed in Phase 2 (pill + card border + spinner).
|
||||
|
||||
**Lenses.** Von Restorff (isolated chromatic signal must be alone to pop), Information Scent (top-left where the eye lands first per F-pattern is the right home for liveness).
|
||||
|
||||
### 2.3 Hero-spanning activity pill (Phase 3a polish round 3)
|
||||
|
||||
Option C from a Mode 2 alignment exploration. The pill lifts out of the left zone to a hero-level row above the 75/25 grid, and both primary cards share a baseline.
|
||||
|
||||
Three alignment approaches were considered (A: symmetric eyebrows on both zones; B: absorb pill into card; C: hero-spanning header). Option C picked because it is semantically honest (the pill describes the *agent*, not the current-work column), preserves Von Restorff for the cyan signal, has zero structural cost, and handles zero-runs gracefully without conditional complexity.
|
||||
|
||||
**Pill-grounding treatment.** `space-y-3` on the hero wrapper (12px) between pill and grid — tight proximity over a visible separator. Gestalt Proximity groups the pill with the grid below without needing a connector line.
|
||||
|
||||
**Lenses.** Gestalt Proximity (grounding), Jakob's Law (section-header + grid is conventional), Aesthetic-Usability Effect (restraint over decoration).
|
||||
|
||||
### 2.4 Prior-runs compact feed replaces icon strip (Phase 3a polish round 2)
|
||||
|
||||
A concept-level revision from the concept §7 icon-strip pattern. The icon strip was compact but information-sparse — run id, outcome detail, and timestamp all required hover to decode. Replaced with three one-line rows below the Latest Run card, each showing colored icon + mono id + status label + relative time. All info visible; no hover required.
|
||||
|
||||
**Lenses.** Jakob's Law (feed rows reuse the Latest Run card's anatomy at lower weight), Recognition over Recall (outcomes readable at a glance, not color-only under hover), Information Scent (failed runs announce themselves), Serial Position + Miller's Law (1 primary + 3 secondary = 4 items, well within working memory).
|
||||
|
||||
### 2.5 Idle-state "Next up" hint (Phase 3a)
|
||||
|
||||
When the activity status is `idle` or `active` (not running), the left zone below the Latest Run card renders either `Next up · <task-id> <title> →` (linking to the top in-flight task) or `No pending work`. Answers "what's supposed to happen next?" — the forward-looking signal the concept §1 flagged as missing.
|
||||
|
||||
Data scope: derived from the existing `inFlightTasks` memo. A richer "Next scheduled run in 12m" signal would require fetching routines (`RoutineTrigger.nextRunAt`) — out of scope.
|
||||
|
||||
**Lenses.** Goal-Gradient + Zeigarnik (idle agent with pending work surfaces it cheaply), Serial Position (top-priority task earns the hint slot).
|
||||
|
||||
### 2.6 Chart consolidation 4 → 1 with success-rate subtitle (Phase 3b)
|
||||
|
||||
Dropped `PriorityChart`, `IssueStatusChart`, and `SuccessRateChart` from the dashboard composition; kept `RunActivityChart`. Success rate merged into the chart card's subtitle as `N% success · Last 14 days`.
|
||||
|
||||
Dropped charts have 1-interaction reach to their canonical homes:
|
||||
- Issues by Priority / Status → via in-flight "View all →" link to `/issues?participantAgentId=<id>` + filter.
|
||||
- Success Rate → visible as subtitle on the surviving card.
|
||||
|
||||
Success-rate formula: `succeeded / total` (not `succeeded / (succeeded + failed)`), matching the stacked-bars visual so the caption and the chart agree.
|
||||
|
||||
Scope-preserving: `PriorityChart`, `IssueStatusChart`, `SuccessRateChart` are still exported from `ActivityCharts.tsx` for the company-level `pages/Dashboard.tsx` consumer. Only removed from `AgentDetail.tsx` imports and JSX.
|
||||
|
||||
**Lenses.** Information Scent (drops only when canonical home is 1 interaction away), Hick's Law + Choice Overload (one chart asks one question), Pareto (run activity is the 20% doing 80% of the monitoring/debug work).
|
||||
|
||||
### 2.7 Rich chart tooltips + legend scoped to dashboard usage (Phase 3b polish)
|
||||
|
||||
`RunActivityChart` had a pre-existing gap: no legend and only a native-`title` tooltip with daily total (no breakdown). Consolidation raised the chart's prominence, so the gap demanded a fix.
|
||||
|
||||
Scope-preserving:
|
||||
- **Legend**: exported the existing `ChartLegend` helper (was file-private) and rendered it externally inside `ChartCard`. Zero-touch to the shared chart component.
|
||||
- **Tooltips**: added opt-in `richTooltips?: boolean` prop to `RunActivityChart`. When true, each day column wraps in a shadcn `<Tooltip>` with day + `Succeeded: N · Failed: N · Other: N`. When false (default), native-title behavior preserved. `Dashboard.tsx` unchanged.
|
||||
|
||||
The rich-tooltip branch uses a `<button>` as `TooltipTrigger`'s child (not `<div>`) for keyboard focusability.
|
||||
|
||||
**Lenses.** Recognition over Recall (legend), Information Scent (breakdown tooltips), Doherty Threshold (shadcn delayDuration=0 vs native ~500ms), Jakob's Law.
|
||||
|
||||
### 2.8 Selector over drag for priority affordance (Phase 3c)
|
||||
|
||||
Used the existing `PriorityIcon` component's built-in popover picker. Passing `onChange` turns its icon into a shadcn `<Popover>` with 4 keyboard-accessible `<Button>` options. Used in production at `IssueDetail.tsx` and `IssueProperties.tsx`.
|
||||
|
||||
**Why selector over drag** (deferred from concept §4):
|
||||
- Fitts's Law — click-target 16×16 is faster than a drag gesture across four buckets. Priority change is frequent, low-stakes.
|
||||
- Hick's Law — 4 discrete choices are equivalent whether drag-targets or popover items; popover is more compact.
|
||||
- Vertical-space cost — drag-to-bucket would need a 4-column Kanban layout, ~250px before any tasks render. Would break the monitoring-no-scroll goal.
|
||||
- Keyboard accessibility out of the box — popover with 4 `<button>` items is keyboard-navigable without the `@dnd-kit` keyboard-sensor layer.
|
||||
|
||||
Optimistic update scoped to `[...queryKeys.issues.list(companyId), "participant-agent", agentId]` — the exact cache slice driving `inFlightTasks` → `assignedIssues`. On error: revert snapshot + shadcn error toast. On settled: invalidate full prefix for convergence.
|
||||
|
||||
Brief highlight flash (`bg-accent/30` for 1000ms via local state + existing `transition-colors`) on the changed row. Highlight travels with the row when it repositions by the priority-DESC sort — visual closure for the reposition action.
|
||||
|
||||
Click-stopper `<span>` around `PriorityIcon` to prevent the popover-trigger click from bubbling to the EntityRow's parent `<Link>`.
|
||||
|
||||
**Lenses.** Reach-for-what-exists-first (PriorityIcon already ships the picker), Jakob's Law (matches IssueDetail pattern), Doherty Threshold (optimistic update <100ms), Causality + Zeigarnik (reposition + highlight closes the loop).
|
||||
|
||||
### 2.9 Unified costs module (Phase 3d)
|
||||
|
||||
Merged the Phase 0 split (KPI strip + per-run table, two bordered cards) into one bordered container. Summary block (`p-4`) with a one-liner `$X.XX cumulative · N in · N out · N cached` sits above a borderless table; a single `border-t` on the table's `thead` row separates summary from detail.
|
||||
|
||||
Title moved inside the module ("Costs — session" as an eyebrow). Matches `ChartCard`'s self-contained pattern; the external `<h3>Costs</h3>` in `AgentOverview` was removed.
|
||||
|
||||
Session cost and token totals get one line with differentiated weight (cost at `text-sm font-semibold`, tokens at `text-xs text-muted-foreground`). Middot separators; `flex flex-wrap` for narrow viewports.
|
||||
|
||||
Empty states:
|
||||
- Neither runtime state nor runs-with-cost → one compact card with eyebrow + "No cost data yet."
|
||||
- Runtime state without runs-with-cost → summary only.
|
||||
- Runs-with-cost without runtime state → table only. No synthesized summary fallback — session vs all-runs would mix vocabularies.
|
||||
- Both → both, separated by the table-header border-t.
|
||||
|
||||
**Lenses.** Common Region (one bounded area reads as one module), Prägnanz (simpler shape), Progressive Disclosure (glanceable summary, detail below), Information Scent (summary scents to the table), Aesthetic-Usability Effect (single dense line less noisy than 4-cell grid).
|
||||
|
||||
### 2.10 Module heading normalization (Phase 4 polish)
|
||||
|
||||
All module labels use `text-xs font-medium text-muted-foreground` (the `ChartCard` title pattern). Previously: ChartCard used that pattern; Costs eyebrow was missing `font-medium`; In-flight h3 was `text-sm font-medium` (bigger, not muted). One tier now, visual rhythm cleaner.
|
||||
|
||||
**Lens.** Visual restraint (Section 6 of rubric) — one type size for module labels, the page reads as one aesthetic system.
|
||||
|
||||
---
|
||||
|
||||
## 3. DS deviations — "pass with explicit justification"
|
||||
|
||||
Per rubric Section 5: a run that deviates and documents its reasoning passes; silent deviation fails.
|
||||
|
||||
### 3.1 `rounded-full` on progress-bar track and activity-dot
|
||||
|
||||
Sharp-corners default (`rounded-none`) applies to surfaces. A 2px status dot and a progress-bar fill are glyphs, not surfaces. `rounded-full` is the conventional shape for both and matches existing `BudgetPolicyCard` / `agentStatusDot` usage.
|
||||
|
||||
### 3.2 Progress bar colors `bg-red-400 / bg-amber-300 / bg-emerald-300`
|
||||
|
||||
Raw-palette classes. Deliberate match to `BudgetPolicyCard`'s treatment so the dashboard progress bar and the Budget tab card read as one family. Switching to `--signal-success` locally would fork the budget visual vocabulary. "No new tokens" policy prevents proposing one.
|
||||
|
||||
### 3.3 `bg-cyan-400 animate-pulse` on the activity-state dot
|
||||
|
||||
Comes from `agentStatusDot["running"]` — unchanged from Phase 0 baseline, used consistently across the app (Agents list, Design Guide showcase). Deliberate reuse, not new drift.
|
||||
|
||||
### 3.4 `PopoverContent` with `rounded-md`
|
||||
|
||||
Inherited from the shadcn `Popover` primitive. The dashboard aesthetic is `rounded-none`, but shadcn primitives are an accepted exemption per Step 0 DS policy. Overriding would require local className overrides on every popover usage across the codebase — not scoped to Phase 3c. Same for `Button` inside the popover (likely `rounded-md` from shadcn Button variants).
|
||||
|
||||
### 3.5 `bg-accent/30` for the priority-change highlight flash
|
||||
|
||||
`bg-accent` is an existing DS token; `/30` opacity is standard Tailwind modifier syntax. No new token, no new raw-palette drift.
|
||||
|
||||
### 3.6 `text-cyan-600 / text-red-600` on activity-pill text
|
||||
|
||||
Already present in the dashboard region via `runStatusIcons` (`AgentDetail.tsx:104–111`). Reuse of existing palette classes, not new drift.
|
||||
|
||||
---
|
||||
|
||||
## 4. Findings (not fixed, surfaced for comparison writeup)
|
||||
|
||||
Observations that aren't Run A's scope to resolve. Flagged for the post-experiment decision pile.
|
||||
|
||||
### 4.1 `agent.status` cache-coherence gap
|
||||
|
||||
Root cause behind the Phase 2 "dashboard says Idle while Live Run card says running" bug. The `agent` detail query keys on the URL ref (`conversation-tester`) while `LiveUpdatesProvider.onAgentStatus` invalidates via UUID (`queryKeys.agents.detail(agentId)`). React Query's prefix-match fails; the cached `agent.status` drifts stale during active runs. Derivation (see 2.1) sidesteps the symptom; the underlying gap remains. Fix is out-of-scope for Run A (would touch `LiveUpdatesProvider.tsx` or the query-key shape — both outside the three in-scope functions).
|
||||
|
||||
### 4.2 Header/dashboard cyan-vs-blue hue drift
|
||||
|
||||
Phase 0 discovery noted that the page-header's mobile-only live indicator uses `bg-blue-400` / `bg-blue-500` while the inline dashboard card used `bg-cyan-400`. After Phase 3a's cyan relocation, the activity pill uses cyan (via `agentStatusDot`), but the header still uses blue. Out of scope for Run A (header is outside the redesign region).
|
||||
|
||||
### 4.3 `--signal-warning` missing from the token set
|
||||
|
||||
The budget card's warn state uses raw `bg-amber-300` (via `BudgetPolicyCard` pattern). A `--signal-warning` token would make this explicit. Step 0 DS policy prevents proposing new tokens mid-experiment, so we mirror the existing BudgetPolicyCard treatment and flag the gap here.
|
||||
|
||||
### 4.4 "Failed task" vs "failed run" terminology gap
|
||||
|
||||
Rubric Section 2 Debug says "identify failed *tasks*, see when each one failed." Failed runs are tracked on `HeartbeatRun`; tasks (Issues) don't have a direct failure state. A failed task is usually represented by its failed run, but the mapping isn't tight. Brief-terminology decision for a future iteration.
|
||||
|
||||
### 4.5 Per-task cost attribution is weak
|
||||
|
||||
`HeartbeatRun` has no reliable link to `Issue` at the type level. The unified costs table shows cost per run (run id + tokens + dollars), but users can't answer "which task consumed the most" from this view. A real per-task rollup would need either an `issueId` on heartbeat runs or a `contextSnapshot` join — both data-model changes. Matters for rubric Section 2 "Accountability (spend)": we serve "which runs were expensive" (engineer) but not "which tasks were expensive" (manager).
|
||||
|
||||
### 4.6 `agentStatusDot["idle"]` and `agentStatusDot["paused"]` are both `bg-yellow-400`
|
||||
|
||||
Existing DS ambiguity in `status-colors.ts`. Two distinct states, identical dot color. Users reading the label ("Idle" vs "Paused") resolve the ambiguity, but the dot alone isn't state-distinguishing. `status-colors.ts` is explicitly locked by brief — we can't fix.
|
||||
|
||||
### 4.7 No client-side permission gate for issue priority edits
|
||||
|
||||
The dashboard priority selector matches the existing app pattern (`IssueDetail.tsx`, `IssueProperties.tsx`): any authenticated user can change priority; server-side rejection surfaces via error toast. If product wants tighter client gating, it's a new requirement + a server-side permission check. Deliberate continuity, not silent granting.
|
||||
|
||||
### 4.8 Nested-interactive DOM: `<Link>` > `<PopoverTrigger as button>`
|
||||
|
||||
On the in-flight task rows, the priority icon's popover trigger sits inside `EntityRow`'s `<Link>`. Screen-reader-visible warning pattern (nested interactive). The existing `EntityRow` component assumes this pattern is acceptable given its `to`-prop contract. Functional impact is minimal — keyboard focus flows correctly, click propagation is stopped — but a11y auditors will flag it. Scoped fix (splitting the row into priority-button + title-link siblings) would remove the row-wide click affordance. Accepted compromise.
|
||||
|
||||
### 4.9 "Cumulative session" vs "month-to-date observed" vocabulary overlap
|
||||
|
||||
The costs module shows `$X.XX cumulative` (session-lifetime, from `runtimeState.totalCostCents`). The hero budget shows `$A of $B` (calendar-month-to-date observed, from `budgetSummary`). Two cost numbers at different scopes on the same dashboard. Each explicitly labeled ("session" vs "this month"), but legible-but-fragile vocabulary — a user glancing at the dashboard could conflate them.
|
||||
|
||||
---
|
||||
|
||||
## 5. Deferred items — Phase 4 consciously left alone
|
||||
|
||||
### 5.1 Chart sparseness
|
||||
|
||||
Chart band at full width has ~80px of bar content inside a wide card. The "thin" impression from Phase 3b polish has been partially resolved by Phase 3d's tighter costs module (below-fold rebalanced). Not densified here because densification adds decoration that Section 6 "Visual restraint" scores against; the chart's job is glanceable, not comprehensive. The subtitle (`N% success`) already carries the summary signal.
|
||||
|
||||
### 5.2 Latest Run card right-edge alignment
|
||||
|
||||
The hero's 75/25 split creates a visual step between the left-zone's 75% right edge and the full-width modules below. Accepted as deliberate rhythm: the hero's 75/25 is about dividing "now" (current work) from "budget state"; the modules below are about chart / list / costs, none of which share the hero's duality. Different structural treatments for different content. Forcing alignment would require either shrinking the chart/in-flight/costs to 75% (loses module width budget) or expanding the hero to full width with internal re-composition (undoes the 3a alignment work).
|
||||
|
||||
### 5.3 Nested-interactive a11y on in-flight rows
|
||||
|
||||
See 4.8 above. Considered refactoring the row inline (priority-button + title-link as siblings rather than nested) but the refactor loses whole-row click affordance. Accepted as a compromise matching the existing `EntityRow` pattern.
|
||||
|
||||
### 5.4 Chart-bar keyboard tab count (14 stops)
|
||||
|
||||
`richTooltips` renders each chart bar as a focusable `<button>` for keyboard-accessible tooltip content. 14 tab stops before a keyboard user can reach the in-flight list below is a lot. Not fixed — the per-day breakdown is a legitimate keyboard-access detail for Section 2 Debug users. Adding `tabIndex={-1}` would remove the keyboard path to the tooltip content.
|
||||
|
||||
---
|
||||
|
||||
## 6. Rubric alignment
|
||||
|
||||
Self-scoring lives in [`run-a-self-check.md`](./run-a-self-check.md), walked item by item with one-sentence reasoning per the rubric's scoring template. The summary:
|
||||
|
||||
```
|
||||
Section 1 (Primary goal): PASS
|
||||
Section 2 (Mode coverage): 4 / 4 passed
|
||||
Section 3 (Actions): 2 / 2 passed
|
||||
Section 4 (Data): 3 / 3 passed
|
||||
Section 5 (DS compliance): 8 / 8 passed
|
||||
└─ 4 clean pass + 4 pass-with-justification (§3 above)
|
||||
Section 6 (Qualitative): Improvement: 4/5 | Hierarchy: 4/5 | Restraint: 4/5 | Live-run: 4/5
|
||||
Section 7: See self-check
|
||||
```
|
||||
|
||||
Rationale for each score is in `run-a-self-check.md`. Section 6 scores are deliberately conservative — a run that claims 5/5 across the board without acknowledging real tradeoffs is a credibility problem.
|
||||
|
||||
---
|
||||
|
||||
## 7. Friction log highlights
|
||||
|
||||
Full friction log is in the self-check document. Top-three worth capturing for the comparison writeup:
|
||||
|
||||
1. **Concept evolves under pressure.** The Phase 0 concept specified a cyan-preserved card, a 55/45 hero, 4-up charts dropped to 1, an icon-strip for recent runs, and a hero pill. The Phase 4 outcome has cyan-relocated (not preserved), 75/25 hero (not 55/45), icon strip replaced by a compact feed, and hero pill promoted to hero-header. Each change was a smoke-test-informed refinement, not a pre-commitment violation. The concept as a document captured *intent*; implementation surfaced *learnings*. Both runs (A and B) are likely to see similar concept drift — it's a feature of the process, not a bug.
|
||||
|
||||
2. **Pre-existing gaps surface under consolidation.** The chart legend + rich-tooltip gap was pre-existing on `RunActivityChart` in `ActivityCharts.tsx` — it never had a legend or a proper tooltip. In the 4-up grid, it was easy to miss; as the sole chart, the gap was immediately visible. Run A inherited and fixed it in a scope-preserving way. Run B may encounter similar inherited-gap patterns; worth flagging as a class of finding.
|
||||
|
||||
3. **Shared-component boundaries matter.** Three shared-component changes were made: `ChartLegend` exported (additive), `RunActivityChart` got opt-in `richTooltips` (additive, backward-compat), `PriorityIcon` reused unchanged. Each change was deliberately scope-preserving for the other consumer (`pages/Dashboard.tsx`). The inverse pattern — restructuring shared components for a single consumer — would have been easier to write and worse for the codebase. Run B should face similar decisions; the right heuristic is "additive opt-in props > narrowed behavior changes."
|
||||
194
projects/agent-profile-dashboard/run-a-plan.md
Normal file
194
projects/agent-profile-dashboard/run-a-plan.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# Run A — Execution plan
|
||||
|
||||
Plan for the Claude Code path redesign of the Agent Profile Dashboard. Grounded in the updated brief + rubric (baseline `53db6095`) and the discovery document (`run-a-discovery.md`).
|
||||
|
||||
This is a plan for your review, not a committed path. Pause for explicit approval before Phase 2 begins.
|
||||
|
||||
---
|
||||
|
||||
## Shape of the work
|
||||
|
||||
Five phases. Phase 0 is done; Phase 1 is the next block of work and produces a paper design you approve before any TypeScript changes. Phase 2 lands the structural frame. Phase 3 redesigns the modules inside that frame. Phase 4 polishes, writes run-notes, and self-scores against the rubric.
|
||||
|
||||
Four mandatory checkpoints — one before each phase that mutates code, plus a final one before I call the run done.
|
||||
|
||||
| Phase | Artifact | Rough size | Opens | Closes |
|
||||
|---|---|---|---|---|
|
||||
| 0 | Discovery (done) | n/a | — | this-plan checkpoint |
|
||||
| 1 | Concept sketch (markdown, no code) | 2–3 hours | concept checkpoint | concept-approved |
|
||||
| 2 | Structural implementation in `AgentDetail.tsx` | 3–5 hours | structure checkpoint | structure-approved |
|
||||
| 3 | Module-level redesign | 5–8 hours, iterated per module | module-level checkpoints (×4) | polish-ready |
|
||||
| 4 | Polish + `run-a-notes.md` + rubric self-check | 2–3 hours | final checkpoint | run-a-complete |
|
||||
|
||||
Sizes are my best guesses. A big design pivot in Phase 1 can redraw the timeline for 2–3.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Concept (paper only)
|
||||
|
||||
**Deliverable:** `projects/agent-profile-dashboard/run-a-concept.md`. Not code.
|
||||
|
||||
### What the concept answers
|
||||
|
||||
1. **Hero frame** — what goes in the monitoring-no-scroll 1440px band? Candidates: (a) current activity state + current run/issue + recent health pills + budget position, all in one dense horizontal strip; (b) a single hero card that changes shape based on live/idle; (c) a split — "now" on the left, "budget position" on the right. Pick one, explain tradeoffs.
|
||||
2. **Chart consolidation strategy.** Four charts today; the brief calls them uneven value. Options: keep 4 (fail monitoring goal), drop 2 and merge 2 (most likely), consolidate to 1 richer chart, hide behind a tab/accordion. Paper-sketch the replacement surface and name which charts survive.
|
||||
3. **Budget position placement.** Embed in hero (Section 1 item 4 requires no-scroll visibility). Decide shape — progress bar, utilization %, `amount / limit` pair, or combination. Draws from `BudgetPolicyCard` existing treatment for consistency.
|
||||
4. **Change-priority affordance.** Two concrete options already in the brief (drag between buckets vs. explicit selector); I'll recommend one in the concept with reasoning, based on DS precedent (`KanbanBoard.tsx` uses drag for status changes — drag-across-buckets for priority is the mirror pattern).
|
||||
5. **Session burn rate placement.** Brief says "may live in a secondary surface" — propose how it integrates with the redesigned costs section. Not required in monitoring-no-scroll band.
|
||||
6. **Live-run signal treatment.** Preserve cyan or propose a replacement. I'm inclined to keep cyan because it's the canonical "this is alive" signal across the app (header mobile indicator, workspace-operation status); replacing it introduces inconsistency. Concept defends this.
|
||||
7. **Recent health representation.** Today it's implicit in the charts. Section 1 item 3 requires it visible in monitoring frame. Probably 3–5 status dots or a compact run strip showing the most recent N runs with their outcomes. Sketch which.
|
||||
|
||||
### What the concept does NOT do
|
||||
|
||||
No chart tokenization. No component extraction. No new tokens. No changes outside `AgentOverview` / `LatestRunCard` / `CostsSection`. No edits to `status-colors.ts` or DS files.
|
||||
|
||||
### Checkpoint — concept review
|
||||
|
||||
I pause after the concept is written. You approve, revise, or reject. No code has been written at this point so revisions are cheap.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Structural implementation
|
||||
|
||||
Small, reviewable, and *structural only*. I turn the approved concept into working TypeScript that you can see in the browser. Module styling is deliberately minimal in this phase — the goal is to prove the frame fits the brief before investing in the visual pass.
|
||||
|
||||
### What lands
|
||||
|
||||
1. **Prop-passing change:** thread `agentBudgetSummary` (already computed in `AgentDetail`) into `AgentOverview` as a new optional prop. One-line callsite edit + typed prop.
|
||||
2. **Top-level layout rewrite** of `AgentOverview` to match the approved concept (hero band + secondary bands).
|
||||
3. **Stub-level module edits:** `LatestRunCard`, charts, priority-change, costs each rendered in their approved positions but with current styling carried over mostly intact. Deliberately unfinished — the module-redesign pass in Phase 3 is where the visual work happens.
|
||||
4. **Monitoring-frame smoke test:** load the dashboard at 1440px viewport with a seeded dataset (live run + idle agent) and confirm no-scroll-required pass by measurement, not feel.
|
||||
|
||||
### What does not land in this phase
|
||||
|
||||
- No chart merges / consolidation (those are module-level work for Phase 3).
|
||||
- No new visual affordances for priority change — just a placeholder (e.g., a disabled `Change priority →` link) to reserve space.
|
||||
- No polish, no copy, no final typography.
|
||||
|
||||
### Checkpoint — structure review
|
||||
|
||||
You open the running dashboard in a browser and confirm the frame reads the way the concept promised. Crucially: does the 1440px no-scroll test pass? If not, this is the cheap moment to fix it before modules are styled. Revisions here cascade cheaply; after Phase 3 they're expensive.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Module-level redesign
|
||||
|
||||
Four modules, iterated in dependency order. Each module gets its own mini-checkpoint so we don't have to unwind a pile of work if a module doesn't land.
|
||||
|
||||
### 3a. Hero (live/latest run + activity + recent health + budget position) — ~2 hours
|
||||
|
||||
- Redesigns what was `LatestRunCard` plus new budget position + recent-health elements.
|
||||
- Live-run visual treatment nailed down (cyan pulse preserved or approved replacement).
|
||||
- Budget position rendered; decide on `BudgetPolicyCard` reuse vs. inlined variant.
|
||||
- Recent-health dots / strip rendered from `heartbeats` array.
|
||||
- **Sub-checkpoint:** hero is the load-bearing surface for Section 1. Review after it lands.
|
||||
|
||||
### 3b. Charts (consolidation executed) — ~1.5 hours
|
||||
|
||||
- Whatever the concept decided: merge, drop, or pull behind secondary view.
|
||||
- Tokens remain hardcoded hex (Section 5 DS compliance: no chart tokenization).
|
||||
- Empty states reviewed — the `rounded-sm` placeholder (`ActivityCharts.tsx:113`) in-scope only if chart usage changes.
|
||||
- **Sub-checkpoint:** charts merged as planned.
|
||||
|
||||
### 3c. Priority-change affordance (on the Recent Issues surface, or wherever concept placed it) — ~2 hours
|
||||
|
||||
- If drag: reuse `@dnd-kit` pattern from `KanbanBoard.tsx`. Four-bucket `SortableContext`s.
|
||||
- If explicit selector: dropdown or segmented control per row.
|
||||
- Wires to `issuesApi.update(id, { priority })`. Optimistic update + query invalidation for `queryKeys.issues.list`.
|
||||
- Keyboard path: if drag is the primary affordance, keyboard fallback is mandatory for rubric item "drag-and-drop or keyboard-reorderable." `@dnd-kit` supports keyboard sensors.
|
||||
- **Sub-checkpoint:** affordance works end-to-end.
|
||||
|
||||
### 3d. Costs (unified, with session burn rate integrated) — ~1.5 hours
|
||||
|
||||
- Merge KPI strip + per-run table into one coherent surface.
|
||||
- Session burn rate computed from `runtimeState.totalCostCents` or derived per-run; rendered as a secondary signal inside this module (not in monitoring frame).
|
||||
- **Sub-checkpoint:** costs module works.
|
||||
|
||||
Between sub-checkpoints I only report briefly; you greenlight each before I move on.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Polish, run-notes, rubric self-check
|
||||
|
||||
### What lands
|
||||
|
||||
1. **Visual restraint pass.** Typography, spacing, density. No decorative additions. Match Step 0 DS policy aggressively. Section 6 "Visual restraint" is scored here.
|
||||
2. **Accessibility audit.** Keyboard navigation through priority-change affordance, hover states, focus rings, aria labels where needed.
|
||||
3. **`projects/agent-profile-dashboard/run-a-notes.md`** with:
|
||||
- DS deviations and their justifications (rubric Section 5 is "pass with explicit justification" — this file is the justification channel).
|
||||
- Implicit decisions made during the run: how I defined "in-flight tasks," chart consolidation choices, live-run signal choice, session-burn-rate placement.
|
||||
- Header-is-source-of-truth-for-agent-level-controls note (paused state), per your spirit interpretation decision.
|
||||
- Known tensions / deferred items.
|
||||
4. **Rubric self-check.** Walk each rubric section and mark pass/fail with one-sentence reasoning, per rubric instructions. Section 1 and Section 3 "change task priority" are the items most at risk.
|
||||
|
||||
### Final checkpoint
|
||||
|
||||
I present the running dashboard, the run-notes, and the self-scored rubric. You review and decide: accept, revise, or redo a phase.
|
||||
|
||||
---
|
||||
|
||||
## Risk areas
|
||||
|
||||
Ordered by how likely they are to bite, not by impact.
|
||||
|
||||
1. **Fit-without-scrolling on 1440px is the single biggest technical risk.** Budget position, recent health, current work, and activity state all in the monitoring band puts pressure on vertical space. If the hero band ends up 200px+ tall, there won't be room for anything else above the fold. Mitigation: bail out of chart consolidation (push charts below the fold entirely) before we compromise on hero content.
|
||||
2. **"In-flight tasks" is still squishy.** Even with the priority-change reframe, the set of issues the dashboard targets has to be defined. Candidate: issues with status `todo` / `in_progress` / `blocked` assigned to this agent. I'll propose a concrete filter in the concept; you approve before I write code against it.
|
||||
3. **Priority drag across 4 buckets could feel heavy.** Dragging a card from the bottom of "medium" to "critical" is a big gesture for a 4-bucket enum. Mitigation: prototype the segmented-control alternative in the concept so you can pick which one to ship. This is concretely a brief-intent-ambiguity risk — the brief says "drag-and-drop **between priority buckets** or explicit priority selector," so both are permitted.
|
||||
4. **Chart consolidation could drop a chart someone loves.** The `Issues by Priority` and `Issues by Status` charts are reachable-elsewhere candidates (they might duplicate the Issues list view). `Run Activity` + `Success Rate` are harder to drop — they're uniquely dashboard-shaped. Mitigation: the concept names which charts survive, and I cross-check what the other surfaces cover before proposing drops.
|
||||
5. **Session burn rate "live" semantic is underspecified.** The brief requires both metrics but only the per-run cost data in `heartbeats` gives useful recent-window granularity. `runtimeState.totalCostCents` is session-lifetime, not rate. I'll propose "last N runs rolling cost" in the concept and you can push back if that's not what you meant.
|
||||
6. **Status-color routing.** The existing `runStatusIcons` map uses raw-palette tones inline. The redesign is tempted to route everything through `StatusBadge`/`StatusIcon` for DS consistency — but `StatusBadge` only covers the status values in its catalog; run statuses include `scheduled_retry` and `timed_out` which may or may not be in `status-colors.ts`. If they aren't, and I can't modify `status-colors.ts`, I may have to keep the inline raw-palette map. Flagging so it's not a surprise in Phase 4.
|
||||
7. **Scope creep temptation: header-vs-dashboard.** The color-drift cyan-pulse issue (header uses `bg-blue-*`, inline card uses `bg-cyan-*`) is real and visible. Fixing requires editing the header. Out of scope for Run A. I'll note it in run-notes and not touch it.
|
||||
8. **`rounded-sm` in `ActivityCharts.tsx:113`.** Pre-existing, inside a shared chart component I shouldn't modify if the charts are dropped. If the charts stay and this empty-state placeholder is removed because we take a different approach, the `rounded-sm` goes with it. If the charts stay and the placeholder stays, it's a noted-but-accepted pre-existing state.
|
||||
9. **Stale DS-policy bullets in the brief about pause** (`## Design system policy` bullet on "Pause-agent uses neutral button styling" and the entire `## Known deferrals` section about `--signal-warning`). These now reference a scope the brief has removed. Not a run-A execution risk — just a cleanup item for a follow-up brief tightening pass. Flagging in case you want a tiny cleanup commit before Phase 1.
|
||||
|
||||
---
|
||||
|
||||
## What "done" looks like — rubric mapping
|
||||
|
||||
Each rubric section mapped to the phase that delivers it. Phases are cumulative — by the end of Phase 4, every item has been touched.
|
||||
|
||||
| Rubric section | Delivered by | Notes |
|
||||
|---|---|---|
|
||||
| Section 1, item 1 (activity state) | Phase 3a (hero) | Relies on `agent.status` being surfaced in-frame |
|
||||
| Section 1, item 2 (current work) | Phase 3a (hero) | Live-run card gives this; idle state needs an empty treatment |
|
||||
| Section 1, item 3 (recent health) | Phase 3a (hero) | New element, not in current dashboard |
|
||||
| Section 1, item 4 (budget position in-frame) | Phase 2 (structural) + Phase 3a (hero styling) | This is the brief's new hard requirement |
|
||||
| Section 2, Operations | Phase 3c (priority affordance) | The priority-change surface IS the operations-mode answer |
|
||||
| Section 2, Accountability (spend + tokens) | Phase 3d (unified costs) | Per-run data lets engineers investigate |
|
||||
| Section 2, Debug | Phase 3a (hero) + Phase 3d (costs table provides failed-run context) | Failed runs must be 1-interaction-reachable |
|
||||
| Section 3, Change task priority | Phase 3c | Drag or selector — concept picks |
|
||||
| Section 3, Navigate to detail | Phase 3a (hero link) + Phase 3c (issue rows) | Already working today; preserved |
|
||||
| Section 4, Chart data reachable | Phase 3b | Concept decides "reachable where" — probably a link-out or expander |
|
||||
| Section 4, Cost + token both present | Phase 3d | Unified costs module shows both |
|
||||
| Section 4, Recent issues reachable | Phase 3c (priority rows) | The priority-change surface IS the recent-issues list |
|
||||
| Section 5, Rounded corners | Phase 4 audit | Default sharp; any `rounded-lg`/`xl`/`md` flagged in run-notes |
|
||||
| Section 5, No new tokens | Phase 4 audit | Confirmed by git diff; tokens file untouched |
|
||||
| Section 5, No component extraction | Phase 4 audit | Confirmed by git diff; no new files under `ui/src/components/` |
|
||||
| Section 5, No chart tokenization | Phase 3b | Deliberately preserved |
|
||||
| Section 5, No raw-palette drift | Phase 4 audit | Confirmed by diff review against pre-existing raw palette |
|
||||
| Section 5, Pause neutral | Phase 4 audit | Vacuous — pause is out of scope, so "pass" by absence |
|
||||
| Section 5, Live-run visual distinction | Phase 3a | Concept decides cyan vs. replacement |
|
||||
| Section 5, No out-of-scope changes | Phase 4 audit | Confirmed by git diff limited to `AgentDetail.tsx` dashboard region |
|
||||
| Section 6, Improvement over current | Phase 4 self-score | Most important qualitative axis |
|
||||
| Section 6, Hierarchy clarity | Phase 3a + Phase 4 restraint pass | Concept decides the three layers |
|
||||
| Section 6, Visual restraint | Phase 4 polish | The Swiss-minimal scoring axis |
|
||||
| Section 6, Live-run signal strength | Phase 3a | Scored against concept-chosen treatment |
|
||||
| Section 7 (friction log, not scored) | Phase 4 `run-a-notes.md` | Captured throughout, organized at end |
|
||||
|
||||
---
|
||||
|
||||
## Open questions before Phase 1 begins
|
||||
|
||||
Not blockers; noting so Phase 1 isn't littered with these.
|
||||
|
||||
1. **Stale DS-policy brief bullets on pause** — want a tiny cleanup commit before Phase 1, or accept it and let run-notes handle it in Phase 4?
|
||||
2. **Headless smoke test for 1440px no-scroll** — is it okay if I manually open a browser and measure, or do you want a Playwright-style automated check? Manual is much cheaper.
|
||||
3. **Seeded dataset for testing** — do you have a specific agent I should point at, or should I rely on whatever dev data is in my local db?
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Phase 0 complete. Phase 1 is concept-only and opens at the next checkpoint. Phase 2 onward requires concept approval. The plan treats the hero band as the load-bearing surface for Section 1 pass; all other modules serve secondary modes without crowding the hero. The three biggest risks (1440px fit, in-flight-tasks definition, chart consolidation) get early decisions in Phase 1 before any TypeScript is written.
|
||||
|
||||
Waiting on your read.
|
||||
168
projects/agent-profile-dashboard/run-a-self-check.md
Normal file
168
projects/agent-profile-dashboard/run-a-self-check.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# Run A — Rubric self-check
|
||||
|
||||
Walk each rubric item with pass/fail and one-sentence reasoning. Scoring template per `rubric.md`. Reference document: [`run-a-notes.md`](./run-a-notes.md).
|
||||
|
||||
Tested against `http://localhost:3000/SKI/agents/conversation-tester/dashboard` at 1440px / 100% zoom.
|
||||
|
||||
---
|
||||
|
||||
## Scoring summary
|
||||
|
||||
```
|
||||
Section 1 (Primary goal): PASS
|
||||
Section 2 (Mode coverage): 4 / 4 passed
|
||||
Section 3 (Actions): 2 / 2 passed
|
||||
Section 4 (Data): 3 / 3 passed
|
||||
Section 5 (DS compliance): 8 / 8 passed
|
||||
└─ 4 clean pass + 4 pass-with-justification
|
||||
Section 6 (Qualitative): Improvement: 4/5 | Hierarchy: 4/5 | Restraint: 4/5 | Live-run: 4/5
|
||||
Section 7: See friction log below
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Section 1 — Primary goal
|
||||
|
||||
- [x] **Monitoring test** (1440px, no scrolling, all four visible):
|
||||
1. **Activity state** — hero-level activity pill at top-left with colored dot + state label (`Running` / `Idle` / `Paused` / `Error`). Visible at page top. **✓**
|
||||
2. **Current work** — Latest Run card in hero left zone, showing status icon + badge + run id + source + relative time + summary excerpt. Visible. **✓**
|
||||
3. **Recent health** — prior-runs compact feed (3 one-line rows) directly below the Latest Run card in the hero left zone, each showing colored status icon + run id + status label + relative time. Visible. **✓**
|
||||
4. **Live economics — budget position** — budget card in hero right zone showing `$spent of $limit`, `utilization%`, colored progress bar, `$remaining · resets in N days`. Visible. **✓**
|
||||
|
||||
**Result: PASS.** All four items visible within the hero band without scrolling at 1440px / 100% zoom. Verified against the conversation-tester agent (has a real budget policy + recent run history).
|
||||
|
||||
---
|
||||
|
||||
## Section 2 — Mode coverage
|
||||
|
||||
- [x] **Operations.** In-flight tasks list at the bottom half of the dashboard has a priority icon on each row that opens a 4-option popover; selecting a new priority mutates the issue and optimistically repositions the row. No page navigation required. **PASS.**
|
||||
- [x] **Accountability (spend).** Unified costs module shows per-run cost rows with 10-row cap; users can spot expensive runs; each run row links via run id to the run detail (which exposes the issue context). One caveat: cost is per-*run*, not per-*task* (finding 4.5 — data-model gap). Still meets the "granularity to spot outliers" bar. **PASS (with note).**
|
||||
- [x] **Accountability (tokens).** Costs summary gives `N in · N out · N cached` for the session; per-run table gives per-run Input + Output columns. Run id is mono-formatted and navigates to run detail for context. **PASS.**
|
||||
- [x] **Debug.** Prior-runs feed in the hero shows any recent failed runs with colored X icon + "failed" label + relative time; clicking a row → run detail (1 interaction). The Run Activity chart also renders failed runs in red. **PASS.**
|
||||
|
||||
**Result: 4 / 4 passed.**
|
||||
|
||||
---
|
||||
|
||||
## Section 3 — Action affordances
|
||||
|
||||
- [x] **Change task priority.** Explicit selector via popover on each in-flight task row, scoped to the top 7 in-flight tasks (filter: `status ∈ {todo, in_progress, blocked}`; sort: priority-DESC then updatedAt-DESC). Popover opens with 4 options; keyboard-accessible via Tab/Enter/Escape. Matches rubric's "drag-and-drop or explicit priority selector." **PASS.**
|
||||
- [x] **Navigate to detail.** Latest Run card = `<Link>` to run detail. Prior-run feed rows = `<Link>`s. In-flight task rows = `<Link>`s to issue detail. Chart bars (with `richTooltips`) are focusable buttons and click-tooltip-reveal for per-day breakdown. All link-based or equivalent. **PASS.**
|
||||
|
||||
**Result: 2 / 2 passed.** Rubric confirmed: Section 3 has 2 items (Pause was removed in the brief scope update).
|
||||
|
||||
---
|
||||
|
||||
## Section 4 — Data preservation
|
||||
|
||||
- [x] **Chart data reachable within 2 interactions.**
|
||||
- Run Activity + Success Rate: 0 interactions (chart + subtitle visible).
|
||||
- Issues by Priority: 2 interactions (click "View all →" in in-flight tasks header → priority filter in Issues page).
|
||||
- Issues by Status: 2 interactions (same path, status filter).
|
||||
- All ≤2. **PASS.**
|
||||
- [x] **Cost + token data both present on dashboard.** Costs module summary shows `$cumulative` + `N in · N out · N cached`. Per-run table has Input + Output columns and a Cost column. Both present. **PASS.**
|
||||
- [x] **Recent issues reachable within 2 interactions.** In-flight tasks list shows up to 7 rows directly; "View all →" link (1 interaction) navigates to `/issues?participantAgentId=<id>` for the full list. ≤2. **PASS.**
|
||||
|
||||
**Result: 3 / 3 passed.**
|
||||
|
||||
---
|
||||
|
||||
## Section 5 — DS compliance (pass with justification)
|
||||
|
||||
All eight items pass. Four are clean passes; four required justifications (documented in [`run-a-notes.md` §3](./run-a-notes.md#3-ds-deviations--pass-with-explicit-justification)). The rubric explicitly allows pass-with-justification — distinguishing it from clean pass is more honest than flattening everything to a uniform "pass."
|
||||
|
||||
- [x] **Rounded corners.** Default sharp (`rounded-none`) preserved on all dashboard surfaces. Deviations documented: `rounded-full` on glyphs (dot, progress-bar fill) — glyph-appropriate, not surface. `rounded-md` on `PopoverContent` and nested `Button` — inherited from shadcn primitive, accepted per Step 0 policy. **PASS with justification.**
|
||||
- [x] **No new tokens.** Confirmed via diff: zero additions to `ui/src/index.css` or `doc/design-system/tokens/*`. **PASS (clean).**
|
||||
- [x] **No component extraction.** `AgentOverview`, `LatestRunCard`, `CostsSection` all remain inline functions inside `ui/src/pages/AgentDetail.tsx`. No new files under `ui/src/components/` for these. **PASS (clean).**
|
||||
- [x] **No chart tokenization.** Chart colors remain hardcoded hex inside `RunActivityChart` (`bg-emerald-500`, `bg-red-500`, `bg-neutral-500`) and the inline `ChartLegend` items. No `--chart-*` token usage. **PASS (clean).**
|
||||
- [x] **No raw-palette drift.** Phase 2 introduced `bg-red-400`, `bg-amber-300`, `bg-emerald-300` on the budget card's progress bar — verified via `git show 0f5895e4:ui/src/pages/AgentDetail.tsx` that none of these existed in the dashboard region pre-Run A. These ARE new raw-palette references to the dashboard region. Justification: the trio mirrors `BudgetPolicyCard`'s existing app-wide treatment verbatim (`ui/src/components/BudgetPolicyCard.tsx:107–110`), keeping the budget visual vocabulary consistent across surfaces. No novel hex introduced — reuse of app-wide classes, narrowed to where they were needed. Shared-component additive changes (see next item) introduced no further drift. **PASS with justification.**
|
||||
- [x] **Pause uses neutral styling.** Pause is out of scope (handled by the page-chrome `PauseResumeButton`, not duplicated on dashboard). Vacuous pass. **PASS (clean).**
|
||||
- [x] **Live-run visual distinction preserved.** Two complementary signals when running: (a) activity pill has cyan pulse dot + cyan text (`text-cyan-600 dark:text-cyan-400`), (b) Latest Run card's internal `StatusIcon` is a cyan spinning `Loader2`. Clear distinction from idle/paused/error states. **PASS (clean).**
|
||||
- [x] **No out-of-scope changes.** Did NOT edit: other profile tabs, other pages, `status-colors.ts`, DS token files, plugin SDK. DID edit three things beyond the three in-scope inline functions, all in `ui/src/components/ActivityCharts.tsx`: (1) exported the previously-file-private `ChartLegend`; (2) added an opt-in `richTooltips?: boolean` prop to `RunActivityChart`; (3) imported shadcn `Tooltip` primitives. All three are strictly additive and backward-compatible — the other consumer `pages/Dashboard.tsx` continues to render with identical behavior (no `ChartLegend` import, no `richTooltips` prop passed → native-title tooltip fallback preserved). Rubric wording enumerates "other profile tabs, other pages, status-colors.ts, DS token files, plugin SDK" — shared chart components aren't explicitly listed, but spirit is "don't expand scope." Additive opt-in extensions respect the spirit. **PASS with justification.**
|
||||
|
||||
**Result: 8 / 8 passed (4 clean + 4 with justification).**
|
||||
|
||||
---
|
||||
|
||||
## Section 6 — Qualitative (1–5)
|
||||
|
||||
### Improvement over current state: **4 / 5**
|
||||
|
||||
**Reasoning.** Materially better for primary monitoring mode: the hero band now answers the four Section-1 questions at a glance, which the current dashboard doesn't (no activity-state element, no budget position, no consolidated recent-health surface). The priority-change affordance turns the dashboard into an operations surface, not just a read-only lens. The unified costs module halves the visual fragmentation of the Phase-0 split. Not rated 5 because the redesign is evolutionary rather than revolutionary — the existing dashboard is functional, and this redesign is cleaner, denser, and more monitoring-oriented, but it doesn't fundamentally rethink the concept (still a stacked vertical scroll of sections, still a lens over adjacent canonical surfaces). A 5/5 would require reconceiving what a dashboard is for — e.g., inverting the page so in-flight tasks + costs sit in the hero and run history becomes secondary — which the brief didn't ask for and the current interpretation respects.
|
||||
|
||||
### Hierarchy clarity: **4 / 5**
|
||||
|
||||
**Reasoning.** Three tiers distinct:
|
||||
- Primary: activity pill + Latest Run card (hero top-left, largest visual weight in monitoring frame).
|
||||
- Secondary: budget card + prior-runs feed + idle hint (hero remainder).
|
||||
- Tertiary: chart / in-flight list / costs (below hero).
|
||||
|
||||
Module heading normalization (Phase 4 polish) reinforced the tier boundaries. Not rated 5 because below-hero ordering (chart → in-flight → costs) is more "sequential sections" than "clear tier distinction" — a user scanning down sees three full-width modules at roughly equal visual weight. Section 2 "Accountability" (costs) sits at the bottom by spatial convention but isn't visually subordinated; a user debugging spend has to scroll past chart and in-flight to reach costs.
|
||||
|
||||
### Visual restraint: **4 / 5**
|
||||
|
||||
**Reasoning.** Swiss-minimal aesthetic preserved:
|
||||
- Sharp corners default.
|
||||
- No decorative additions.
|
||||
- Module headings normalized to one tier (Phase 4 polish).
|
||||
- Chromatic emphasis concentrated on the running state (cyan); rest of the UI is foreground/muted.
|
||||
- Raw-palette usage kept minimal and inherited (no new drift introduced).
|
||||
|
||||
Not rated 5 because two specific elements still carry decorative weight a stricter pass would cut: (a) the 1000ms `bg-accent/30` highlight flash on priority change — justified as closing-the-loop feedback, but a purist would argue the reposition itself is the feedback and the flash is additive; (b) the middot separators on the costs summary line (`·`) — readable, but pure spacing alone would also work. Neither is a clear error, and 4/5 vs 5/5 here is partly an approximation — restraint lives on a continuum, and claiming 5/5 would require defending that no further strip-down is possible.
|
||||
|
||||
### Live-run signal strength: **4 / 5**
|
||||
|
||||
**Reasoning.** When running: cyan pulse dot + cyan text in the hero-level activity pill; cyan spinning `Loader2` inside the Latest Run card. Two signals, different scopes (agent-level vs. run-level), neither decorative. Clear distinction from idle/paused/error states. Not rated 5 because the in-card spinner is small (`h-3.5 w-3.5`) — a viewer glancing very briefly may miss it if their eye lands on the card body first. The pill's cyan pulse carries the primary signal, but a user who's scanning the card (not the pill) could miss the liveness cue for a beat.
|
||||
|
||||
---
|
||||
|
||||
## Section 7 — Friction log (not scored)
|
||||
|
||||
### What did the run get stuck on?
|
||||
|
||||
- **Reorder scope** (Phase 0 → Phase 1 checkpoint). The initial brief suggested drag-and-drop reordering; discovery showed no `order`/`rank`/`position` field exists on `Issue` or `HeartbeatRun`. Resolved by reframing as a priority-bucket selector (4-value enum); concept §4 made this explicit.
|
||||
- **Alignment misalignment** (Phase 3a smoke-test). 55/45 and 50/50 hero splits both felt off — budget zone cramped, then primary/secondary baselines mismatched. Took two polish rounds (3a polish 1 + 3a polish 3 via Mode 2 exploration) to land on the hero-spanning pill above 75/25.
|
||||
- **Pre-existing chart gaps surfaced under consolidation** (Phase 3b). `RunActivityChart` had never had a legend and used a native-`title` tooltip only. Consolidation made the gap visible; Run A inherited and fixed it in a scope-preserving way.
|
||||
|
||||
### What decisions got made implicitly vs. explicitly?
|
||||
|
||||
- **Explicit:** the Mode-2 alignment exploration (Phase 3a polish 3) forced a written proposal before implementation. Selector-over-drag was called out explicitly in concept §4 with Fitts's Law + Hick's Law reasoning. Feed-vs-icon-strip was explicitly revised with a note in the concept doc.
|
||||
- **Implicit-but-documented:** "anyone who can view can edit" permission stance. Matches the existing app pattern but could be read as silent granting — flagged in notes (finding 4.7).
|
||||
- **Implicit-and-accepted:** nested-interactive DOM on in-flight rows. Accepted compromise; documented but not iterated.
|
||||
|
||||
### Biggest interpretation of the brief
|
||||
|
||||
Treating the hero band as the entire monitoring surface (not just activity + run) — pulling budget and recent-health into the band with Latest Run and activity pill. This is what the brief asked for ("A user in monitoring mode should be able to answer 'is this agent healthy and what is it doing right now' without scrolling") but required aggressive consolidation to deliver at 1440px. Alternative interpretations (budget as a secondary surface, recent-health as a chart-only signal) would have failed Section 1 item 4 or item 3.
|
||||
|
||||
### DS awareness
|
||||
|
||||
- Flagged and accepted: `rounded-full` on glyphs, `rounded-md` inherited from shadcn primitives, `bg-red-400 / -amber-300 / -emerald-300` raw-palette classes reused from `BudgetPolicyCard`.
|
||||
- Flagged and deferred: `--signal-warning` missing from tokens (finding 4.3), header cyan-vs-blue hue drift (finding 4.2), `agentStatusDot["idle"]` and `["paused"]` both yellow (finding 4.6).
|
||||
- Scope-respected: `status-colors.ts` untouched, no new tokens, no component extraction, no chart tokenization.
|
||||
|
||||
### Mobile implications
|
||||
|
||||
Considered: hero grid uses `grid-cols-1 lg:grid-cols-[3fr_1fr]` — stacks to one column at narrower viewports. Summary line in costs uses `flex-wrap` so the `$X cumulative · ...` content wraps gracefully. Chart uses `flex-1` on bars so the 14-bar strip shrinks to available width.
|
||||
|
||||
Not addressed: at very narrow widths (mobile), the chart bars become too thin to be useful (~12px each); the in-flight tasks list rows have tight content at <400px; priority popover may need different positioning. Mobile is out of scope per the brief; these are degradation items for a future responsive pass.
|
||||
|
||||
### Rubric experience
|
||||
|
||||
- **Easy to apply:** Sections 1, 3, 4, 5 — mostly binary pass/fail with clear criteria.
|
||||
- **Ambiguous-but-usable:** Section 2 "Accountability (spend)" — "enough granularity to spot outliers" is subjective; per-run cost is a reasonable proxy for per-task but not direct. The data-model gap is flagged (finding 4.5), so the passing grade is earned but noted.
|
||||
- **Genuinely hard to score:** Section 6 qualitative axes. "Improvement" requires comparing the redesign against the current dashboard — the reference screenshots are captured at 50% zoom, so my visual comparison is approximate. Scoring 4/5 across the board is a credibility choice; I could argue 5/5 on "Improvement" or 3/5 on "Visual restraint" depending on how the scoring is interpreted.
|
||||
- **Missing from rubric:** no explicit item for *keyboard efficiency* (tab-stop count), which matters for the chart-bar richTooltips tradeoff. No explicit item for *responsive degradation* (mobile is out-of-scope but awareness should be scoreable). No explicit item for *implementation discipline* (optimistic updates with proper error handling) — though Section 3 "Change task priority" covers it functionally.
|
||||
|
||||
### What would you add to the brief if running this again?
|
||||
|
||||
- An explicit note that **pre-existing gaps in shared components** are in-bounds if they surface under consolidation. The `RunActivityChart` legend + tooltip gap was pre-existing; I treated it as "polish this phase surfaced" rather than "out of scope." A future brief could codify the heuristic.
|
||||
- A note about **permission model stance**: "Client-side gating for issue-priority changes is not required; the run should mirror the existing app pattern and flag the choice." This would reduce ambiguity on whether silent granting is acceptable.
|
||||
- A concrete **1440px no-scroll test script** — e.g., "render the dashboard at 1440×900 with the default sidebar open; confirm the last pixel of the hero's bottom is ≤ (viewport_height - 140px for top chrome)." The current rubric says "no scrolling" but doesn't spec the viewport height.
|
||||
|
||||
### Top 3 observations for the comparison writeup
|
||||
|
||||
1. **Concept drift is the process, not a failure.** The concept as authored in Phase 1 evolved meaningfully by the end of Phase 4 — icon strip → compact feed, 55/45 → 75/25 hero, hero-header activity pill that wasn't in the original §1. Each change was a smoke-test-informed refinement. A two-path experiment will likely see both paths drift; the question is whether the drift is principled (smoke test → explicit revision → documented note) or accidental (silent changes that undermine the concept).
|
||||
|
||||
2. **Pre-existing gaps surface under consolidation.** `RunActivityChart`'s missing legend + tooltips were pre-existing. When the chart was 1-of-4 in a grid, the gap was easy to overlook; when it's 1-of-1 and wider, the gap jumps out. Run A inherited and fixed with opt-in shared-component props. Run B will almost certainly encounter similar inherited gaps in other modules; worth surfacing as a recurring pattern.
|
||||
|
||||
3. **Shared-component boundaries matter.** Three shared-component changes were made: `ChartLegend` exported (additive), `RunActivityChart` got opt-in `richTooltips` (additive, backward-compat), `PriorityIcon` reused unchanged. The inverse pattern — restructuring shared components to fit one consumer — would have been faster to write and worse for the codebase. "Additive opt-in props > narrowed behavior changes" was a load-bearing heuristic across phases; worth codifying for Run B and future briefs.
|
||||
@@ -156,7 +156,7 @@ function OnboardingRoutePage() {
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-xl py-10">
|
||||
<div className="rounded-lg border border-border bg-card p-6">
|
||||
<div className="rounded-none border border-border bg-card p-6">
|
||||
<h1 className="text-xl font-semibold">{title}</h1>
|
||||
<p className="mt-2 text-sm text-muted-foreground">{description}</p>
|
||||
<div className="mt-4">
|
||||
@@ -233,7 +233,7 @@ function NoCompaniesStartPage() {
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-xl py-10">
|
||||
<div className="rounded-lg border border-border bg-card p-6">
|
||||
<div className="rounded-none border border-border bg-card p-6">
|
||||
<h1 className="text-xl font-semibold">Create your first company</h1>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
Get started by creating a company.
|
||||
|
||||
@@ -65,7 +65,7 @@ export function ActiveAgentsPanel({ companyId }: ActiveAgentsPanelProps) {
|
||||
Agents
|
||||
</h3>
|
||||
{runs.length === 0 ? (
|
||||
<div className="rounded-xl border border-border p-4">
|
||||
<div className="rounded-none border border-border p-4">
|
||||
<p className="text-sm text-muted-foreground">No recent agent runs.</p>
|
||||
</div>
|
||||
) : (
|
||||
@@ -111,7 +111,7 @@ const AgentRunCard = memo(function AgentRunCard({
|
||||
}) {
|
||||
return (
|
||||
<div className={cn(
|
||||
"flex h-[320px] flex-col overflow-hidden rounded-xl border shadow-sm",
|
||||
"flex h-[320px] flex-col overflow-hidden rounded-none border shadow-sm",
|
||||
isActive
|
||||
? "border-cyan-500/25 bg-cyan-500/[0.04] shadow-[0_16px_40px_rgba(6,182,212,0.08)]"
|
||||
: "border-border bg-background/70",
|
||||
@@ -144,7 +144,7 @@ const AgentRunCard = memo(function AgentRunCard({
|
||||
</div>
|
||||
|
||||
{run.issueId && (
|
||||
<div className="mt-3 rounded-lg border border-border/60 bg-background/60 px-2.5 py-2 text-xs">
|
||||
<div className="mt-3 rounded-none border border-border/60 bg-background/60 px-2.5 py-2 text-xs">
|
||||
<Link
|
||||
to={`/issues/${issue?.identifier ?? run.issueId}`}
|
||||
className={cn(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { DashboardRunActivityDay, HeartbeatRun } from "@paperclipai/shared";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
|
||||
/* ---- Utilities ---- */
|
||||
|
||||
@@ -31,7 +32,7 @@ function DateLabels({ days }: { days: string[] }) {
|
||||
);
|
||||
}
|
||||
|
||||
function ChartLegend({ items }: { items: { color: string; label: string }[] }) {
|
||||
export function ChartLegend({ items }: { items: { color: string; label: string }[] }) {
|
||||
return (
|
||||
<div className="flex flex-wrap gap-x-2.5 gap-y-0.5 mt-2">
|
||||
{items.map(item => (
|
||||
@@ -46,7 +47,7 @@ function ChartLegend({ items }: { items: { color: string; label: string }[] }) {
|
||||
|
||||
export function ChartCard({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="border border-border rounded-lg p-4 space-y-3">
|
||||
<div className="border border-border rounded-none p-4 space-y-3">
|
||||
<div>
|
||||
<h3 className="text-xs font-medium text-muted-foreground">{title}</h3>
|
||||
{subtitle && <span className="text-[10px] text-muted-foreground/60">{subtitle}</span>}
|
||||
@@ -84,7 +85,7 @@ function resolveRunActivity(props: RunChartProps): DashboardRunActivityDay[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
export function RunActivityChart(props: RunChartProps) {
|
||||
export function RunActivityChart(props: RunChartProps & { richTooltips?: boolean }) {
|
||||
const activity = resolveRunActivity(props);
|
||||
const days = activity.length > 0 ? activity.map((day) => day.date) : getLast14Days();
|
||||
const grouped = new Map(activity.map((day) => [day.date, day]));
|
||||
@@ -101,17 +102,51 @@ export function RunActivityChart(props: RunChartProps) {
|
||||
const entry = grouped.get(day) ?? { date: day, succeeded: 0, failed: 0, other: 0, total: 0 };
|
||||
const total = entry.total;
|
||||
const heightPct = (total / maxValue) * 100;
|
||||
const bar = total > 0 ? (
|
||||
<div className="flex flex-col-reverse gap-px overflow-hidden" style={{ height: `${heightPct}%`, minHeight: 2 }}>
|
||||
{entry.succeeded > 0 && <div className="bg-emerald-500" style={{ flex: entry.succeeded }} />}
|
||||
{entry.failed > 0 && <div className="bg-red-500" style={{ flex: entry.failed }} />}
|
||||
{entry.other > 0 && <div className="bg-neutral-500" style={{ flex: entry.other }} />}
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-muted/30 rounded-sm" style={{ height: 2 }} />
|
||||
);
|
||||
|
||||
if (props.richTooltips) {
|
||||
return (
|
||||
<Tooltip key={day}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex-1 h-full flex flex-col justify-end bg-transparent border-0 p-0 cursor-default"
|
||||
aria-label={`${day}: ${total} run${total === 1 ? "" : "s"}`}
|
||||
>
|
||||
{bar}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="text-xs">
|
||||
<div className="font-medium tabular-nums">{day}</div>
|
||||
<div className="text-muted-foreground tabular-nums">
|
||||
{total === 0 ? (
|
||||
"No runs"
|
||||
) : (
|
||||
<>
|
||||
<span>Succeeded: {entry.succeeded}</span>
|
||||
{" · "}
|
||||
<span>Failed: {entry.failed}</span>
|
||||
{" · "}
|
||||
<span>Other: {entry.other}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={day} className="flex-1 h-full flex flex-col justify-end" title={`${day}: ${total} runs`}>
|
||||
{total > 0 ? (
|
||||
<div className="flex flex-col-reverse gap-px overflow-hidden" style={{ height: `${heightPct}%`, minHeight: 2 }}>
|
||||
{entry.succeeded > 0 && <div className="bg-emerald-500" style={{ flex: entry.succeeded }} />}
|
||||
{entry.failed > 0 && <div className="bg-red-500" style={{ flex: entry.failed }} />}
|
||||
{entry.other > 0 && <div className="bg-neutral-500" style={{ flex: entry.other }} />}
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-muted/30 rounded-sm" style={{ height: 2 }} />
|
||||
)}
|
||||
{bar}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -457,7 +457,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
||||
? <h3 className="text-sm font-medium mb-3">Identity</h3>
|
||||
: <div className="px-4 py-2 text-xs font-medium text-muted-foreground">Identity</div>
|
||||
}
|
||||
<div className={cn(cards ? "border border-border rounded-lg p-4 space-y-3" : "px-4 pb-3 space-y-3")}>
|
||||
<div className={cn(cards ? "border border-border rounded-none p-4 space-y-3" : "px-4 pb-3 space-y-3")}>
|
||||
<Field label="Name" hint={help.name}>
|
||||
<DraftInput
|
||||
value={eff("identity", "name", props.agent.name)}
|
||||
@@ -548,7 +548,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className={cn(cards ? "border border-border rounded-lg p-4 space-y-3" : "px-4 pb-3 space-y-3")}>
|
||||
<div className={cn(cards ? "border border-border rounded-none p-4 space-y-3" : "px-4 pb-3 space-y-3")}>
|
||||
{showAdapterTypeField && (
|
||||
<Field label="Adapter type" hint={help.adapterType}>
|
||||
<AdapterTypeDropdown
|
||||
@@ -675,7 +675,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
||||
? <h3 className="text-sm font-medium mb-3">Permissions & Configuration</h3>
|
||||
: <div className="px-4 py-2 text-xs font-medium text-muted-foreground">Permissions & Configuration</div>
|
||||
}
|
||||
<div className={cn(cards ? "border border-border rounded-lg p-4 space-y-3" : "px-4 pb-3 space-y-3")}>
|
||||
<div className={cn(cards ? "border border-border rounded-none p-4 space-y-3" : "px-4 pb-3 space-y-3")}>
|
||||
<Field label="Command" hint={help.localCommand}>
|
||||
<DraftInput
|
||||
value={
|
||||
@@ -874,7 +874,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
||||
? <h3 className="text-sm font-medium flex items-center gap-2 mb-3"><Heart className="h-3 w-3" /> Run Policy</h3>
|
||||
: <div className="px-4 py-2 text-xs font-medium text-muted-foreground flex items-center gap-2"><Heart className="h-3 w-3" /> Run Policy</div>
|
||||
}
|
||||
<div className={cn(cards ? "border border-border rounded-lg p-4 space-y-3" : "px-4 pb-3 space-y-3")}>
|
||||
<div className={cn(cards ? "border border-border rounded-none p-4 space-y-3" : "px-4 pb-3 space-y-3")}>
|
||||
<ToggleWithNumber
|
||||
label="Heartbeat on interval"
|
||||
hint={help.heartbeatInterval}
|
||||
@@ -895,7 +895,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
||||
? <h3 className="text-sm font-medium flex items-center gap-2 mb-3"><Heart className="h-3 w-3" /> Run Policy</h3>
|
||||
: <div className="px-4 py-2 text-xs font-medium text-muted-foreground flex items-center gap-2"><Heart className="h-3 w-3" /> Run Policy</div>
|
||||
}
|
||||
<div className={cn(cards ? "border border-border rounded-lg overflow-hidden" : "")}>
|
||||
<div className={cn(cards ? "border border-border rounded-none overflow-hidden" : "")}>
|
||||
<div className={cn(cards ? "p-4 space-y-3" : "px-4 pb-3 space-y-3")}>
|
||||
<ToggleWithNumber
|
||||
label="Heartbeat on interval"
|
||||
|
||||
@@ -52,7 +52,7 @@ export function ApprovalCard({
|
||||
const hasFooter = showResolutionButtons || Boolean(detailLink || onOpen);
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-border/70 bg-card p-4 shadow-sm">
|
||||
<div className="rounded-none border border-border/70 bg-card p-4 shadow-sm">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-start gap-3">
|
||||
@@ -102,7 +102,7 @@ export function ApprovalCard({
|
||||
</div>
|
||||
|
||||
{approval.decisionNote && (
|
||||
<div className="mt-4 rounded-lg border border-border/60 bg-muted/30 px-3.5 py-3 text-xs leading-5 text-muted-foreground">
|
||||
<div className="mt-4 rounded-none border border-border/60 bg-muted/30 px-3.5 py-3 text-xs leading-5 text-muted-foreground">
|
||||
<span className="font-medium text-foreground">Decision note.</span> {approval.decisionNote}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -189,7 +189,7 @@ function BoardApprovalPayloadContent({ payload }: { payload: Record<string, unkn
|
||||
</div>
|
||||
)}
|
||||
{recommendedAction && (
|
||||
<div className="rounded-lg border border-amber-500/20 bg-amber-500/10 px-3.5 py-3">
|
||||
<div className="rounded-none border border-amber-500/20 bg-amber-500/10 px-3.5 py-3">
|
||||
<p className="text-[11px] font-medium uppercase tracking-[0.08em] text-amber-700 dark:text-amber-300">
|
||||
Recommended action
|
||||
</p>
|
||||
@@ -197,7 +197,7 @@ function BoardApprovalPayloadContent({ payload }: { payload: Record<string, unkn
|
||||
</div>
|
||||
)}
|
||||
{nextActionOnApproval && (
|
||||
<div className="rounded-lg border border-border/60 bg-background/60 px-3.5 py-3">
|
||||
<div className="rounded-none border border-border/60 bg-background/60 px-3.5 py-3">
|
||||
<p className="text-[11px] font-medium uppercase tracking-[0.08em] text-muted-foreground">On approval</p>
|
||||
<p className="mt-1 leading-6 text-foreground">{nextActionOnApproval}</p>
|
||||
</div>
|
||||
@@ -220,7 +220,7 @@ function BoardApprovalPayloadContent({ payload }: { payload: Record<string, unkn
|
||||
<p className="text-[11px] font-medium uppercase tracking-[0.08em] text-muted-foreground">
|
||||
Proposed comment
|
||||
</p>
|
||||
<pre className="max-h-48 overflow-auto rounded-lg border border-border/60 bg-muted/50 px-3.5 py-3 font-mono text-xs leading-5 text-muted-foreground whitespace-pre-wrap">
|
||||
<pre className="max-h-48 overflow-auto rounded-none border border-border/60 bg-muted/50 px-3.5 py-3 font-mono text-xs leading-5 text-muted-foreground whitespace-pre-wrap">
|
||||
{proposedComment}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
@@ -66,7 +66,7 @@ export function BudgetIncidentCard({
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 px-5 pb-5 pt-0">
|
||||
<div className="flex items-start gap-2 rounded-xl border border-red-400/20 bg-red-500/10 px-3 py-2 text-sm text-red-50/90">
|
||||
<div className="flex items-start gap-2 rounded-none border border-red-400/20 bg-red-500/10 px-3 py-2 text-sm text-red-50/90">
|
||||
<PauseCircle className="mt-0.5 h-4 w-4 shrink-0" />
|
||||
<div>
|
||||
{incident.scopeType === "project"
|
||||
@@ -75,7 +75,7 @@ export function BudgetIncidentCard({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border border-border/60 bg-background/60 p-3">
|
||||
<div className="rounded-none border border-border/60 bg-background/60 p-3">
|
||||
<label className="text-[11px] uppercase tracking-[0.18em] text-muted-foreground">
|
||||
New budget (USD)
|
||||
</label>
|
||||
|
||||
@@ -74,14 +74,14 @@ export function BudgetPolicyCard({
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<div className="rounded-xl border border-border/70 bg-black/[0.18] px-4 py-3">
|
||||
<div className="rounded-none border border-border/70 bg-black/[0.18] px-4 py-3">
|
||||
<div className="text-[11px] uppercase tracking-[0.18em] text-muted-foreground">Observed</div>
|
||||
<div className="mt-2 text-xl font-semibold tabular-nums">{formatCents(summary.observedAmount)}</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
{summary.amount > 0 ? `${summary.utilizationPercent}% of limit` : "No cap configured"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-xl border border-border/70 bg-black/[0.18] px-4 py-3">
|
||||
<div className="rounded-none border border-border/70 bg-black/[0.18] px-4 py-3">
|
||||
<div className="text-[11px] uppercase tracking-[0.18em] text-muted-foreground">Budget</div>
|
||||
<div className="mt-2 text-xl font-semibold tabular-nums">
|
||||
{summary.amount > 0 ? formatCents(summary.amount) : "Disabled"}
|
||||
@@ -116,7 +116,7 @@ export function BudgetPolicyCard({
|
||||
);
|
||||
|
||||
const pausedPane = summary.paused ? (
|
||||
<div className="flex items-start gap-2 rounded-xl border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-100">
|
||||
<div className="flex items-start gap-2 rounded-none border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-100">
|
||||
<PauseCircle className="mt-0.5 h-4 w-4 shrink-0" />
|
||||
<div>
|
||||
{summary.scopeType === "project"
|
||||
@@ -127,7 +127,7 @@ export function BudgetPolicyCard({
|
||||
) : null;
|
||||
|
||||
const saveSection = onSave ? (
|
||||
<div className={cn("flex flex-col gap-3 sm:flex-row sm:items-end", isPlain ? "" : "rounded-xl border border-border/70 bg-background/50 p-3")}>
|
||||
<div className={cn("flex flex-col gap-3 sm:flex-row sm:items-end", isPlain ? "" : "rounded-none border border-border/70 bg-background/50 p-3")}>
|
||||
<div className="min-w-0 flex-1">
|
||||
<label className="text-[11px] uppercase tracking-[0.18em] text-muted-foreground">
|
||||
Budget (USD)
|
||||
|
||||
@@ -8,7 +8,7 @@ import { queryKeys } from "@/lib/queryKeys";
|
||||
function BootstrapPendingPage({ hasActiveInvite = false }: { hasActiveInvite?: boolean }) {
|
||||
return (
|
||||
<div className="mx-auto max-w-xl py-10">
|
||||
<div className="rounded-lg border border-border bg-card p-6">
|
||||
<div className="rounded-none border border-border bg-card p-6">
|
||||
<h1 className="text-xl font-semibold">Instance setup required</h1>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
{hasActiveInvite
|
||||
@@ -26,7 +26,7 @@ function BootstrapPendingPage({ hasActiveInvite = false }: { hasActiveInvite?: b
|
||||
function NoBoardAccessPage() {
|
||||
return (
|
||||
<div className="mx-auto max-w-xl py-10">
|
||||
<div className="rounded-lg border border-border bg-card p-6">
|
||||
<div className="rounded-none border border-border bg-card p-6">
|
||||
<h1 className="text-xl font-semibold">No company access</h1>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
This account is signed in, but it does not have an active company membership or instance-admin access on
|
||||
|
||||
@@ -98,17 +98,17 @@ export function ExecutionWorkspaceCloseDialog({
|
||||
</DialogHeader>
|
||||
|
||||
{readinessQuery.isLoading ? (
|
||||
<div className="flex items-center gap-2 rounded-xl border border-border bg-background px-4 py-3 text-sm text-muted-foreground">
|
||||
<div className="flex items-center gap-2 rounded-none border border-border bg-background px-4 py-3 text-sm text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Checking whether this workspace is safe to close...
|
||||
</div>
|
||||
) : readinessQuery.error ? (
|
||||
<div className="rounded-xl border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive">
|
||||
<div className="rounded-none border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive">
|
||||
{readinessQuery.error instanceof Error ? readinessQuery.error.message : "Failed to inspect workspace close readiness."}
|
||||
</div>
|
||||
) : readiness ? (
|
||||
<div className="space-y-4">
|
||||
<div className={`rounded-xl border px-4 py-3 text-sm ${readinessTone(readiness.state)}`}>
|
||||
<div className={`rounded-none border px-4 py-3 text-sm ${readinessTone(readiness.state)}`}>
|
||||
<div className="font-medium">
|
||||
{readiness.state === "blocked"
|
||||
? "Close is blocked"
|
||||
@@ -132,7 +132,7 @@ export function ExecutionWorkspaceCloseDialog({
|
||||
<h3 className="text-sm font-medium">Blocking issues</h3>
|
||||
<div className="space-y-2">
|
||||
{blockingIssues.map((issue) => (
|
||||
<div key={issue.id} className="rounded-xl border border-destructive/20 bg-destructive/5 px-4 py-3 text-sm">
|
||||
<div key={issue.id} className="rounded-none border border-destructive/20 bg-destructive/5 px-4 py-3 text-sm">
|
||||
<div className="flex min-w-0 flex-wrap items-center justify-between gap-2">
|
||||
<Link to={issueUrl(issue)} className="min-w-0 break-words font-medium hover:underline">
|
||||
{issue.identifier ?? issue.id} · {issue.title}
|
||||
@@ -150,7 +150,7 @@ export function ExecutionWorkspaceCloseDialog({
|
||||
<h3 className="text-sm font-medium">Blocking reasons</h3>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
{readiness.blockingReasons.map((reason) => (
|
||||
<li key={reason} className="break-words rounded-lg border border-destructive/20 bg-destructive/5 px-3 py-2 text-destructive">
|
||||
<li key={reason} className="break-words rounded-none border border-destructive/20 bg-destructive/5 px-3 py-2 text-destructive">
|
||||
{reason}
|
||||
</li>
|
||||
))}
|
||||
@@ -163,7 +163,7 @@ export function ExecutionWorkspaceCloseDialog({
|
||||
<h3 className="text-sm font-medium">Warnings</h3>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
{readiness.warnings.map((warning) => (
|
||||
<li key={warning} className="break-words rounded-lg border border-amber-500/20 bg-amber-500/5 px-3 py-2">
|
||||
<li key={warning} className="break-words rounded-none border border-amber-500/20 bg-amber-500/5 px-3 py-2">
|
||||
{warning}
|
||||
</li>
|
||||
))}
|
||||
@@ -174,7 +174,7 @@ export function ExecutionWorkspaceCloseDialog({
|
||||
{readiness.git ? (
|
||||
<section className="space-y-2">
|
||||
<h3 className="text-sm font-medium">Git status</h3>
|
||||
<div className="rounded-xl border border-border bg-background px-4 py-3 text-sm">
|
||||
<div className="rounded-none border border-border bg-background px-4 py-3 text-sm">
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
<div>
|
||||
<div className="text-xs uppercase tracking-[0.16em] text-muted-foreground">Branch</div>
|
||||
@@ -212,7 +212,7 @@ export function ExecutionWorkspaceCloseDialog({
|
||||
<h3 className="text-sm font-medium">Other linked issues</h3>
|
||||
<div className="space-y-2">
|
||||
{otherLinkedIssues.map((issue) => (
|
||||
<div key={issue.id} className="rounded-xl border border-border bg-background px-4 py-3 text-sm">
|
||||
<div key={issue.id} className="rounded-none border border-border bg-background px-4 py-3 text-sm">
|
||||
<div className="flex min-w-0 flex-wrap items-center justify-between gap-2">
|
||||
<Link to={issueUrl(issue)} className="min-w-0 break-words font-medium hover:underline">
|
||||
{issue.identifier ?? issue.id} · {issue.title}
|
||||
@@ -230,7 +230,7 @@ export function ExecutionWorkspaceCloseDialog({
|
||||
<h3 className="text-sm font-medium">Attached runtime services</h3>
|
||||
<div className="space-y-2">
|
||||
{readiness.runtimeServices.map((service) => (
|
||||
<div key={service.id} className="rounded-xl border border-border bg-background px-4 py-3 text-sm">
|
||||
<div key={service.id} className="rounded-none border border-border bg-background px-4 py-3 text-sm">
|
||||
<div className="flex min-w-0 flex-wrap items-center justify-between gap-2">
|
||||
<span className="font-medium">{service.serviceName}</span>
|
||||
<span className="text-xs text-muted-foreground">{service.status} · {service.lifecycle}</span>
|
||||
@@ -248,11 +248,11 @@ export function ExecutionWorkspaceCloseDialog({
|
||||
<h3 className="text-sm font-medium">Cleanup actions</h3>
|
||||
<div className="space-y-2">
|
||||
{readiness.plannedActions.map((action, index) => (
|
||||
<div key={`${action.kind}-${index}`} className="rounded-xl border border-border bg-background px-4 py-3 text-sm">
|
||||
<div key={`${action.kind}-${index}`} className="rounded-none border border-border bg-background px-4 py-3 text-sm">
|
||||
<div className="font-medium">{action.label}</div>
|
||||
<div className="mt-1 break-words text-muted-foreground">{action.description}</div>
|
||||
{action.command ? (
|
||||
<pre className="mt-2 whitespace-pre-wrap break-all rounded-lg bg-background px-3 py-2 font-mono text-xs text-foreground">
|
||||
<pre className="mt-2 whitespace-pre-wrap break-all rounded-none bg-background px-3 py-2 font-mono text-xs text-foreground">
|
||||
{action.command}
|
||||
</pre>
|
||||
) : null}
|
||||
@@ -262,14 +262,14 @@ export function ExecutionWorkspaceCloseDialog({
|
||||
</section>
|
||||
|
||||
{currentStatus === "cleanup_failed" ? (
|
||||
<div className="rounded-xl border border-amber-500/20 bg-amber-500/5 px-4 py-3 text-sm text-muted-foreground">
|
||||
<div className="rounded-none border border-amber-500/20 bg-amber-500/5 px-4 py-3 text-sm text-muted-foreground">
|
||||
Cleanup previously failed on this workspace. Retrying close will rerun the cleanup flow and update the
|
||||
workspace status if it succeeds.
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{currentStatus === "archived" ? (
|
||||
<div className="rounded-xl border border-border bg-background px-4 py-3 text-sm text-muted-foreground">
|
||||
<div className="rounded-none border border-border bg-background px-4 py-3 text-sm text-muted-foreground">
|
||||
This workspace is already archived.
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user