Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
48e8582
feat(console): invert the register onto Twenty and rebuild elevation
Travis-Gilbert Aug 3, 2026
9d4f9ce
Merge remote-tracking branch 'origin/main' into claude/commonplace-tw…
Travis-Gilbert Aug 3, 2026
c7b26b0
feat(console): land the 21st rails and put them on the register
Travis-Gilbert Aug 3, 2026
20bf223
feat(console): one sidebar system, the 21st rail and panel everywhere
Travis-Gilbert Aug 3, 2026
dc4771d
fix(console): sink the centre and drop the bottom status strip
Travis-Gilbert Aug 3, 2026
b361d03
fix(console): fit the sidebar panel to its words
Travis-Gilbert Aug 3, 2026
367db6b
feat(console): mount the right rail as a dock you can drag and shut
Travis-Gilbert Aug 3, 2026
83b2980
chore(console): drop the two dead surfaces the hand-roll audit found
Travis-Gilbert Aug 4, 2026
75d0338
fix(console): retire the connect affordance the product does not want
Travis-Gilbert Aug 4, 2026
189f5aa
feat(console): carry the OWOX canvas in the rail, on a floating frame
Travis-Gilbert Aug 4, 2026
9c55078
feat(console): build the rail's insights list and action cluster
Travis-Gilbert Aug 4, 2026
1cc1aa8
fix(console): stack the rail cluster so it fits the rail
Travis-Gilbert Aug 4, 2026
7f889e2
feat(console): drop the Thread companion and flatten the shell
Travis-Gilbert Aug 4, 2026
a1de629
feat(console): float the inspector rail over the well
Travis-Gilbert Aug 4, 2026
11fd1a3
fix(console): attach the rail to the right edge, retoken its nav rows
Travis-Gilbert Aug 4, 2026
dbe3594
fix(console): collapse the detail panel to nothing, toggle it from th…
Travis-Gilbert Aug 4, 2026
d2231be
Merge main into the console rail branch
Travis-Gilbert Aug 4, 2026
fd68ad0
fix(console): derive the rail's empty model, drop the dev launch config
Travis-Gilbert Aug 5, 2026
168a504
Merge remote-tracking branch 'origin/main' into claude/commonplace-tw…
Travis-Gilbert Aug 5, 2026
7760a38
fix(console): clear the three react-hooks errors in prototype.stage
Travis-Gilbert Aug 5, 2026
03b9665
fix(console): give prototype.stage real tokens instead of raw values
Travis-Gilbert Aug 5, 2026
81790da
chore(console): record the three new metrics in the token manifest
Travis-Gilbert Aug 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .cursor/rules/no-hand-roll-components.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
description: Never hand-roll UI when a catalog/library component exists — install and mount it.
alwaysApply: true
---

# No hand-rolled components

If a visual or behavioral need already exists as a component (21st.dev, shadcn registry, jalco, blocks.so, ledger source, npm library), **install that component and mount it**. Do not approximate it with a local rewrite.

## Required sequence

1. **Search first** — `21st search …`, shadcn/registry, and the console library ledger in `apps/console/CLAUDE.md`.
2. **Install** — `npx @21st-dev/cli add author/slug` or `npx shadcn@latest add …` into `apps/console`. Auth via `~/.config/21st/auth.json` / `API_KEY_21ST` when the registry requires it.
3. **Mount the installed file** — product code imports from `components/ui/<slug>.tsx` (or the package). Keep the published structure and visual DNA.
4. **Adapt in place** — add props/slots/retokens inside the installed file or a thin wrapper that still renders it. Do not recreate the layout in a parallel hand-rolled file.

## Forbidden

```tsx
// ❌ BAD — "inspired by" / pattern copy while the real component exists
// components/ui/icon-rail-sidebar.tsx (hand-rolled stand-in for jshguo)

// ✅ GOOD — installed source + product shell export
// components/ui/sidebar-component.tsx (21st/@jshguo/sidebar-component)
import { TwoLevelSidebarShell } from '@/components/ui/sidebar-component';
```

- No second implementation that “looks similar” to a named upstream.
- No deleting an installed component and replacing it with divs + Tailwind that mimic it.
- If nothing in the catalog fits, say so, add a ledger row with the named gap, then build — never skip the search.

## Evidence of compliance

