project.kind = source of truth (retire date-derived label) — List/Plan foundation

Seen in 1 project by 1 person

About

Make projects.kind the single source of truth for what a project IS, and retire the date-derived projectKindLabel heuristic. Foundation for the user-facing List/Plan terminology rename (copy threading is a SEPARATE follow-up — do NOT do copy here).

Why

projects.kind already exists (text, CHECK list|decision|plan, DEFAULT 'list', migration 20260502100001) and the public section routes off it. But the protected app ignores it and derives plan-vs-list from dates via projectKindLabel(project) (11 call sites). NewProjectModal never sets kind, so new projects sit at the 'list' default while the in-app UI calls them plans once they have dates → the stored kind and the in-app label can DISAGREE (a dated project is a "plan" in-app but kind='list' in the DB and would route publicly as a list). This unifies on the stored column.

Decisions (locked unless a reviewer overrides)

  • D1 create UX: NewProjectModal gets a List / Plan segmented toggle, default List. Do NOT add 'decision' to the create flow; existing decision projects keep working via their current path.
  • D2 kind ⊥ dates: kind is explicit; start_date/end_date mean schedulability (DayView/itinerary) ONLY. Adding/removing dates must NOT change kind.
  • D3 backfill: new migration (unique HHMMSS, check history) — UPDATE public.projects SET kind='plan' WHERE kind='list' AND (start_date IS NOT NULL OR end_date IS NOT NULL); Idempotent. Leaves 'decision' and any explicitly-set kind untouched. Preserves current in-app classification.
  • D4 retire projectKindLabel: replace all 11 projectKindLabel(...) usages with reads of project.kind. Tab mapping: Plans tab → kind='plan', Shortlists tab → kind='list'. Sites incl: components/projects/ProjectListView.tsx, app/app/(protected)/projects/[projectId]/kindIndex.ts, .../settings/page.tsx (the wasPlan logic at ~L304 — base it on stored kind, not dates), lib/mcp/tools/projects.ts (2 sites). Remove projectKindLabel from @pickism/shared once unused (keep isSchedulable/isLocatable).
  • Upsert: create_project (apps/web/app/api/projects/route.ts + lib/mcp/tools/projects.ts) accepts optional kind (validated ∈ {list,plan,decision}, default 'list') and persists it; update_project allows changing kind. Settings page gets a kind control (replace the date-inferred wasPlan).
  • CHECK stays list|decision|plan. The "both list and plan" case is intentionally NOT modeled yet.

Verify

  • shared + web tsc clean; shared tests; full web vitest (wrap any newly kind-dependent tests appropriately); add tests for: create persists kind, update changes kind, backfill rule, the retired-label sites read kind. Apply the backfill migration to the dev DB (supabase migration up, never reset) and confirm.
  • This touches supabase/migrations/**review-gated, do NOT auto-merge. Open the PR, hand-off comment, state=review, keep worktree. Prod migration-first is the reviewer's step.

Out of scope (separate follow-up)

The EN/FR copy threading — rendering kindLabel(project.kind) (with FR gender/articles) in the ~85 'project' strings. That task depends on this one.

Links

No links shared yet.

Listed in

Bookmarked in

Not in any public bookmark categories yet.