# Phase 6 — Navigation and Project Kind Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Group projects by client on the home page and in a new sidebar "Kanban" flyout that jumps straight into a project's board; make the dashboard the click target for a project tile; add a `kind` field so a project can be tagged `prototype` — full board, full dashboard, same permissions, just visually distinguished. **Architecture:** One new select field on `projects` (`kind: "project" | "prototype"`, backfilled to `"project"`). All new browsing surfaces (home page grouping, sidebar flyout) read with the caller's own PocketBase token, exactly like the existing home page and `/plan` — the Phase 1 read rules do the scoping, not a UI filter or a role branch anyone could forget. `AppShell` becomes an async server component that loads the sidebar's project tree once and passes it down, so no existing page needs to change to get the new nav. **Tech Stack:** Next.js 16 App Router · PocketBase 0.30 · Tailwind 4 · vitest **Spec:** `docs/superpowers/specs/2026-09-17-phase-6-navigation-and-project-kind-design.md` **Prerequisite:** Phase 1 complete (clients/projects/read-rules exist). No other phase is required. --- ## What already exists — do not rebuild this The dashboard already has a "Back to board" link (`src/app/p/[clientSlug]/[projectSlug]/dashboard/page.tsx:66-73`), and the board's `TopBar` already links to "Dashboard" (`src/app/p/[clientSlug]/[projectSlug]/page.tsx`). **The dashboard ↔ board shortcut from the spec already works.** Task 5 verifies this directly instead of writing new code for it — if it turns out broken, fix it there, but do not add a second link. --- ## File structure ``` scripts/ └── setup-remote.ts # + ensureProjectKindField src/ ├── lib/ │ ├── pb/types.ts # + ProjectKind, kind on ProjectRecord │ └── data/ │ └── board.ts # + loadSidebarProjects, SidebarProjectGroup ├── app/ │ ├── actions/ │ │ └── projects.ts # createProject accepts kind │ ├── page.tsx # grouped by client, prototype tag, links to dashboard │ └── admin/projects/page.tsx # Kind column └── components/ ├── admin/ │ └── project-form.tsx # Kind select └── shell/ ├── app-shell.tsx # async — loads the sidebar tree ├── sidebar.tsx # renders KanbanNav └── kanban-nav.tsx # NEW — the flyout tests/ └── integration/ └── project-kind-and-nav.test.ts # NEW ``` --- ## Task 1: The `kind` field **Files:** - Modify: `scripts/setup-remote.ts` - Modify: `src/lib/pb/types.ts` - [ ] **Step 1: Add the type** In `src/lib/pb/types.ts`, add beside the other project-related types (near `ProjectStatus`, line 5): ```ts export type ProjectKind = "project" | "prototype"; ``` Then add `kind` to `ProjectRecord`: ```ts export interface ProjectRecord extends BaseRecord { client: string; name: string; slug: string; description: string; status: ProjectStatus; kind: ProjectKind; color: string; repo_owner: string; repo_name: string; repo_url: string; /** Server-only. Must never appear in a browser payload. */ webhook_secret: string; last_ingest_at: string; session_gap_minutes: number; pre_session_minutes: number; post_session_minutes: number; } ``` - [ ] **Step 2: Add the migration helper** In `scripts/setup-remote.ts`, add a new function beside `ensureProjectIngestFields` (after it, around line 183): ```ts /** * Add the `kind` field to projects, for Phase 6's project/prototype * distinction — a prototype is a full project (board, dashboard, same * permissions), just tagged as not yet a committed piece of work. * * Backfilled to "project" for every existing row. Nothing already in * production should read as a prototype just because the field is new. * * Re-runs are safe: the field is not duplicated, and backfill only touches * rows where kind is still unset. */ async function ensureProjectKindField(pb: PocketBase): Promise { const projects = await pb.collections.getOne("projects"); const have = new Set(projects.fields.map((f: { name: string }) => f.name)); if (!have.has("kind")) { const fields = [...(projects.fields as unknown[])]; fields.push({ name: "kind", type: "select", required: false, maxSelect: 1, values: ["project", "prototype"], presentable: false, hidden: false, system: false, }); await pb.collections.update(projects.id, { fields }); console.log(" + projects field: kind"); } else { console.log(" = projects.kind already present"); } const unset = await pb.collection("projects").getFullList({ filter: 'kind = ""', fields: "id", }); for (const project of unset) { await pb.collection("projects").update(project.id, { kind: "project" }); } console.log(` = project kind backfilled (${unset.length} row(s))`); } ``` - [ ] **Step 3: Call it from `main()`** In `main()`, directly after the existing `await ensureProjectIngestFields(pb);` call (around line 898): ```ts // Add ingest-related fields to projects (only if missing — the projects // collection already exists from earlier migrations). await ensureProjectIngestFields(pb); // Add the project/prototype kind field for Phase 6. await ensureProjectKindField(pb); ``` - [ ] **Step 4: Run it against the live PocketBase** ```bash set -a && source .env.local && set +a && npm run setup-remote ``` Expected: prints `+ projects field: kind` (or `= projects.kind already present` on a re-run) followed by `= project kind backfilled (N row(s))`. - [ ] **Step 5: Verify the build** ```bash npm run build ``` Expected: build succeeds. - [ ] **Step 6: Commit** ```bash git add scripts/setup-remote.ts src/lib/pb/types.ts git commit -m "feat(projects): add kind field (project | prototype)" ``` --- ## Task 2: Creating and displaying a project's kind **Files:** - Modify: `src/app/actions/projects.ts` - Modify: `src/components/admin/project-form.tsx` - Modify: `src/app/admin/projects/page.tsx` - Modify: `src/app/page.tsx` - [ ] **Step 1: Accept `kind` in `createProject`** In `src/app/actions/projects.ts`, change the `createProject` signature and the record it creates: ```ts export async function createProject(input: { clientId: string; name: string; description: string; repoOwner: string; repoName: string; color: string; kind?: ProjectRecord["kind"]; }): Promise<{ id: string }> { await requireOwner(); const pb = await getServerPb(); const name = input.name.trim().slice(0, 120); if (!name) throw new Error("A name is required"); const siblings = await pb.collection("projects").getFullList({ filter: `client = "${input.clientId}"`, fields: "slug", }); const project = await pb.collection("projects").create({ client: input.clientId, name, slug: uniqueSlug(slugify(name), siblings.map((p) => p.slug)), description: input.description.slice(0, 20000), status: "active", kind: input.kind ?? "project", color: input.color || "#2f6dad", repo_owner: input.repoOwner.trim(), repo_name: input.repoName.trim(), repo_url: "", }); ``` The rest of the function (column seeding, error handling, `revalidatePath`) is unchanged. - [ ] **Step 2: Add the Kind toggle to the create form** In `src/components/admin/project-form.tsx`, add state and a select: ```tsx "use client"; import { useState, useTransition } from "react"; import { Plus } from "lucide-react"; import { createProject } from "@/app/actions/projects"; import { DEFAULT_BRAND_COLOR } from "@/lib/brand-default"; import type { ClientRecord, ProjectRecord } from "@/lib/pb/types"; export function NewProjectForm({ clients }: { clients: ClientRecord[] }) { const [open, setOpen] = useState(false); const [clientId, setClientId] = useState(clients[0]?.id ?? ""); const [name, setName] = useState(""); const [description, setDescription] = useState(""); const [repoOwner, setRepoOwner] = useState(""); const [repoName, setRepoName] = useState(""); const [kind, setKind] = useState("project"); const [pending, startTransition] = useTransition(); const [error, setError] = useState(null); if (clients.length === 0) { return (

Create a client first.

); } function submit(event: React.FormEvent) { event.preventDefault(); if (!name.trim() || !clientId) return; setError(null); startTransition(async () => { try { await createProject({ clientId, name, description, repoOwner, repoName, color: DEFAULT_BRAND_COLOR, kind, }); setName(""); setDescription(""); setRepoOwner(""); setRepoName(""); setKind("project"); setOpen(false); } catch (e) { setError(e instanceof Error ? e.message : "Could not create that project"); } }); } if (!open) { return ( ); } const field = "w-full rounded-lg border border-border-subtle bg-panel px-3 py-2 text-sm text-text-primary outline-none focus:border-brand"; return (