Skip to main content
A dedicated workspace per Cerebrum-built project, separate from the global Agent Workbench. Each project gets its own URL (/projects/:slug), its own KPI strip, its own workflow sidebar, its own conversation canvas, and its own ⌘K search. This is the surface long-running products like cachepool are driven from — every prompt, every file, every deploy belongs to one project rather than living as an isolated workflow in the global list. Concretely: a user lands on /projects, sees a card grid of all their projects with status, repo, branch, and last-edit timestamps, and opens one to a workspace that shows the project’s full life — recent workflows, live activity, generated files, cost/time/tokens spent — all in one place. Selecting a workflow inside a project switches the canvas to a wider conversation lane (~1100px composer, vs. ~500px in the legacy three-panel Workbench) with the agent’s scenario, graph, and live deployment preview rendered above the threads.

Refinement (2026-05-31): heterogeneity-aware command-bar console

The /projects/:slug console was reworked from a three-band poster header + single-card feed into a denser, professional layout that treats the project as one abstraction above workflows — a long-running shared context hosting concurrent, heterogeneous workflows from different people (e.g. one person’s build alongside another’s QA pass). What it now enables:
  • A single ~56px command bar carries every fact exactly once. The old triple-redundancy — a synthesised hero sentence, an idle chip, and a six-tile KPI grid all repeating the same workflow/prompt counts — is gone. Counts live only in one inline stat ribbon (N workflows · C/T prompts).
  • The who × what-kind mix is legible at a glance. The command bar shows a contributor avatar stack (real faces via the team-override avatar) + active-kind chips (Build / QA / Research / Docs / Review), with running kinds pulsing. A single-author/single-kind project collapses this to a quiet "Azat · Build" so a focused project never looks busy; a mixed project expands and the board auto-groups by kind.
  • The canvas fills space at 0 / 1 / many workflows. A responsive card grid replaces the single-card feed (no more empty Mars-background void at one workflow); a 0-workflow Launchpad offers an inline composer instead of a lone button in a void.
  • Each card/row answers who+what+state in one glance via a fixed four-corner identity stamp: kind tile (top-left), status pill (top-right), author avatar (bottom-left), prompt numbers + progress underline (bottom-right). Three reserved colour zones — kind = scenario tone, status = status colour, author = avatar ring — so no element ever carries two meanings.
  • needs_attention is now surfaced on /projects (a completed-with-warnings job no longer reads as a clean “completed”), via a shared status module the rail/board/changelog/mix all consume.

Refinement (2026-06-11): collapsible Workflows panel + latest prompt per row

The project dashboard’s Workflows section (apps/frontend/app/(authenticated)/projects/dashboard/project-workflows-list.tsx/projects/dashboard/project-workflows-list.tsx)) now:
  • Collapses to its header, collapsed by default — the dashboard leads with the deployment card; the header keeps the workflow count and the New-workflow action either way, and a caret toggle (rotates on expand) reveals the search box + rows.
  • Shows the latest user prompt in each row, truncated in muted text next to the (often auto-named) workflow title — so a row like workflow:wf-188 also tells you what was last asked of it.
Backing API change (additive): WorkflowStatusResponse gains latest_prompt: Option<String> — the newest prompt_threads.prompt_text per workflow, filled only by the list query (GET /v1/workflows) via a correlated subquery; single-workflow endpoints leave it null since they fetch full prompt threads separately. Paths: mother-ai/src/models.rs, mother-ai/src/routes.rs (fetch_workflows_from_postgres), zod schema in apps/frontend/lib/schemas.ts. Old clients are unaffected (skip_serializing_if on null; .passthrough() schema). The dashboard tab bar (apps/frontend/app/(authenticated)/projects/dashboard/project-dashboard.tsx/projects/dashboard/project-dashboard.tsx)) gains:
  • GitHub Repository link now shows for kind=new projects. Root cause: the worker auto-provisions the GitHub repo for new projects but never backfills projects.repo_url (deliberately — feeding it back as metadata.project.repo on follow-up prompts would flip the worker’s explicit-override path and disable repo auto-provisioning / github_full_name resolution, which Vercel self-heal depends on). Fix: mother-ai now lifts the public remote from the worker’s push audit log (job_runs.audit_log.project_push.remote_url, falling back to project_initial_push.remote_url) into metadata.project.repo_url in both the single-workflow status query and GET /v1/workflows (compose_workflow_metadata in mother-ai/src/routes.rs — the list endpoint now runs the same overlay, which also makes production_url available to list consumers). The dashboard reads project.repoUrl ?? deployment.repoUrl (the .git suffix is trimmed for the browser link).
  • Share button (ShareNetwork icon) — copies the production URL (or the console URL pre-deploy) to the clipboard with a 1.5s “Copied” check flash.
  • Actions menu — a 3-dot trigger (radix @radix-ui/react-dropdown-menu, same pattern as the workflow rail rows) with a single Delete item. Visual affordance only; deletion is not wired up yet. Superseded same-day: Rename and Delete are both live — see the project-lifecycle refinement below.

