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¶
- 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. - 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. - 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_dateelse the newcrm.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. Addcrm.deal_stage_eventsfirst if that is ever wanted. - 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 inlistActivities). - 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.
- Quick-new.
useQuickNewwired: sidebar "+ New deal" (/sales?new=<nonce>) opens the deal modal on the Deals tab. - App Library graduation (goal 2).
lib/apps.ts:salesdroppedownerOnly, refreshed tagline/description; card now renders LIVE in the Business section (verified in the Library modal)./salespage owner gate removed (crm.* is workspace-scoped by RLS + explicit workspace_id, same graduation move as Social). Gmail surfaces stay owner-only:gmail_syncop 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 newemailSyncprop.
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 --noEmitclean.
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_evaluatecalls. - Known pre-existing (not touched): the floating
ws-toprowoverlay 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
lifeosat 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/