- `// SOURCING: 21st/@author/slug` (or registry/package) on the file.
- The installed path exists under `components/ui/` or `node_modules`.
- Call sites import that path, not a local lookalike.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
63
492
4 changes: 3 additions & 1 deletion apps/console/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ asks for inspiration, reference, mood, or direction.
| Need | Source | Owns |
|---|---|---|
| Split layout, tool window panels | `react-resizable-panels` | split geometry, persisted sizes, 1px `--ij-divider` handles |
| Inspector rail dock | `react-resizable-panels` collapsible `Panel` in a second group (`autoSaveId="console.inspector-dock"`) | the right rail's width, drag, collapsed state, keyboard resize, and persistence. A second group, not another Panel in the surface group: the surface group persists sizes by region id through the host, and the rail is chrome with no region of its own. Collapse is `collapsedSize={0}`, so the rail's own edge control (collapse) renders only while open and the well renders `InspectorRailReopen`; a Panel clips its overflow, so a control left inside a zero-width rail is unclickable and still in the accessibility tree |
| Search everywhere, palettes | `cmdk` | command list, filtering, keyboard nav |
| Approval dialog | `cmdk` Dialog | focus-managed, keyboard-selectable one-shot Plan approval and rejection |
| Tabular lenses | tablecn structure on `@tanstack/react-table` plus blocks.so `table-01`..`table-05` anatomy | record.table sorting, filtering, column model |
Expand All @@ -124,7 +125,7 @@ asks for inspiration, reference, mood, or direction.
| Object contract | `@commonplace/block-view` | BlockHost, ObjectQuery, descriptors, surface tree |
| Ground / Material Layer | hand-roll (MaterialLayer / GroundCanvas alias) | full-viewport WebGL SDF islands, terracotta ground, edge falloff, grain; DOM shell fills stay transparent |
| Files tree | 21st.dev `builduilabs/filesystem-item` behavior extraction plus `@tanstack/react-virtual` | recursive disclosure behavior and large memory projection virtualization |
| Chat sidebar dock | fork of ibelick/motion-primitives `dock` under `components/ui/dock.tsx` (`motion/react`, magnification 44) | sole surface switcher in the chat sidebar |
| Chat / console left chrome | 21st.dev `@jshguo/sidebar-component` installed at `components/ui/sidebar-component.tsx` (`TwoLevelSidebarShell`) | icon rail + detail panel; never hand-roll a lookalike. Retokened onto the register: the published file's Tailwind neutrals, `rounded-lg`, Lexend literals, and `duration-500` resolve to nothing here, because the `@theme` block resets `--color-*`, `--radius-*`, and `--font-*` to initial. Elevation is lifted rail / frame panel / sunken well. The vendored `@carbon/icons-react` glyphs drive the Frame760 demo only; every rail icon a user sees arrives through `items` as a Noun mark, and `brand` / `panelBrand` carry the product's own mark |
| Chat context tree | fork of Build UI filesystem-item under `components/ui/filesystem-item.tsx` | graph-object folders with include/exclude and unavailable honesty |
| Composer material | `ComposerMaterial` (`@paper-design/shaders-react` GrainGradient) | scoped ShaderMount behind the chat composer; idle speed 0; no backdrop-filter |
| Form selection controls | Radix UI Select and Checkbox through `components/ui/select.tsx` and `components/ui/checkbox.tsx` | keyboard and pointer selection, checked state, focus, and disabled behavior |
Expand All @@ -134,6 +135,7 @@ asks for inspiration, reference, mood, or direction.
| Agent plan | `@assistant-ui/react` plus 21st.dev `isaiahbjork/agent-plan` structure extraction | in-thread plan rows, tool labels, and run status |
| Graph canvas | `@xyflow/react` (React Flow) over `@dagrejs/dagre` (layered ranking) | the proactivity graph, per SPEC-PROACTIVITY-GRAPH-WIRING: dagre computes the layered join topology (roots rank first, sinks last), React Flow owns pan, zoom, selection, and edge routing; nodes and edges are register-styled (base CSS only, `--xy-*` mapped to the register in app.css), dynamic-imported so the sentence and card altitudes load no graph bundle |
| Data canvas | `@xyflow/react` wrap plus `@commonplace/json-canvas` (JSON Canvas 1.0 interchange) plus Paper DotGrid via `ShaderSurface` | graph-native spatial arrangement of ObjectRefs; Obsidian `.canvas` import/export at the edge only; Island Shells DotGrid extract (size 0.7, gap 32, triangle, speed 0, register colors) behind the pane; mounts as surface and companion |
| Inspector Obsidian canvas | 21st `@arunjdass/dashboard-sidebar` Z-layer + `JsonCanvasLayer` + `CanvasStore` id `canvas.inspector.rail` | chrome-owned rail canvas (not block companion); double-click adds text; persists via `canvas.apply_json`. The nav's Places and Objects rows paint Noun marks (`NavIcon` widens the published `LucideIcon` slot); the vendored lucide glyphs drive the demo tree only |
| Graph node and sentence card building blocks | jalco-ui `commit-graph` and `repo-card` (ui.justinlevine.me), structure extraction | the commit-entry node (a response is a stack of agent-action steps a person builds) and the RepoCard sentence card; the jalco layouts are reproduced and every shadcn token re-skinned to the register |
| Object addressing and copy | `@commonplace/block-view/addressing` (the shared `theorem://` grammar, per DESIGN-THEOREM-URI) plus the platform Clipboard API | every canonical address this app emits, parses, or offers: the inspector footer, the card copy affordance, mention chips, the Composer paste offer, and the Search field's address lane. `src/lib/object-address.ts` is the only place a tenant plus an object becomes an address, and `src/lib/use-copy.ts` is the only clipboard call (the apps/web hook's shape, re-implemented because the import fence is structural) |
| Goal Stack canvas | `@xyflow/react` plus `@dagrejs/dagre`, `cmdk`, and `@dnd-kit/core` | DAG canvas and progress edges, named left-to-right layout, searchable capability palette and approval dialog, deferred-affordance drag and drop |
Expand Down
19 changes: 16 additions & 3 deletions apps/console/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,14 @@ gap: add the row, with a named source, before writing code.
| Need | Source | Owns |
|---|---|---|
| Split layout, tool window panels | `react-resizable-panels` | split geometry, persisted sizes, 1px `--ij-divider` handles |
| Inspector rail dock | `react-resizable-panels` collapsible `Panel` in a second group (`autoSaveId="console.inspector-dock"`) | the right rail's width, drag, collapsed state, keyboard resize, and persistence. A second group, not another Panel in the surface group: the surface group persists sizes by region id through the host, and the rail is chrome with no region of its own. Collapse is `collapsedSize={0}`, so the rail's own edge control (collapse) renders only while open and the well renders `InspectorRailReopen`; a Panel clips its overflow, so a control left inside a zero-width rail is unclickable and still in the accessibility tree |
| Search everywhere, palettes | `cmdk` | command list, filtering, keyboard nav |
| Approval dialog | `cmdk` Dialog | focus-managed, keyboard-selectable one-shot Plan approval and rejection |
| Tabular lenses | tablecn structure on `@tanstack/react-table` | record.table sorting, filtering, column model |
| Tabular lenses | tablecn structure on `@tanstack/react-table` plus blocks.so `table-01`..`table-05` anatomy | record.table sorting, filtering, column model |
| Schema-driven record cells and chips | FieldType map in `views/records/cells.tsx` plus blocks.so badge / jalco chip anatomy | polymorphic cells, RecordChip, select hues |
| Model canvas object cards | `@xyflow/react` plus jalco `repo-card` header anatomy (structure extract) | ObjectTypeCard / GhostCard on the diagram lens |
| Model relation edges | `@xyflow/react` BaseEdge / smooth-step | field-anchored RelationEdge |
| Record view bar and calculate footer | `cmdk` plus blocks.so command-menu / table footer patterns | ViewBar grammar and server aggregate footer |
| Row virtualization | `@tanstack/react-virtual` | large record sets |
| Thread and messages | `@assistant-ui/react` 0.12 | message list, streaming, message state |
| Composer mechanics | `@assistant-ui/react` 0.12 | auto-grow input, attachments, object mentions, destination slot, send behavior |
Expand All @@ -118,14 +123,21 @@ gap: add the row, with a named source, before writing code.
| Object contract | `@commonplace/block-view` | BlockHost, ObjectQuery, descriptors, surface tree |
| Ground / Material Layer | hand-roll (MaterialLayer / GroundCanvas alias) | full-viewport WebGL SDF islands, terracotta ground, edge falloff, grain; DOM shell fills stay transparent |
| Files tree | 21st.dev `builduilabs/filesystem-item` behavior extraction plus `@tanstack/react-virtual` | recursive disclosure behavior and large memory projection virtualization |
| Chat sidebar dock | fork of ibelick/motion-primitives `dock` under `components/ui/dock.tsx` (`motion/react`, magnification 44) | sole surface switcher in the chat sidebar |
| Chat / console left chrome | 21st.dev `@jshguo/sidebar-component` installed at `components/ui/sidebar-component.tsx` (`TwoLevelSidebarShell`) | icon rail + detail panel; never hand-roll a lookalike. Retokened onto the register: the published file's Tailwind neutrals, `rounded-lg`, Lexend literals, and `duration-500` resolve to nothing here, because the `@theme` block resets `--color-*`, `--radius-*`, and `--font-*` to initial. Elevation is lifted rail / frame panel / sunken well. The vendored `@carbon/icons-react` glyphs drive the Frame760 demo only; every rail icon a user sees arrives through `items` as a Noun mark, and `brand` / `panelBrand` carry the product's own mark |
| Chat context tree | fork of Build UI filesystem-item under `components/ui/filesystem-item.tsx` | graph-object folders with include/exclude and unavailable honesty |
| Composer material | `ComposerMaterial` (`@paper-design/shaders-react` GrainGradient) | scoped ShaderMount behind the chat composer; idle speed 0; no backdrop-filter |
| Context graph | D3 | deterministic ego graph layout and relation geometry |
| Proactivity graph | `@xyflow/react`, `@dagrejs/dagre`, and the existing `@commonplace/theorem-acp` state/session path | controlled denormalized dependency graph, deterministic topological layout, same-origin firing overlay, and pending compile-review gate |
| Agent plan | `@assistant-ui/react` plus 21st.dev `isaiahbjork/agent-plan` structure extraction | in-thread plan rows, tool labels, and run status |
| Graph canvas | `@xyflow/react` (React Flow) over `@dagrejs/dagre` (layered ranking) | the proactivity graph, per SPEC-PROACTIVITY-GRAPH-WIRING: dagre computes the layered join topology (roots rank first, sinks last), React Flow owns pan, zoom, selection, and edge routing; nodes and edges are register-styled (base CSS only, `--xy-*` mapped to the register in app.css), dynamic-imported so the sentence and card altitudes load no graph bundle |
| Data canvas | `@xyflow/react` wrap plus `@commonplace/json-canvas` (JSON Canvas 1.0 interchange) plus Paper DotGrid via `ShaderSurface` | graph-native spatial arrangement of ObjectRefs; Obsidian `.canvas` import/export at the edge only; Island Shells DotGrid extract (size 0.7, gap 32, triangle, speed 0, register colors) behind the pane; mounts as surface and companion |
| Data canvas (pane `CanvasView`) | `@xyflow/react` wrap plus `@commonplace/json-canvas` (JSON Canvas 1.0 interchange) plus Paper DotGrid via `ShaderSurface` | graph-native spatial arrangement of ObjectRefs; Obsidian `.canvas` import/export at the edge only; mounts as surface/full pane |
| Inspector Obsidian canvas | 21st `@arunjdass/dashboard-sidebar` Z-layer + `JsonCanvasLayer` + `CanvasStore` id `canvas.inspector.rail` | chrome-owned rail canvas (not block companion); double-click adds text; persists via `canvas.apply_json` |
| Inspector rail frame | `twenty-ui/surfaces` `Card` / `CardContent` / `CardFooter` | the rail is a floating inset panel, not an aside flush to the window edge with a `border-l`. Radius, surface elevation, and the seam against the ground come from the fork's card, so the rail reads as a card lying on the MaterialLayer the way the OpenHuman reference does |
| Inspector rail activity rows | `twenty-ui/navigation` `MenuItemNavigate` | one row per agent step in the rail's task-insights list: label plus the trailing navigate affordance, which is the row anatomy the reference uses. Never a hand-rolled `div` with a chevron |
| Inspector rail step state | `twenty-ui/data-display` `Tag` and `Status` | the three weights the reference distinguishes: a step that is the current heading, a step running now (the warm accent), and a step already passed (muted). Colour arrives through the fork's token generator, so `--ij-*` still drives it |
| Inspector rail action cluster | `twenty-ui/input` `RoundedIconButton` (primary) plus `LightIconButton` (secondary) | the rail's bottom action group: one round primary affordance and the smaller secondary controls beside it. Sizing and press behaviour are the fork's, not restated here |
| Inspector rail meta footer | `twenty-ui/data-display` `Pill` plus register type utilities | the context/cost readout and the goal affordance on the rail's last line |
| Inspector rail data canvas | `@commonplace/model-canvas` (OWOX hard fork, Apache-2.0) `ModelCanvasShell` | the rail's canvas body. Supersedes `JsonCanvasLayer` as the rail's canvas: `SPEC-COMMONPLACE-MODEL-CANVAS-FORK-1.0` makes the OWOX fork the one canvas language, and the rail carrying a second one was the reason the rail and the Data model surface disagreed about what a node looks like. Same package the Data model surface mounts through `ForkDiagramCanvas`, so `MartNode` / `RelEdge` / the substrate edge language are shared rather than reimplemented |
| Goal Stack canvas | `@xyflow/react` plus `@dagrejs/dagre`, `cmdk`, and `@dnd-kit/core` | DAG canvas and progress edges, named left-to-right layout, searchable capability palette and approval dialog, deferred-affordance drag and drop |
| Workspace substrate | `@tanstack/react-virtual`, CodeMirror 6 merge, `cmdk`, and Radix Popover | semantic workspace entity tree, virtual rows, local-history revision diff, project Find, readiness detail |
| Graph node and sentence card building blocks | jalco-ui `commit-graph` and `repo-card` (ui.justinlevine.me), structure extraction | the commit-entry node (a response is a stack of agent-action steps a person builds) and the RepoCard sentence card; the jalco layouts are reproduced and every shadcn token re-skinned to the register |
Expand Down Expand Up @@ -175,6 +187,7 @@ markdown. Use colons, periods, commas, semicolons, or parentheses instead.