Refinement (2026-06-11): project lifecycle — guaranteed binding, rename, delete, deployment duration

What it enables:
  • ”+ New workflow” can no longer silently attach to a nonexistent project. Workflow submissions carrying metadata.project.slug are validated against the projects table at enqueue time and the client’s project snapshot (name/kind/base_branch/repo) is overwritten from the authoritative row — so the binding the /projects console relies on is server-guaranteed, not client trust.
  • Rename a project from the dashboard actions menu. Display name only — the slug (and the GitHub repo / Vercel project / workspace naming derived from it) is immutable. The server backfills metadata.project.name on every bound workflow in the same transaction, so legacy projects whose names were derived from the first prompt can finally be fixed, everywhere at once.
  • Delete a project (typed-name confirmation) — removes the project, all of its workflows, prompts, and run history from Cerebrum. The GitHub repository, Vercel project, and any provisioned database are deliberately not touched; the dialog says so.
  • The deployment card’s Duration field is real — workflow wall-clock (completed_at − created_at) of the deployment workflow, formatted via the shared formatDuration; an em dash while queued/running.
What’s changed:
  • mother-ai (mother-ai/src/routes.rs, mother-ai/src/models.rs):
    • validate_and_stamp_project_binding runs in submit_chat, create_workflow, and submit_command_intent (not append_prompt, which inherits stored workflow metadata). Unknown slug → 404. project.repo is only stamped when projects.repo_url is set (kind=existing) so kind=new repo auto-provisioning stays intact; a workflow-pinned design_system_id beats the project default. Without a Postgres DSN (dev mode) validation is skipped.
    • PATCH /v1/projects/:slug (UpdateProjectRequest { name }) — renames + backfills workflow_runs.metadata.project.name via jsonb_set, transactionally, without bumping workflow_runs.updated_at (protects updated_at desc keyset cursors).
    • DELETE /v1/projects/:slug — one transaction: lock project row (for update, so rename/delete races serialize), delete job_runs by both linkage paths (no FK), delete workflow_runs (prompt_threads cascade via FK), delete the project row. Returns 200 + {deleted, slug, workflows_deleted} (the BFF proxy re-serializes JSON bodies, so no 204). Redis queue/status keys self-expire via TTL.
    • WorkflowStatusResponse.completed_at: Option<DateTime<Utc>> — read from workflow_runs.completed_at in both the list and single-workflow Postgres queries; null on Redis fallback paths. Additive (skip_serializing_if).
  • worker (worker/worker/storage/postgres_repo.py): new expression index idx_workflow_runs_project_slug on (metadata->'project'->>'slug') — serves the project-scoped list, the rename backfill, and the delete cascade. Idempotent, added in ensure_schema().
  • Frontend BFF: PATCH/DELETE handlers on apps/frontend/app/api/projects/[slug]/route.ts; PATCH joins the proxy method union in apps/frontend/app/api/mother-ai/_shared.ts.
  • Frontend client/UI: updateProject / deleteProject in apps/frontend/lib/mother-ai-client.ts; completed_at in the zod schema (apps/frontend/lib/schemas.ts); new rename-project-dialog.tsx + delete-project-dialog.tsx under apps/frontend/app/(authenticated)/projects/dashboard//projects/dashboard/); the dashboard’s actions menu wires both, and projects-workbench.tsx drops back to the projects rail after a delete. deriveDeployment computes durationMs consumed by deployment-card.tsx.
Impact scope: all additive except one intentional behavior change — submitting a workflow with a metadata.project.slug that has no projects row now returns 404 instead of queueing. Workflows without project metadata are untouched. Delete is Cerebrum-data-only; a job already running for a deleted workflow finishes and re-upserts a ghost workflow row carrying the dead slug (visible in the global /workflows list, 404 under the project) — accepted under the data-only scope. Rename is safe mid-flight: the worker’s metadata upsert merges stored-wins (excluded.metadata || stored), so a completing job carrying the old name cannot clobber the backfill. Tests: cargo test in mother-ai/ (5 new unit tests on the stamp function: identity refresh, repo None/Some, pass-through, design-system pinning — 23 total green); worker suite worker/.venv/bin/python -m pytest tests/ -v stays 0-fail; tsc --noEmit + biome clean in apps/frontend.

