Skip to content

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> and ct_feat-sales-crm)
  • What: Run ~/forge/scripts/forge_creatortrack_agent_bootstrap.sh feat/sales-crm 3064 --no-server off current main. Record <WT>.
  • Verification: git -C <WT> branch --show-current prints feat/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: crm schema already holds crm.people (Personal CRM); new tables coexist, no changes to crm.people.
  • Verification: (cd <WT> && PGDATABASE=ct_feat-sales-crm WORKSPACE_MIGRATIONS_DIR=db/migrations python3 ~/forge/scripts/forge_workspace_migrate.py) then PGDATABASE=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.md index 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.sh passes; 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.