| Date | Decision | Why |
|---|---|---|
| 2026-07-29 | Inspector Obsidian canvas is chrome-owned (`canvas.inspector.rail` in `DashboardSidebar`), not the `CanvasView` companion mount. Live durability: `CONSOLE_LIVE_CANVAS_SMOKE=1` plus `CONSOLE_LIVE_CANVAS_BASE` (and auth cookie/header as required) runs `src/lib/canvas/store.live.test.ts`; v2 manual smoke is logout/login hard-refresh after placing a note. | Spec-review F3 choice A; unit seam alone cannot close F2. |
| 2026-07-18 | The user-facing topic result surface is Indexer. Existing `survey.*` identifiers remain stable. | A topic click returns the full indexed evidence corpus. Keeping internal identifiers avoids breaking persisted surface objects while the product language settles. |
| 2026-07-18 | Indexer keeps the installed 21st.dev golden-ratio sphere. Relationship data may contain cycles and never controls card placement. | The spatial shape is the primary reading surface. Edges stay faint at rest, strengthen on hover, and pin on click. |
| 2026-07-18 | Indexer pegboard is retired in favor of MaterialLayer grammar. | Source cards and their evidence connections remain the canvas while the transparent scene shell preserves the shared ambient ground. |
Expand Down
5 changes: 4 additions & 1 deletion apps/console/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,8 @@
},
"menuColor": "default",
"menuAccent": "subtle",
"registries": {}
"registries": {
"@blocks-so": "https://blocks.so/r/{name}.json",
"@jalco": "https://ui.justinlevine.me/r/{name}.json"
}
}
3 changes: 2 additions & 1 deletion apps/console/e2e/appearance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ test.describe('appearance surface', () => {
});
expect(running).toBe('rgb(31, 117, 54)');
await expect(page.locator('[data-run-widget]')).toHaveCount(0);
await expect(page.locator('[data-account-trigger]')).toHaveCSS('height', '28px');
// Twenty proportions: --ij-control-h is --t-spacing-8.
await expect(page.locator('[data-account-trigger]')).toHaveCSS('height', '32px');
await expect(page.locator('html')).toHaveCSS('font-size', '13px');
await page.keyboard.press('Alt+Shift+1');
await expect(page.locator('[data-tool-window="files"]')).toBeVisible();
Expand Down
5 changes: 3 additions & 2 deletions apps/console/e2e/console-ia.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ test.describe('Console information architecture', () => {
);
await expect(page.locator('[data-companion-nav]')).toHaveCount(0);
await expect(page.locator('[data-rail-connection]')).toHaveCount(0);
await expect(page.locator('[data-connection-owner="status-bar"]')).toHaveCount(1);
await expect(page.locator('[data-connection-owner="status-bar"]')).toHaveCount(0);

const blocks = page.locator('[data-rail-tier="blocks"] button');
await expect(blocks).toHaveCount(11);
Expand Down Expand Up @@ -204,7 +204,8 @@ test.describe('Console information architecture', () => {
const input = composer.locator('[data-composer-input]');
await expect(page.locator('[data-chat-sidebar]')).toBeVisible();
await expect(page.locator('[data-chat-transcript]')).toBeVisible();
await expect(page.locator('[data-chat-rail]')).toBeVisible();
await expect(page.locator('[data-inspector-rail]')).toBeVisible();
await expect(page.locator('[data-inspector-rail-edge]')).toBeVisible();
await expect(page.locator('[data-thread-composer-input]')).toHaveCount(0);
await expect(composer.getByRole('button', { name: 'Attach file' })).toBeVisible();
await expect(composer.getByLabel('Model')).toHaveValue('theorem');
Expand Down
Loading
Loading