What’s changed

Backend (mother-ai)

  • GET /v1/projects/:slug — single-project lookup by collision-free slug. Returns ProjectRecord (id, name, slug, kind, repo_url, base_branch, created_by, timestamps). Path: mother-ai/src/routes.rs.
  • GET /v1/workflows?project_slug=<slug> — new optional filter on the existing list endpoint. Applies to both page rows and total_count, using a metadata->'project'->>'slug' JSON path filter at the SQL layer. The handler’s optional-clause builder was refactored to a running param-index counter so the next filter we add slots in without re-indexing $N positions.
  • ListWorkflowsQuery.project_slug: Option<String> added to mother-ai/src/models.rs.

Frontend BFF

  • GET /api/projects/[slug] proxies the new mother-ai endpoint through requireApiUser (apps/frontend/app/api/projects/[slug]/route.ts).
  • GET /api/mother-ai/workflows now forwards project_slug when present (apps/frontend/app/api/mother-ai/workflows/route.ts).

Frontend client

  • getProject(slug) and a new projectSlug argument on listWorkflows() in apps/frontend/lib/mother-ai-client.ts.
  • useWorkflowSession(workflowId) hook in apps/frontend/lib/workflow/use-workflow-session.ts lifts the workflow status + prompts + active-job SSE + scenario-derived state out of WorkflowPanels so both the legacy Workbench and the new console share one source of truth. (A prior commit had already updated WorkflowPanels to import this hook without committing the file itself — this change unbreaks HEAD.)
  • “Projects” nav entry added between Workbench and Atlas in apps/frontend/components/layout/app-header.tsx.

Frontend route surface

All under apps/frontend/app/(authenticated)/projects//projects/):
  • page.tsx/projects index, max-w-[1680px] frosted-card layout with a 1–3 column responsive project grid.
  • projects-list-view.tsx — list view + create-new modal (POST /api/projects).
  • [slug]/page.tsx + [slug]/project-detail-view.tsx — the per-project shell. Command bar on top, collapsible sidebar (workflows + activity) on the left, mode-driven canvas on the right (launchpad / board / detail), ⌘K palette and new-workflow dialog overlaid. Holds the lifted status-filter state (shared rail + board), the board groupBy state (localStorage cerebrum:projects:board-groupby, with an auto-vs-explicit default), the mix focus state, and ESC-to-deselect.
  • [slug]/project-header.tsx — the ~56px command bar: identity + ProjectMixSummary + one stat ribbon (the only place counts live) + a quiet Setup popover (repo/branch real; db/scenario as muted configure → placeholders, never coloured fake status) + ⌘K + the one canonical + Workflow CTA. Replaces the old hero sentence + LastActivityBadge + six-tile KPI grid.
  • [slug]/project-mix-summary.tsx — the command-bar who × what-kind roll-up: contributor avatar stack (max 4 + +N) + active-kind chips (running kinds pulse) + a liveness token; collapses to inline "Azat · Build" when calm; clicking an avatar/chip sets a soft board focus (filter-only). Exports the MixFocus type.
  • [slug]/use-project-workflows.ts — shared useInfiniteQuery hook + aggregateProjectStats + new aggregateProjectMix (contributors / kinds / isHeterogeneous, derived purely from loaded pages, no extra fetch). Sidebar, command bar, board, activity strip, and ⌘K palette all consume the same React Query cache key (["project-workflows", slug]).
  • [slug]/project-workflow-sidebar.tsx — compact nav rail. Now prepends a per-row identity stamp (kind glyph + author avatar — author is no longer constant within a project), consumes the shared status module (so needs_attention shows), lifts its status filter up to the shell, and owns its single header (with the new-workflow and collapse controls) — the redundant stacked PROJECT band above it is removed.
  • [slug]/project-changelog.tsx — the Activity strip (kept visible in the sidebar footer): an actor-led, verb-led chronological feed — Azat · Build · completed · "…" — last 12 state changes, using the same avatar + kind vocabulary as the cards. No counts (orthogonal to the mix summary).
  • [slug]/project-workflow-board.tsx — the no-selection canvas (replaces workflow-showcase-feed.tsx): every loaded workflow as a four-corner identity card in a responsive grid (grid-cols-1 lg:grid-cols-2 3xl:grid-cols-3), segmented in place by Kind / Author / Status / None via a Group: control, over the one shared cursor (one Load more sentinel re-buckets all sections — no per-lane pagination). Keeps the showcase card body + useQueries 6-card preview-fetch cap.
  • [slug]/project-launchpad.tsx — the 0-workflow canvas: an inline ComposerTextarea wired to the same submitChat({ project }) path the dialog uses (routes through ScenarioOrchestrator), instead of a lone button in a void.
  • lib/scenario-kind.tsbucketKind(scenario_id) → Build/QA/Research/Docs/Review/Ops/Other (+ KIND_ORDER, kindVisual), the label-side sibling of scenarioIcon. The kind axis of project heterogeneity.
  • lib/workflow-status.ts — shared status vocabulary (effectiveStatus with needs_attention, statusColor/statusDot/statusLabel/statusToFilter, STATUS_FILTERS, relativeTime) lifted from JobsListPanel and consumed by the /projects surfaces; /workflows keeps its own copies untouched (no regression on the reference page).
  • [slug]/selected-workflow-canvas.tsx — when a workflow is selected, mounts (in order): slim header, StatusTicker, WorkflowSummaryTile (cost/tokens/time/files/build), reused ScenarioHeader, reused AgentGraphCard, ArtifactPreview (iframe of metadata.project.production_url when set, dashed placeholder otherwise), collapsible FilesGenerated list, and the existing JobContextPanel (threads + composer). Per-status radial-gradient hue on the canvas.
  • [slug]/new-workflow-dialog.tsx — submits via submitChat({ project: {…} }) so the resulting workflow is stamped with metadata.project.slug and surfaces immediately in the project’s own list.
  • [slug]/project-cmd-k.tsx — project-scoped ⌘K palette. Arrow-key navigation, fuzzy search on objective + workflow id, ”+ Workflow” fallback when nothing matches.

