Skip to content

Handoff: Sales CRM beef-up + App Library graduation (2026-07-06)

Branch: feat/sales-crm-beefup (worktree ~/forge-suite-wt/feat/sales-crm-beefup, DB ct_feat_sales_crm_beefup, dev :3069) Status: built, migrated, verified end-to-end on the dev slot. NOT merged, NOT deployed.

What shipped

  1. Weighted forecast. crm.deal_stages.probability (0-100; lead 10 / contacted 25 / negotiating 50 / contracted 80 / delivered 90 / paid 100 / lost 0). Deals tab gets a forecast strip (Open pipeline, Weighted forecast, Won) plus per-column value totals in the kanban headers.
  2. Follow-up snooze. crm.contacts.snoozed_until. Snooze select (1d/3d/1w/2w/1mo) on every Follow-ups row; snoozed contacts drop off the strip and show "snoozed until " with a Clear snooze button in the contact panel. touchState() stays the single cadence-math source.
  3. Insights tab. Stat tiles (open pipeline, weighted forecast, won revenue, win rate, avg won deal), won-revenue-by-month bars (12 months, bucketed by close_date else the new crm.deals.stage_changed_at, stamped on every stage change), and pipeline-by-stage bars. All computed from the one-shot dataset, no new endpoint. Deliberately no stage-conversion funnel: there is no stage-transition history table, and faking conversion from current positions would lie. Add crm.deal_stage_events first if that is ever wanted.
  4. Company account view. Companies tab is now list + panel (Contacts-style split): facts, People (click-through to Contacts tab), Deals with stage + amount, won-revenue in the list row, and a merged account timeline (GET ?companyId= unions activities across the company's contacts AND deals in listActivities).
  5. Search. Header search box filters all three data tabs client-side (deals by name/company/notes, contacts by name/title/company/email/city, companies by name/industry/domain/city). Deals shows an "N of M match" note; forecast totals intentionally stay unfiltered.
  6. Quick-new. useQuickNew wired: sidebar "+ New deal" (/sales?new=<nonce>) opens the deal modal on the Deals tab.
  7. App Library graduation (goal 2). lib/apps.ts: sales dropped ownerOnly, refreshed tagline/description; card now renders LIVE in the Business section (verified in the Library modal). /sales page owner gate removed (crm.* is workspace-scoped by RLS + explicit workspace_id, same graduation move as Social). Gmail surfaces stay owner-only: gmail_sync op and ?messageBody= body fetch now 403 for non-owners (they ride the owner's server-level token), and the Sync email button hides via the new emailSync prop.

Migration

db/migrations/20260706T235712_..._stage_probability_contact_snooze_deal_stage_changed.sql: three ADD COLUMN IF NOT EXISTS + probability backfill that only touches rows still at DEFAULT 0 (idempotent, never clobbers user-tuned values). Applied to the branch clone.

Verified (dev :3069, seeded 2 companies / 2 contacts / 5 deals)

  • Forecast math correct ($15,200 open, $4,550 weighted, $6,000 won), column totals render.
  • Follow-ups: overdue sort, Log touch clears the row, snooze hides Ravi, snoozed label + Clear snooze in panel.
  • Insights tiles, month bars (May 26: $6,000), stage bars all correct; win rate 1W/1L = 50%.
  • Company panel people/deals/merged timeline; search 2/5 "volt"; quick-new modal opens and nonce cleans from URL.
  • App Library: Sales CRM under Business, not Early access.
  • Fixed during verify: SSR hydration mismatch in compact currency (Node ICU "$6.0K" vs browser "$6K"); fmtCompact is now hand-rolled deterministic. Console is 0 errors.
  • npx tsc --noEmit clean.

Gotchas hit

  • The dev-slot reaper reaped this slot mid-session (uncommitted work lost once; slot re-bootstrapped, changes replayed, committed immediately). Lesson re-learned: in WDA slots, commit after every coherent chunk; an idle slot has a 45-minute fuse.
  • The shared Playwright MCP browser is contended with other agent sessions (tab stealing); verification ran inside single atomic browser_evaluate calls.
  • Known pre-existing (not touched): the floating ws-toprow overlay can intercept clicks on the lower half of the app tab row at narrow viewports.

To review / next

  • components/apps/sales/InsightsView.tsx (new), CompaniesView.tsx (rewritten), DealsView.tsx, FollowUpsStrip.tsx, SalesClient.tsx, ContactsView.tsx, lib/sales-crm.ts, app/api/apps/sales/route.ts, app/(suite)/sales/page.tsx, lib/apps.ts, components/apps/shared/KanbanBoard.tsx (label: string -> ReactNode only).
  • Merge notes: migration must be applied to live lifeos at merge; rm -rf .next-dev + restart creatortrack-dev after merging (route-adjacent changes), per the stale-types gotcha.

Branch: feat/sales-crm-beefup Preview: http://100.97.43.104:3069 (Deals / Insights tabs, Companies panel, Follow-ups snooze; App Library via account menu > Get apps)

URL: https://mkdocs.justinsforge.com/memory/handoffs/creatortrack-sales-crm-beefup-2026-07-06/