Skip to content

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 creators table + 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 existing lib/invites.ts consent + 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_enabled opt-out. Reuse existing getWorkspaceProgress/ProgressBoard pattern -> 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, the gamification_enabled toggle.
  • 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.