Impact scope

  • Affected subsystems: mother-ai (Rust HTTP layer), Next.js BFF + UI. Worker unchanged. Auth/permissions inherit the existing requireApiUser + non-admin created_by scoping.
  • Backwards compatibility: the new project_slug query param is optional; callers that don’t pass it see exactly the same behaviour as before. Existing workflows that already carry metadata.project on the worker side become filterable immediately — no backfill required.
  • Defaults: /projects is visible to every signed-in user. Workflows created via the new ”+ Workflow” button are stamped with the project metadata automatically. The ⌘K palette only activates when focus is on a /projects/:slug page.
  • Auto-runs: none. No new cron jobs or background workers; the page polls via React Query with the same active/idle intervals the Workbench uses (2s while anything is running, 6s idle).
  • Known stubs (slots reserved, follow-ups tracked elsewhere):
    • db / scenario in the Setup popover — quiet configure → placeholders (no backing field yet); wire to DB provisioning / scenario live-edit when those land. No longer rendered as coloured fake status in the header.
    • Composer attachment dropdown items remain console.info stubs.
    • Pinned/featured workflows not yet implemented.
    • The mix summary and aggregates summarise loaded pages only (one shared cursor); a large project under-reports kinds/people until more pages load — the +N chip signals there may be more. No separate count endpoint.
    • A fuller extraction of WorkflowRow/WorkflowList into shared components (so JobsListPanel consumes them too) is a deliberate follow-up — out of scope here to avoid regressing /workflows.

Tests

  • Rust: cd mother-ai && cargo check — passes. No new unit tests added; the new SQL filter is exercised end-to-end via the FE.
  • TypeScript: cd apps/frontend && npx --no-install tsc --noEmit — passes.
  • Biome: cd apps/frontend && pnpm run lint (or npx --no-install biome check) — passes.
  • Build: cd apps/frontend && pnpm build — passes (exit 0).
  • Manual verification path:
    1. cd apps/frontend && pnpm dev
    2. Visit /projects, click ”+ New project”, create one (slug console-test for example).
    3. Open /projects/console-test with no workflows → the Launchpad composer (not an empty void); submit a prompt and confirm it appears in the rail + board.
    4. With ≥1 workflow → the board card fills its column (no Mars void); confirm the command bar’s stat ribbon is the only place counts appear (no hero sentence / KPI grid). Hit ⌘K to confirm the palette opens.
    5. Heterogeneity: with workflows from two people / two kinds, confirm the command bar shows a contributor avatar stack + kind chips and the board auto-groups by Kind; a single-author/single-kind project collapses the summary to "Azat · Build" and shows a flat grid. Switch the Group: control (Kind/Author/Status/None) and click an avatar/kind chip to focus-filter.
    6. Select a workflow → the canvas swaps to the detail view (summary tile / scenario card / agent graph / artifact preview / files / composer); the rail + command bar persist; the selected rail row gets the cyan border; press ESC to return to the board. Confirm a completed-with-warnings job renders as needs attention on the rail.
    7. Visit /workflows and confirm the Agent Workbench list is unchanged (the shared status module is consumed only by /projects).
  • No pre-existing test regressions introduced by this UI refinement.