CodePlans is a code change coordination tool for engineering teams. It organizes work around a three-level hierarchy: Products → Assets → Code Plans → Tasks. Users track technical debt, coordinate architectural changes, and measure team velocity. Deployed at codeplans.ai. Stack: Next.js 15 (App Router), Drizzle ORM, pluggable auth/DB (SQLite local / Supabase+Postgres cloud).
| Field | Type | Notes |
|---|---|---|
| id | text (UUID) | PK |
| text | ||
| name | text | |
| avatarUrl | text? | |
| billingTier | free|pro|team|enterprise |
default free |
| role | owner|admin|editor|viewer |
default viewer |
| organizationId | text? | FK → organizations (nullable) |
| featureFlags | JSON { alpha?, beta?, aiAssistance? } |
default {} |
| passwordHash | text? | only used in local auth mode |
| createdAt | timestamp |
| Field | Type | Notes |
|---|---|---|
| id | text (UUID) | PK |
| name | text | |
| slug | text | unique |
| ownerId | text | FK → users.id |
| billingTier | free|pro|team|enterprise |
default free |
| productLimit | integer | default 1 |
| createdAt | timestamp |
| Field | Type | Notes |
|---|---|---|
| id | text (UUID) | PK |
| organizationId | text | FK → organizations (cascade delete) |
| userId | text | FK → users (cascade delete) |
| role | owner|admin|editor|viewer |
default viewer |
| invitedBy | text? | FK → users |
| joinedAt | timestamp? | null = pending invitation |
| createdAt | timestamp |
Rule: A member with joinedAt = null is a pending invite (not yet counted as active member).
| Field | Type | Notes |
|---|---|---|
| id | text (UUID) | PK |
| name | text | |
| slug | text | unique |
| description | text | default '' |
| tags | JSON string[] |
|
| organizationId | text? | FK → organizations (set null on delete) |
| creatorId | text | FK → users |
| createdAt | timestamp |
| Field | Type | Notes |
|---|---|---|
| id | text (UUID) | PK |
| productId | text | FK → products (cascade delete) |
| name | text | |
| type | app|service|library|datastore|platform |
|
| description | text | |
| tags | JSON string[] |
|
| health | healthy|warning|critical |
default healthy |
| status | active|deprecated|planned |
default active |
| techDebtScore | integer? | 0–100 scale |
| repositoryUrl | text? | |
| documentationUrl | text? | |
| metadata | JSON object | extensible |
| createdAt / updatedAt | timestamp |
| Field | Type | Notes |
|---|---|---|
| id | text (UUID) | PK |
| sourceAssetId | text | FK → assets (cascade delete) |
| targetAssetId | text | FK → assets (cascade delete) |
| dependencyType | depends_on|integrates_with|aggregates |
|
| description | text? | |
| createdAt | timestamp |
Note: This table exists in the schema but is not yet used by any query or UI.
| Field | Type | Notes |
|---|---|---|
| id | text (UUID) | PK |
| title | text | |
| description | text | |
| productId | text | FK → products (cascade delete) |
| type | refactor|feature|improvement|bugfix |
|
| status | draft|active|completed|cancelled |
default draft |
| tags | JSON string[] |
|
| targetAssetIds | JSON string[] |
asset IDs this plan affects |
| startDate / endDate / deadline | text? | ISO date strings |
| creatorId | text | FK → users |
| assigneeIds | JSON string[] |
user IDs |
| createdAt / updatedAt | timestamp |
| Field | Type | Notes |
|---|---|---|
| id | text (UUID) | PK |
| codePlanId | text | FK → code_plans (cascade delete) |
| assetId | text? | FK → assets (set null on delete) |
| title | text | |
| description | text | |
| status | not_started|in_progress|done |
default not_started |
| priority | low|medium|high|critical |
default medium |
| tags | JSON string[] |
|
| assigneeId | text? | FK → users (set null on delete) |
| estimatedEffort | integer? | hours |
| actualEffort | integer? | hours |
| createdAt / updatedAt | timestamp |
Product visibility (used consistently across all queries):
- If the user has an
organizationId: they see products they created OR products belonging to their organization. - If no org: they only see products they created.
Mutation ownership checks:
updateProduct/deleteProduct: requirescreatorId = userIddeleteCodePlan: requirescreatorId = userIdupdateAsset/deleteAsset/updateCodePlan/updateTask/createTask: no ownership check — any authenticated user can mutate
Team members: Only members with joinedAt IS NOT NULL are treated as active. Pending invites are invisible in the team list and memberCount.
| Function | Returns | Key behavior |
|---|---|---|
getDashboardStats(userId) |
DashboardStats |
Org-aware product scope; velocity = tasksThisWeek (rolling 7 days, done status) |
getProducts(userId) |
Product[] |
Org-aware; includes assetCount + activePlanCount via correlated subqueries |
getProduct(slug, userId) |
Product & { assets } | null |
Org-aware; assets ordered by createdAt desc; dependencies always [] |
getCodePlans(userId, filters?) |
CodePlan[] |
Org-aware; filters: productId, status, type; includes taskCount, completedTaskCount, progress, productName |
getCodePlan(id) |
CodePlanDetail | null |
No auth check; includes full tasks[], assignees[], targetAssets[]; progress = % done |
getTasks(userId, filters?) |
TaskWithContext[] |
Org-aware; filters: planId, assigneeId, status; includes planTitle, assetName, assigneeName |
getOrganization(id) |
Organization & { memberCount } | null |
memberCount = joined members only |
getTeamMembers(orgId) |
TeamMember[] |
Joined members only; includes nested user object |
| Function | Auth check | Notes |
|---|---|---|
createProduct(data, userId) |
none | Returns full product row |
updateProduct(id, data, userId) |
creatorId = userId | Returns null if not found/unauthorized |
deleteProduct(id, userId) |
creatorId = userId | Returns { id } or null |
createAsset(data) |
none | Default health=healthy, status=active |
updateAsset(id, data) |
none | Bumps updatedAt |
deleteAsset(id) |
none | Returns { id } or null |
createCodePlan(data, userId) |
none | Forces status='draft' |
updateCodePlan(id, data) |
none | Bumps updatedAt |
deleteCodePlan(id, userId) |
creatorId = userId | Returns { id } or null |
createTask(data) |
none | Default status=not_started |
updateTask(id, data) |
none | Bumps updatedAt |
updateTaskStatus(id, status) |
none | Narrow update, bumps updatedAt |
deleteTask(id) |
none | Returns { id } or null |
Centered layout with no sidebar.
| Route | Status | Notes |
|---|---|---|
/login |
Wired | Email+password form → signIn server action |
/signup |
Wired | Email+password+name → signUp server action |
Both redirect to / on success.
All routes share AppShell: 64px top header + 256px sidebar. Sidebar contains:
- Product switcher dropdown (cosmetic — switches
selectedProductlocal state but doesn't filter content) - Primary nav: Dashboard, Products, Code Plans, Tasks, Analytics
- Secondary nav: Team, Billing (hidden if
BILLING_ENABLED=false), Settings - Org/user footer: org name + billing tier, links to Team/Billing/Settings
Header: Global search input (cosmetic — not wired), bell icon (cosmetic), user avatar dropdown with sign out.
| Component | Data source | Status |
|---|---|---|
| Greeting (time-based) | user profile | Wired |
StatCards (4 cards) |
getDashboardStats |
Wired — real data |
VelocityChart |
none | Stub — hardcoded placeholder chart |
PlansOverview |
getCodePlans |
Wired — renders plan list |
ActivityFeed |
none | Stub — always shows "No recent activity yet" (activities prop always []) |
- Grid of product cards with name, description (truncated), tags (max 3 shown), asset count, active plan count
- "New Product" button →
/products/new(route does not exist yet) - Card dropdown: View Details, Edit Product →
/products/[slug]/edit(not exists), Add Asset →/products/[slug]/assets/new(not exists), Delete (no action wired) - "Add Product" card as last grid item →
/products/new
Wiring gaps: New/edit/delete product actions not implemented.
Two tabs: Assets and Code Plans.
Assets tab:
- Assets grouped by type (app, service, library, datastore, platform)
- Each card shows: name, type label, health status with color-coded icon, description, tags, tech debt score bar (if present)
- "Add Asset" button →
/products/[slug]/assets/new(not exists) - "Settings" button →
/products/[slug]/edit(not exists)
Plans tab:
- Lists plans for this product: title (link to plan detail), description, status badge, task progress
- "Create Plan" button →
/plans/new?product=[id](route not exists)
Client component (PlansClient) with:
- Summary stats: Active / Draft / Completed counts
- Tab filter: All / Active / Draft / Completed (client-side)
- Product dropdown filter (client-side)
- Plan cards: title (link to detail), type badge, status badge, product name, deadline, assignee count, progress bar, tags
- "New Plan" button →
/plans/new(route not exists)
- Breadcrumb: Code Plans → plan title
- Header: title, type badge, status badge, description, product link, date range, deadline
- Action buttons: "Edit" (not wired), "Activate Plan" (draft only, not wired), "Mark Complete" (active only, not wired)
- Stats row: Overall Progress (% + progress bar + task count), Target Assets (count + names), Assignees (avatars)
- Tags row
- Tasks section: 3-column kanban by status (Not Started / In Progress / Done)
- Done column capped at 5 shown
- "Add Task" button (not wired)
- Task cards: title (strikethrough if done), priority badge, effort hours
Wiring gap: No task status changes from the UI; all task cards are read-only.
Client component (TasksClient) with:
- Summary stats: Total / Not Started / In Progress / Done
- Tab filter by status, plan dropdown filter (active plans only)
- View toggle: List view / Board view
List view: Table with columns: checkbox (cosmetic, reflects status), task title+tags, code plan link, asset name, priority badge, assignee avatar, effort, status.
Board view: 3 kanban columns (up to 8 per column); task cards with priority badge, plan title, assignee avatar, effort.
Wiring gaps:
- Checkbox not wired to
updateTaskStatus - "New Task" button not wired (no form/modal)
- No task detail/edit view
- Requires org membership; shows message if no org
TeamClientrenders:- Org info card: name, member count, billing tier, admins count, pending invites (hardcoded 0)
- Members table: avatar, name, email, role badge (with crown for owner), joined date, kebab menu per row
- Kebab menu options: "Change Role" / "Remove from Team" (neither wired)
- "Invite Member" button opens dialog with email + role select → button closes dialog only (not wired to any action)
All charts use hardcoded static data (not wired to real queries except getDashboardStats for the 4 metric cards at top). Charts (Recharts):
- Team Velocity: area chart (actual vs estimated, 8 weeks — fake data)
- Tasks by Type: donut chart (fake distribution)
- Effort Estimation Accuracy: grouped bar chart by month (fake data)
- Tech Debt by Product: bar-gauge list (fake data)
- AI Insights panel: 3 hardcoded recommendation cards
Wiring gap: All chart data is static placeholder.
Client component with 4 tabs (all cosmetic/unactionable):
- Profile: Name + email inputs, photo upload button, org info card with billing tier
- Notifications: Email and in-app notification toggles (not persisted)
- Features: Feature flag toggles for AI Assistance / Beta / Alpha (reads from props but writes not wired)
- Security: Password change form, 2FA setup, Delete Account button (none wired)
Guarded by BILLING_ENABLED env flag (redirects to / if false). Shows:
- Current plan card with usage progress bars (hardcoded usage values)
- 4-column plan comparison grid (Free $0 / Pro $29 / Team $79 / Enterprise custom)
- Invoice history (3 hardcoded invoices)
Wiring gap: No actual Stripe/payment integration; all usage data is hardcoded.
Auth providers (pluggable via AUTH_PROVIDER env):
local: bcrypt password hash stored in DB, JWT session cookie,adminCreateUserfor seedingsupabase: delegates to Supabase client, session via cookies
DB providers (pluggable via DB_PROVIDER env):
sqlite:@libsql/client+drizzle-orm/libsql, local file or:memory:(tests)postgres:postgres(postgres.js) +drizzle-orm/postgres-js
Feature flags:
BILLING_ENABLED=falsehides billing nav, billing page, and billing info throughout the shell
| Area | Gap |
|---|---|
| Products | No create/edit/delete forms |
| Assets | No create/edit/delete forms; asset_dependencies table unused |
| Code Plans | No create/edit forms; status transitions (Activate, Complete) not wired |
| Tasks | No create form/modal; status changes not wired; no task detail view |
| Team | Invite flow not wired; role change and remove not wired |
| Settings | All save/update actions not wired |
| Billing | Hardcoded usage data; no payment integration |
| Analytics | All charts use hardcoded data; no time-range filtering |
| Activity Feed | Always empty — no activity tracking system |
| Velocity Chart (dashboard) | Placeholder/stub |
| Search | Header search input is cosmetic only |
| Product switcher | Sidebar switcher changes local state only — doesn't filter views |
getCodePlan |
No auth/ownership guard — any authenticated user can fetch any plan by ID |
asset.dependencies |
Always returns [] in getProduct — dependency resolution deferred |
| Notifications | All toggles cosmetic; no notification system exists |