Plan: CreatorTrack Sales CRM, Phase A (schema + kanban + core UI)¶
URL: https://mkdocs.justinsforge.com/memory/plans/creatortrack-sales-crm-phase-a-2026-07-06/
Date: 2026-07-06
Approved spec: Native, workspace-scoped business CRM inside CreatorTrack: crm.companies, crm.contacts, crm.deals (+ configurable crm.deal_stages, crm.activities timeline foundation), data model cribbed from Twenty CRM (people/companies/opportunities/activities). One generic shared KanbanBoard component (WeekStrip pattern) used first by the deals pipeline. New app id sales ("Sales CRM"); existing crm app stays as Personal CRM, existing business app (ERPNext invoices) stays untouched. Deals carry brand-deal fields: deliverables, usage rights, rate, exclusivity window, close date. Contacts carry LinkedIn URL, cadence_days, last_touch_at (reminder logic lands in Phase B). All tables carry workspace id so JustinWieb / Gus Outdoor Co / Gus the Bass pipelines are isolated.
Out of scope (this build):
- Gmail ingest, email-to-contact association, auto last-touch, cadence reminders (Phase B)
- Contracts, tasks linkage, ERPNext invoice link (Phase C)
- Outreach draft agent, any LinkedIn automation (Phase D; LinkedIn has no usable personal API, URL field only)
- Merge to main, deploy, dev server (isolated worktree, --no-server)
Conventions locked during scoping:
- Worktree slot: ~/forge/scripts/forge_creatortrack_agent_bootstrap.sh feat/sales-crm 3064 --no-server; <WT> below = worktree path the bootstrap prints; dev DB clone = ct_feat-sales-crm.
- Migrations apply via (cd <WT> && PGDATABASE=ct_feat-sales-crm WORKSPACE_MIGRATIONS_DIR=db/migrations python3 ~/forge/scripts/forge_workspace_migrate.py).
- No dev server this build, so UI verification = typecheck/build + SQL-level checks; click-through happens when Justin opens a dev slot later.
- Follow RLS + role pattern from recent db/migrations/2026070*_finances_native_*.sql and db/schema.ts notes (app role never admin).
- App registration follows the rss (News) pattern: PG-native, route-based, not in APP_NODE_TYPES.
Task list¶
Task 1: Bootstrap isolated worktree + DB clone¶
- Files: none in repo (creates
<WT>andct_feat-sales-crm) - What: Run
~/forge/scripts/forge_creatortrack_agent_bootstrap.sh feat/sales-crm 3064 --no-serveroff current main. Record<WT>. - Verification:
git -C <WT> branch --show-currentprintsfeat/sales-crm;psql -lqt | grep ct_feat-sales-crm - Commit: none (no repo change)
Task 2: Core schema migration¶
- Files:
<WT>/db/migrations/20260706TXXXXXX_sales_crm_core.sql - What: Create
crm.companies(workspace_id, name, domain, linkedin_url, industry, city, notes),crm.contacts(workspace_id, company_id FK, first_name, last_name, emails text[], phone, job_title, linkedin_url, city, cadence_days int null, last_touch_at timestamptz null, notes),crm.deal_stages(workspace_id, key, label, position, is_won, is_lost; unique(workspace_id,key)),crm.deals(workspace_id, company_id, primary_contact_id, name, amount_cents bigint, currency, stage_key, close_date, deliverables jsonb, usage_rights text, exclusivity_until date, sort_order),crm.activities(workspace_id, contact_id null, deal_id null, kind check in note/email/call/meeting/touch, body, occurred_at). RLS + grants matching the finances-native migrations. Note:crmschema already holdscrm.people(Personal CRM); new tables coexist, no changes tocrm.people. - Verification:
(cd <WT> && PGDATABASE=ct_feat-sales-crm WORKSPACE_MIGRATIONS_DIR=db/migrations python3 ~/forge/scripts/forge_workspace_migrate.py)thenPGDATABASE=ct_feat-sales-crm psql -c "\dt crm.*"shows the 5 new tables - Commit:
feat(sales-crm): core schema (companies, contacts, deals, stages, activities)
Task 3: Default stage seeding¶
- Files:
<WT>/lib/sales-crm.ts(new, seeding portion only) - What:
ensure_default_stages(workspace_id)upsert: lead, contacted, negotiating, contracted, delivered, paid, lost (idempotent, unique key on workspace_id+key). Called lazily from the list endpoint so every workspace gets a pipeline on first open. - Verification:
PGDATABASE=ct_feat-sales-crm psql -c "select count(*) from crm.deal_stages"after invoking seed via a one-line node script against the clone (documented in task); count = 7 and re-run stays 7 - Commit:
feat(sales-crm): idempotent default deal stages per workspace
Task 4: Data layer CRUD¶
- Files:
<WT>/lib/sales-crm.ts - What: Workspace-scoped CRUD for companies, contacts, deals (list/create/update/archive), deal stage move + sort_order reorder, activity insert/list. Every query filters by workspace_id; no cross-workspace reads.
- Verification:
(cd <WT> && npx tsc --noEmit) - Commit:
feat(sales-crm): workspace-scoped data layer
Task 5: API routes¶
- Files:
<WT>/app/api/apps/sales/companies/route.ts,.../contacts/route.ts,.../deals/route.ts,.../deals/move/route.ts,.../activities/route.ts - What: Thin JSON handlers over lib/sales-crm.ts using the same auth/workspace-resolution helpers as
app/api/apps/rss. Move endpoint takes deal id + stage_key + sort_order (idempotent upsert of position). - Verification:
(cd <WT> && npx tsc --noEmit && npx next build --no-lint 2>&1 | tail -3)builds clean - Commit:
feat(sales-crm): API routes for companies, contacts, deals, activities
Task 6: Shared KanbanBoard component¶
- Files:
<WT>/components/apps/shared/KanbanBoard.tsx,<WT>/components/apps/shared/kanban.css - What: Generic controlled component:
columns[{key,label,accent?}],cards[{id,columnKey,sortOrder,render}],onMove(cardId, toColumnKey, toIndex). HTML5 drag-drop, no new deps, knows nothing about deals. Mobile: horizontal scroll columns (ws-msheet conventions where applicable). - Verification:
(cd <WT> && npx tsc --noEmit) - Commit:
feat(shared): generic KanbanBoard component (WeekStrip pattern)
Task 7: Sales app shell + registration¶
- Files:
<WT>/lib/apps.ts,<WT>/components/apps/sales/SalesApp.tsx,<WT>/components/apps/sales/SalesClient.tsx,<WT>/components/apps/sales/sales.css, route wiring per rss pattern (<WT>/app/(suite)/...equivalent) - What: Register app
{id: "sales", name: "Sales CRM", ownerOnly: true, tagline: "Companies, contacts, deal flow"}. Shell with three tabs: Deals / Contacts / Companies; installable per workspace via existing App Library. - Verification:
(cd <WT> && npx next build --no-lint 2>&1 | tail -3)builds;grep -n '"sales"' lib/apps.ts - Commit:
feat(sales-crm): app shell + App Library registration
Task 8: Companies + Contacts views¶
- Files:
<WT>/components/apps/sales/CompaniesView.tsx,<WT>/components/apps/sales/ContactsView.tsx,<WT>/components/apps/sales/ContactPanel.tsx - What: List + create/edit. ContactPanel shows profile (title, company, LinkedIn link opens new tab, cadence_days field, emails), activity timeline (manual notes/touch logging via activities endpoint).
- Verification:
(cd <WT> && npx tsc --noEmit) - Commit:
feat(sales-crm): companies and contacts views with contact panel
Task 9: Deals kanban + deal dialog¶
- Files:
<WT>/components/apps/sales/DealsBoard.tsx,<WT>/components/apps/sales/DealDialog.tsx - What: DealsBoard renders KanbanBoard from stages+deals, onMove calls /deals/move. DealDialog: name, company, primary contact, amount, close date, deliverables (line items), usage rights, exclusivity date. Cards show amount + company + close date.
- Verification:
(cd <WT> && npx next build --no-lint 2>&1 | tail -3)builds clean - Commit:
feat(sales-crm): deals kanban board + deal dialog
Task 10: Register and document¶
- Files:
/home/justinwieb/forge/memory/general/reference_creatortrack_sales_crm.md(with mkdocs URL line),~/.claude/.../MEMORY.mdindex line, this plan updated with outcome - What: Topic file: schema, app id, migration name, worktree/branch, what Phases B-D still owe. Index entry under CreatorTrack block. Run
bash /home/justinwieb/forge/scripts/forge_eval_run.sh. - Verification:
bash /home/justinwieb/forge/scripts/forge_eval_run.shpasses;grep sales_crm /home/justinwieb/forge/memory/general/reference_creatortrack_sales_crm.md - Commit:
docs(sales-crm): register Phase A in memory + topic file(forge repo)
Outcome (2026-07-06)¶
All tasks complete on branch feat/sales-crm (5 commits, worktree /home/justinwieb/forge-suite-wt/feat/sales-crm, dev slot http://100.97.43.104:3064/sales). Deviations from plan: Justin asked for a dev server so the slot runs one (WORKSPACE_DEV_AUTH=1); the API is a single op-dispatched route (matches the personal-CRM house style) instead of five files; Tasks 7-9 landed as one buildable UI commit. Extra fix found by live testing: postgres.js returns bigint ids as strings, so all id selects cast ::int. End-to-end CRUD verified against the running dev server. Docs at reference_creatortrack_sales_crm. NOT merged, NOT deployed.
Review notes for Justin¶
- App name "Sales CRM" (id
sales) is my pick to avoid colliding with Personal CRM; rename is a one-liner in Task 7 if you prefer "Deal Flow" or "Business CRM". - Default stages (lead → contacted → negotiating → contracted → delivered → paid, + lost) are editable per workspace later; Phase A ships them fixed-but-seeded.
- No dev server per session rules, so visual QA waits for a dev slot; everything else verifies via build + SQL.