CreatorTrack University + Gamification + Tasks/Pages Overhaul, Build Plan (2026-07-10)¶
Branch: feat/ct-university-tasks-assets (worktree /home/justinwieb/forge-suite-wt/feat/ct-university-tasks-assets, dev :3061, slot DB ct_feat_ct_university_tasks_assets). All work in ONE worktree, dependency-ordered, commit each task, NO parallel writers. Migrations idempotent (IF NOT EXISTS), applied via PGDATABASE=ct_feat_ct_university_tasks_assets WORKSPACE_MIGRATIONS_DIR=db/migrations python3 ~/forge/scripts/forge_workspace_migrate.py. Never delete rows that carry progress. Never run a full prod build (OOM); verify via :3061 + scoped tsc/eslint.
Justin's approved decisions¶
- Connections: mutual "add each other" graph (add by email + accept), NOT workspace-membership.
- Extra features: ALL (streaks+daily goal, timestamped in-video notes & bookmarks, completion certificates, activity feed).
- Points: platform-wide currency with (a) a profile setting toggle to opt out, (b) a combined total AND a University-only subtotal.
- Leaderboard: connections-scoped (you + accepted connections), timeframes week/month/lifetime.
- University visible only where installed (personal workspace shows neither University nor Creatorpedia).
WAVE 1 (clear-cut UX fixes) — separate workflow, re-run first¶
Script: /home/justinwieb/.claude-sub2/projects/-home-justinwieb-forge/e2568836-499b-4093-8416-eac446a2b8bd/workflows/scripts/ct-clearcut-build-batch-wf_d9919d65-fb7.js (runId wf_d9919d65-fb7, all 4 died on session limit, nothing cached). Tasks: (1) instant optimistic page/app delete + drag-to-trash (PageActionsMenu.tsx:235, PageTree.tsx:333/900, new trashDropBus.ts mirroring favoritesDropBus.ts, TrashMenu.tsx:24); (2) tasks toolbar+peek redesign (slim Group/Sort pills TaskFilterSortBar.tsx:302, one-row date fields Cell.tsx:220, multi-assignee Cell.tsx:476 + seed system-apps.ts:257/298, unify Project/ProjectRel, restyle .dbrel-add); (3) creatorpedia decouple (lib/university.ts:417/526, layout.tsx:61, lib/data.ts:113, lib/apps.ts new isInternalRouteAppNode, gate on getInstalledApps); (4) asset-library block (blocks/types.ts, BlockRenderer.tsx, BlockList.tsx, migration widening blocks CHECK, reuse /api/files/upload + downloadUrl()).
WAVE 2 phases (this plan)¶
Phase 1 — Global catalog + account-scoped progress/notes (Opus, riskiest)¶
Files: db/migrations/<new>, lib/university.ts, seed.
- Drop workspace_id from courses/modules/lessons; add global-read RLS ("any authenticated user reads"); UNIQUE(title) on courses, UNIQUE(course_id,title) on modules, UNIQUE(module_id,url) on lessons.
- Reconcile duplicate justin/gus-the-bass catalogs: pick canonical lesson_id by joining on url; repoint lesson_progress/quiz_attempts/lesson_notes to the canonical id; collapse with ON CONFLICT (lesson_id,user_id). Do not drop rows with progress; reconcile. The Phase 1 worker MUST first read the current schema + seed script to confirm exact column names and whether the two catalogs have already diverged (clean collapse vs manual reconcile), then write the migration.
- Drop workspace_id from lesson_progress/quiz_attempts (keep UNIQUE(lesson_id,user_id)).
- Notes: pin the lesson-notes doc node to the user's kind='personal' workspace (lib/university.ts:~464) so notes are identical in every workspace.
- Course create/edit → owner-only.
- Verify: same user sees identical progress + notes across two workspaces on :3061.
- Commit: feat(university): global catalog + account-scoped progress and notes.
Phase 2 — Multi-creator + resources; Creatorpedia = creator directory (Sonnet)¶
- New
creatorstable +course_creators(course↔creator many-to-many). A course can list Colin AND Samir. creator_resources(creator_id, kind, label, url) = the creator's channels/links ("categories = resources").- Remove lessons that are literally "a bullet point: watch the channel"; those become creator resources instead.
- Creatorpedia UI renders the creator directory inside University only.
- Commit:
feat(university): multi-creator courses + creator resources + creatorpedia directory.
Phase 3 — University UI: Home / Explore / My Courses tabs (Sonnet)¶
user_courses(user_id, course_id, added_at) = "added / My Courses".- Home tab (default, top-left): courses you've STARTED (in-progress) + progress/points/level/streak snapshot.
- Explore tab: ALL global courses, grouped by category/creator/resource.
- My Courses tab: only courses in
user_courses; "Add to My Courses" action; progress shows for all added. - Remove the "+ New Course" button.
- Commit:
feat(university): home/explore/my-courses tabs + add-to-my-courses.
Phase 4 — Gamification core: CT Points, levels, badges, streaks (Opus)¶
New gamification schema:
- points_ledger(user_id, source_type, source_id, points, created_at, UNIQUE(user_id,source_type,source_id)) — idempotent awards. Award at University progress/quiz completion call-sites with source_type='university'. source_type extensible (future tasks,habits,goals).
- Combined total = SUM all; University subtotal = SUM WHERE source_type='university'. Show both.
- level = deterministic fn(combined total).
- badges + user_badges (grant rules).
- Streaks: daily activity streak + streak-freeze + customizable daily goal (user_streaks, streak_freezes).
- Profile setting gamification_enabled (default true); when off, hide that user's points/level/badges/streak UI and exclude them from leaderboards (their view and others').
- Commit: feat(gamification): CT points ledger, levels, badges, streaks + opt-out.
Phase 5 — Connections + leaderboard + activity feed (Opus)¶
core.user_connections(requester_id, addressee_id, status, created_at)via existinglib/invites.tsconsent + bell notification. Add by email + accept; symmetric visibility once accepted.- Leaderboard: connections-scoped (self + accepted), timeframes week/month/lifetime, metric = modules completed (primary) + CT points; respect
gamification_enabledopt-out. Reuse existinggetWorkspaceProgress/ProgressBoardpattern ->getConnectionsProgress+app/(suite)/leaderboard/page.tsx. - Activity feed: connections' milestones (course finished, level up, badge earned).
- Commit:
feat(social): connections graph + connections leaderboard + activity feed.
Phase 6 — Profile area + timestamped notes/bookmarks + certificates (Sonnet)¶
- Profile page (
ProfilePanel.tsx:~259): combined points + University points, level, badges, streak, connections list + pending requests, thegamification_enabledtoggle. - Timestamped in-video notes & bookmarks pinned to a video timestamp; "my notes across all courses" view (reuse notes infra).
- Completion certificate (shareable / LinkedIn) per finished course.
- Commit:
feat(university): profile stats + timestamped notes + completion certificates.
Phase 7 — Tasks subtasks (Notion-style) (Opus) — independent of University¶
Recon (confirmed): the DB engine stores relation links in app.collection_relations (NOT props JSON). Self-relation precedent = "Blocked By" / "Parent Project" (lib/system-apps.ts:370/374, config:{target_db: tasksId}). Rollup precedent in lib/prop-types.ts:339 + eval lib/data.ts:351, agg lib/collection.ts:409. Indent precedent components/apps/projects/ProjectStatusBoardView.tsx:18-26,63. Load pattern lib/tasks.ts:982-992 (parentByChild from relations); CleanTask lib/tasks.ts:21.
- Add ONE self-relation prop "Parent Task" (type:"relation", config:{target_db: tasksId}) in ensureTasksApp/ensureProjectsProps; idempotent ensureProp backfill. Links in collection_relations (fromRow=child, toRow=parent). Do NOT touch nodes.parentId.
- Add a parentTask field on CleanTask mirroring blockedBy (load in lib/tasks.ts, write delete-all-then-reinsert like :1472).
- Rollup "3/5" + progress ring on parent: extend RollupFn/rollup() in lib/collection.ts with a percent/checked-of-subtasks fn + eval in lib/data.ts:351.
- UI: chevron expand + indent (cap ~3 levels) in TableView.tsx/ListView.tsx/TaskRow.tsx copying the projects indent; inline "+ Add sub-task"; independent completion (no cascade); subtasks stay visible in main list.
- Partial index (workspace_id, (props->>'parent_task')) following the systemKey precedent (db/migrations/20260704T223040_...systemkey_unique_guard.sql:18).
- Commit: feat(tasks): notion-style subtasks (self-relation + rollup + indented UI).
Verification (each phase)¶
Curl :3061 = 2xx/3xx after each commit; drive the actual flow on :3061 where possible; scoped tsc/eslint on touched files. Do not merge to main, do not deploy.