Skip to content

feat(code): canvas — experimental Channels space with gen-UI dashboards (project-bluebird)#2492

Closed
adamleithp wants to merge 24 commits into
mainfrom
feat/canvas
Closed

feat(code): canvas — experimental Channels space with gen-UI dashboards (project-bluebird)#2492
adamleithp wants to merge 24 commits into
mainfrom
feat/canvas

Conversation

@adamleithp

@adamleithp adamleithp commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

⚠️ Experimental

Everything new here is gated behind the project-bluebird feature flag (on by default in dev builds only). With the flag off — every production user today — the app is the existing code-only shell, untouched: no nav rail, /website and /inbox redirect to /code, and the Code chrome is byte-for-byte as it is on main. So this is safe to land and iterate on behind the flag.

The app rail is the split point between the two UXs:

  • Code → the existing task/sessions app, exactly as today (the only space prod users see).
  • Channels → the new experimental space (channels, gen-UI dashboards, channel tasks).

What this adds

A Slack-like vertical app nav rail (AppNav) that switches between top-level "spaces", plus a whole new Channels space built on the PostHog desktop file system API.

New routes

Route What it does
/ Redirects to /code (no standalone home).
/code, /code/* The existing app — task list, sessions, inbox, settings. Unchanged.
/website Channels space index — lists the project's channels.
/website/$channelId A channel's dashboards grid (cards with live previews).
/website/$channelId/dashboards/$dashboardId A single dashboard — view, or edit with the gen-UI canvas + chat.
/website/$channelId/new New task scoped to the channel.
/website/$channelId/tasks/$taskId A channel-scoped task.
/website/$channelId/settings Channel settings.

The Channels space has its own chrome (rail + a persistent channel-list sidebar + its own breadcrumb/toolbar bars) rather than the Code header/sidebar.

Channels = desktop file-system folders

A channel is a top-level folder row on the project's desktop_file_system surface. Create / rename / delete go straight through the REST API, so channel names live on the backend and sync across clients.

Dashboards & gen UI

A dashboard is a small, live, data-driven view of the current PostHog project — built conversationally rather than hand-configured.

How the gen UI works. Each dashboard has a chat thread. A PostHog agent (reusing the existing agent + PostHog MCP server) runs with a json-render system prompt describing a fixed component catalog (Page, Grid, Card, Stat, Table, BarList, Heading, etc.). The agent:

  1. Calls PostHog MCP tools to fetch real project data (never fabricated).
  2. Streams back prose + json-render JSONL patches, which the main process assembles into a Spec (a root + flat element map) and forwards to the renderer to render live.
  3. For every metric it fills, it also records the exact HogQL that produced it under state.queries, so the dashboard can be refreshed (or polled) later by re-running those queries and patching the values back in.

The dashboard always opens with a top-level h1 Heading — that h1 is the dashboard's name. Editing it renames the dashboard. Inline text editing is supported in edit mode (drag-and-drop reordering was removed).

How it's backed by the file-system API. Dashboards are not local files — each one is a dashboard-typed row nested under its channel folder on the same desktop_file_system surface:

  • Name = the row's last path segment = the canvas h1. Renaming PATCHes the row's path.
  • Spec + bookkeeping (channel id, timestamps) ride in the row's free-form meta JSON blob (typed/documented as DashboardFileMeta).
  • CRUD + refresh go through DashboardsService (main), which talks to the backend via authenticatedFetch. meta.spec is currently last-write-wins (no versioning) — fine for now, revisit if multi-client editing becomes real.

This keeps dashboards and their names in sync with the backend — the same surface that owns channel names.

See apps/code/src/renderer/features/canvas/AGENTS.md for the breadcrumb / naming / storage conventions.


Behind project-bluebird; no-op for users until we flip the flag.

adamleithp and others added 24 commits June 4, 2026 13:59
Wrap the app in a left app rail (square Quill icon-lg buttons) switching
between a new Home space (/ hello-world scene + its own sidenav) and the
existing Code app (/code). Rail reserves macOS traffic-light space and is a
titlebar drag region.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Give the Home sidenav Quill folder collapsibles with a placeholder nav
(Features, Resources). The Features > Website item opens a new blank /website
canvas route. Centralize Home-space detection in canvas/spaces.ts so the whole
space shares its chrome.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add @json-render/core + @json-render/react. The /website canvas now renders an
agent-built, data-driven UI on the left with a chat panel hugging the right.

- Main CanvasGenService reuses AgentService (PostHog MCP auto-enabled) to run an
  ephemeral __preview__ session per thread with a json-render system prompt and
  bypassPermissions, forwards ACP session updates through @json-render/core's
  mixed-stream parser to assemble the Spec, and streams typed events over tRPC.
- AgentService gains systemPromptOverride to replace the coding-agent prompt for
  non-coding surfaces (keeps only project scoping).
- Renderer: shared json-render catalog (Page/Grid/Card/Stat/Table/BarList/…) +
  Radix registry, a thin canvasChatStore, a scoped subscription registrar, and
  the chat/composer UI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The renderer could OOM/crash while streaming a generated spec: partial or
malformed mid-stream frames were rendered and could recurse infinitely.

- Main only emits a spec once its root element actually exists (no partial
  frames).
- Wrap CanvasRenderer in an ErrorBoundary keyed on the spec, so a bad frame is
  caught and rendering recovers on the next valid frame.
- Only render when isNonEmptySpec(spec).
- Make the canvas subscription idempotent per thread (guards StrictMode
  double-mounts from stacking IPC listeners).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an Inbox rail item (above Code) that opens a full-screen /inbox route
reusing InboxView, without the code app chrome (header/sidebar/space-switcher).
The existing /code/inbox route is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a numeric Quill destructive badge to the Inbox rail button, mirroring the
code sidebar's actionable-report count. Extract the count into a shared
useInboxSignalCount hook that reuses the sidebar's query cache (no extra
polling).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Expand the Website space with a breadcrumb topbar and sub-navigation:
- /website is now a layout (breadcrumb topbar: Website > <page>) with children:
  index (canvas), new (TaskInput reused via onTaskCreated), settings
  (placeholder), and tasks/$taskId (TaskDetail reused).
- New tasks created from /website/new route into the Website space at
  /website/tasks/$id (not /code) and are tracked in a persisted websiteTasksStore.
- HomeSidebar gains a Website section: Canvas, New task, Settings, plus the
  list of created tasks to return to.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Render HomeSidebar nav items as Quill Button (variant default, size sm,
full-width left-aligned), expressing active via data-selected. Active logic
unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the Website "Canvas" entry with "Dashboards": /website redirects to the
default dashboard at /website/dashboards/$dashboardId, which renders mock
website-data dashboards (traffic, acquisition, engagement, conversion,
performance). The breadcrumb second crumb is a Quill combobox to switch the
active dashboard by name (Website > [dashboard]).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an Edit toggle (Quill outline button, ml-auto, data-selected) to the
dashboards breadcrumb bar. When active, the dashboard swaps its tiles for the
gen-UI canvas + side chat input for that dashboard. Make the canvas chat store
multi-thread (one thread per dashboard) so each dashboard keeps its own chat and
generated canvas.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Disable base-ui combobox filtering (filter={null}) so the dashboard dropdown
always lists every dashboard instead of collapsing to the selected one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the mock dashboards with real, file-backed ones: a main DashboardsService
stores each dashboard as JSON (a json-render spec) under <appData>/dashboards.

- Dashboards list, combobox, and sidebar count now come from the saved files.
- A dashboard renders its saved spec read-only; Edit drops into the gen-UI
  canvas + chat for that dashboard's thread.
- Topbar Save (enabled when the generated spec differs from saved) persists it;
  Save as fork copies the current spec into a new dashboard.
- Empty state + sidebar "New dashboard" create a blank dashboard and open it in
  edit mode. Saved dashboards survive a refresh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wrap create+navigate in try/catch with a toast + log so a failed New dashboard
action (e.g. backend not reachable) no longer silently does nothing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clicking Save now opens a Quill dialog to confirm/enter the dashboard name
before persisting. The dashboard name in the breadcrumb (edit mode) is also
inline-editable: hovering shows a faint border, clicking swaps to a Quill input
sized to match the text (no layout shift), committing on Enter/blur. Save is
disabled while renaming and re-enabled on blur.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Revert to the dropdown switcher in edit mode and a direct Save (no name dialog,
no click-to-rename input).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a Quill button group (refresh | gear) to the dashboard topbar. Refresh
refetches the dashboard data; the gear dropdown selects Static (manual refresh)
or a Polling interval (10s, 10min). While polling, the main button counts down
("Refreshing in XX"). Polling pauses in edit mode so the data stays put.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tching

Wrap the Polling label and interval items in DropdownMenuGroup so the
Menu.GroupLabel has its required Menu.Group context (fixes the
MenuGroupRootContext error). Spin the refresh icon (motion-safe:animate-spin)
while the dashboard data is fetching.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The canvas nav rail and Home/Website/Inbox spaces mount in one place
(__root.tsx). Gate that mount on a new project-bluebird flag: when off, the
app is the pre-canvas code-only shell. Stranded users on a canvas-only path
(cold-boot restore, stale deep link) are sent to /code once flags resolve, so
flagged users aren't bounced off /website during the load window.

- New PROJECT_BLUEBIRD_FLAG constant; defaults on in dev so local canvas work
  isn't hidden behind a flag PostHog doesn't serve locally.
- New useFeatureFlagsLoaded hook to defer the redirect until a flag value is
  trustworthy rather than acting on the false-before-load default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- /website index is now a 3-wide card grid of live dashboard previews
  (CanvasRenderer scaled to a thumbnail) instead of redirecting to the first
  dashboard. Clicking a card opens the full dashboard.
- Each card has a hover-revealed "..." menu (outline) with a destructive
  Delete, rendered as a sibling of the card link so it never navigates.
- New dashboards.delete endpoint + deleteDashboard mutation (ENOENT treated as
  success).
- HomeSidebar nav items now actually highlight the active route: Quill's Button
  doesn't style data-selected, so consume it via a Tailwind utility (mirrors
  SidebarItem) and gate the attr on `active || undefined`.
- Dashboard breadcrumbs are now Website > Dashboards [> {name}], with the
  middle crumb linking back to the index.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Editing a saved dashboard rendered WebsiteCanvas, which only shows the chat
thread's spec. A freshly opened board has no thread yet, so the canvas fell
back to EMPTY_THREAD (spec: null) and looked wiped. Seed the thread from the
saved dashboard spec when entering edit, via a new ensureSpec action that only
hydrates an empty thread (never clobbers a live stream or in-session edits).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ettings)

Replace the placeholder Website/Features/Resources nav with server-backed
channels (desktop file-system folders). Each channel gets its own dashboards
(channel-scoped, file-backed), tasks, and settings, routed under
/website/$channelId. Adds channel create/delete, a Slack-style create modal,
orphan-dashboard migration into the first channel, and breadcrumb/nav polish.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@adamleithp adamleithp closed this Jun 5, 2026
@adamleithp adamleithp changed the title feat(code): canvas — Home space with channels, gen-UI dashboards (WIP) feat(code): canvas — experimental Channels space with gen-UI dashboards (project-bluebird) Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant