diff --git a/apps/web/.claude/settings.local.json b/apps/web/.claude/settings.local.json index 3cefc118..3ef2058e 100644 --- a/apps/web/.claude/settings.local.json +++ b/apps/web/.claude/settings.local.json @@ -1,153 +1,31 @@ { "permissions": { "allow": [ - "WebFetch(domain:github.com)", - "WebFetch(domain:react-resizable-panels.vercel.app)", - "Bash(gh run watch:*)", - "Bash(npm run build:*)", - "Bash(NODE_ENV=production npm run build:*)", - "Bash(npx @pandacss/dev:*)", - "Bash(npm run build-storybook:*)", - "Bash(ssh nas.home.network:*)", - "Bash(python3:*)", - "Bash(curl:*)", - "WebSearch", - "WebFetch(domain:community.home-assistant.io)", - "WebFetch(domain:raw.githubusercontent.com)", - "WebFetch(domain:www.google.com)", - "Bash(gcloud auth list:*)", - "Bash(gcloud auth login:*)", - "Bash(gcloud projects list:*)", - "Bash(gcloud projects create:*)", - "Bash(gcloud config set:*)", - "Bash(gcloud services enable:*)", - "Bash(gcloud alpha services api-keys create:*)", - "Bash(gcloud components install:*)", - "Bash(chmod:*)", - "Bash(./fetch-streetview.sh:*)", + "Bash(xargs:*)", + "Bash(npx @biomejs/biome lint:*)", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(git push:*)", "Bash(npm run type-check:*)", "Bash(npm run pre-commit:*)", - "Bash(git add:*)", - "Bash(npm info:*)", - "Bash(npx tsc:*)", - "Bash(git commit -m \"$(cat <<''EOF''\ndocs: add comprehensive merge conflict resolution guide\n\nAdd detailed guide for intelligent diff3-style merge conflict resolution:\n- Explanation of diff3 format (OURS, BASE, THEIRS)\n- 5 resolution patterns with examples (Compatible, Redundant, Conflicting, Delete vs Modify, Rename + References)\n- zdiff3 modern alternative\n- Semantic merge concepts\n- Best practices and anti-patterns\n- Debugging guide for failed resolutions\n- Quick reference checklist\n\nThis guide helps resolve merge conflicts intelligently by understanding the intent of both sides'' changes.\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude \nEOF\n)\")", - "Bash(git commit -m \"$(cat <<''EOF''\ndocs: add merge conflict resolution section to CLAUDE.md\n\nAdd quick reference section for merge conflict resolution:\n- Link to comprehensive guide (.claude/MERGE_CONFLICT_RESOLUTION.md)\n- Enable zdiff3 command\n- Quick resolution strategy summary\n- Reminder to test thoroughly after resolution\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude \nEOF\n)\")", - "Bash(git commit -m \"$(cat <<''EOF''\nchore: add auto-approvals for development commands\n\nAdd auto-approvals for common development workflow commands:\n- npm run type-check\n- npm run pre-commit \n- git add\n- npm info\n- npx tsc\n\nThese commands are safe to run automatically during development and code quality checks.\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude \nEOF\n)\")", - "Bash(git commit:*)", - "Bash(/tmp/worksheet-preview-new.tsx)", - "Bash(npm run format:*)", - "Bash(npm run lint:fix:*)", - "Bash(npm run lint)", - "mcp__sqlite__read_query", - "mcp__sqlite__describe_table", - "Bash(git push:*)", - "Bash(git pull:*)", - "Bash(git stash:*)", - "Bash(npx @biomejs/biome:*)", - "Bash(git rev-parse:*)", "Bash(gh run list:*)", - "Bash(npx biome:*)", - "WebFetch(domain:www.macintoshrepository.org)", - "WebFetch(domain:www.npmjs.com)", - "Bash(npm install:*)", - "Bash(pnpm add:*)", - "Bash(node -e:*)", - "Bash(npm search:*)", - "Bash(git revert:*)", - "Bash(pnpm remove:*)", - "Bash(gh run view:*)", - "Bash(pnpm install:*)", - "Bash(git checkout:*)", - "Bash(node server.js:*)", - "Bash(git fetch:*)", - "Bash(cat:*)", - "Bash(npm run test:run:*)", - "Bash(for:*)", - "Bash(do sleep 30)", - "Bash(echo:*)", - "Bash(done)", - "Bash(do sleep 120)", - "Bash(node --version)", - "Bash(docker run:*)", - "Bash(docker pull:*)", - "Bash(docker inspect:*)", - "Bash(docker system prune:*)", - "Bash(docker stop:*)", - "Bash(docker rm:*)", - "Bash(docker logs:*)", - "Bash(docker exec:*)", - "Bash(node --input-type=module -e:*)", - "Bash(npm test:*)", - "Bash(npx tsx:*)", - "Bash(tsc:*)", - "Bash(npx vitest:*)", "Bash(ssh:*)", - "Bash(break)", - "Bash(npm run lint:*)", - "WebFetch(domain:strudel.cc)", - "WebFetch(domain:club.tidalcycles.org)", - "Bash(git reset:*)", - "WebFetch(domain:abaci.one)", - "Bash(awk:*)", - "Bash(sort:*)", - "Bash(apps/web/src/arcade-games/know-your-world/components/MapRenderer.tsx )", - "Bash(apps/web/src/arcade-games/know-your-world/docs/MAPRENDERER_REFACTORING_PLAN.md )", - "Bash(apps/web/src/arcade-games/know-your-world/features/magnifier/index.ts )", - "Bash(apps/web/src/arcade-games/know-your-world/features/magnifier/useMagnifierStyle.ts )", - "Bash(apps/web/src/arcade-games/know-your-world/features/cursor/ )", - "Bash(apps/web/src/arcade-games/know-your-world/features/interaction/ )", - "Bash(apps/web/src/arcade-games/know-your-world/utils/heatStyles.ts)", - "Bash(ping:*)", - "WebFetch(domain:typst.app)", - "WebFetch(domain:finemotormath.com)", - "WebFetch(domain:learnabacusathome.com)", - "WebFetch(domain:totton.idirect.com)", - "Bash(npx drizzle-kit:*)", - "Bash(npm run db:migrate:*)", - "mcp__sqlite__list_tables", + "Bash(git fetch:*)", + "Bash(npx tsc:*)", + "Bash(npm run build:*)", + "Bash(curl:*)", + "Bash(pkill:*)", + "Bash(git rev-parse:*)", "Bash(sqlite3:*)", - "Bash(npx eslint:*)", - "Bash(src/hooks/useDeviceCapabilities.ts )", - "Bash(src/arcade-games/know-your-world/hooks/useDeviceCapabilities.ts )", - "Bash(src/components/practice/hooks/useDeviceDetection.ts )", - "Bash(src/arcade-games/memory-quiz/components/InputPhase.tsx )", - "Bash(src/app/api/curriculum/*/sessions/plans/route.ts)", - "Bash(src/app/api/curriculum/*/sessions/plans/*/route.ts)", - "Bash(src/components/practice/SessionSummary.tsx )", - "Bash(src/components/practice/ )", - "Bash(src/app/practice/ )", - "Bash(src/app/api/curriculum/ )", - "Bash(src/hooks/usePlayerCurriculum.ts )", - "Bash(src/hooks/useSessionPlan.ts )", - "Bash(src/lib/curriculum/ )", - "Bash(src/db/schema/player-curriculum.ts )", - "Bash(src/db/schema/player-skill-mastery.ts )", - "Bash(src/db/schema/practice-sessions.ts )", - "Bash(src/db/schema/session-plans.ts )", - "Bash(src/db/schema/index.ts )", - "Bash(src/types/tutorial.ts )", - "Bash(src/utils/problemGenerator.ts )", - "Bash(drizzle/ )", - "Bash(docs/DAILY_PRACTICE_SYSTEM.md )", - "Bash(../../README.md )", - "Bash(.claude/CLAUDE.md)", - "Bash(mcp__sqlite__describe_table:*)", - "Bash(ls:*)", - "Bash(mcp__sqlite__list_tables:*)", - "Bash(mcp__sqlite__read_query:*)", - "Bash(gh api:*)", - "Bash(xargs basename:*)", - "Bash(apps/web/src/app/practice/[studentId]/configure/ConfigureClient.tsx )", - "Bash(apps/web/src/components/practice/ManualSkillSelector.tsx )", - "Bash(apps/web/src/components/practice/VerticalProblem.tsx )", - "Bash(apps/web/src/components/practice/VerticalProblem.stories.tsx )", - "Bash(apps/web/src/types/tutorial.ts )", - "Bash(apps/web/src/utils/problemGenerator.ts )", - "Bash(apps/web/src/utils/__tests__/cascadingRegrouping.test.ts)" + "Bash(gh run view:*)", + "Bash(gh run rerun:*)", + "Bash(git checkout:*)" ], "deny": [], "ask": [] }, "enableAllProjectMcpServers": true, - "enabledMcpjsonServers": ["sqlite"] + "enabledMcpjsonServers": [ + "sqlite" + ] } diff --git a/apps/web/data/sqlite.db.bak b/apps/web/data/sqlite.db.bak new file mode 100644 index 00000000..24aadd93 Binary files /dev/null and b/apps/web/data/sqlite.db.bak differ diff --git a/apps/web/drizzle/meta/0032_snapshot.json b/apps/web/drizzle/meta/0032_snapshot.json index f03ef2c4..efad7c9c 100644 --- a/apps/web/drizzle/meta/0032_snapshot.json +++ b/apps/web/drizzle/meta/0032_snapshot.json @@ -116,13 +116,9 @@ "abacus_settings_user_id_users_id_fk": { "name": "abacus_settings_user_id_users_id_fk", "tableFrom": "abacus_settings", - "columnsFrom": [ - "user_id" - ], + "columnsFrom": ["user_id"], "tableTo": "users", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "no action", "onDelete": "cascade" } @@ -240,9 +236,7 @@ "indexes": { "arcade_rooms_code_unique": { "name": "arcade_rooms_code_unique", - "columns": [ - "code" - ], + "columns": ["code"], "isUnique": true } }, @@ -339,26 +333,18 @@ "arcade_sessions_room_id_arcade_rooms_id_fk": { "name": "arcade_sessions_room_id_arcade_rooms_id_fk", "tableFrom": "arcade_sessions", - "columnsFrom": [ - "room_id" - ], + "columnsFrom": ["room_id"], "tableTo": "arcade_rooms", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "no action", "onDelete": "cascade" }, "arcade_sessions_user_id_users_id_fk": { "name": "arcade_sessions_user_id_users_id_fk", "tableFrom": "arcade_sessions", - "columnsFrom": [ - "user_id" - ], + "columnsFrom": ["user_id"], "tableTo": "users", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "no action", "onDelete": "cascade" } @@ -424,9 +410,7 @@ "indexes": { "players_user_id_idx": { "name": "players_user_id_idx", - "columns": [ - "user_id" - ], + "columns": ["user_id"], "isUnique": false } }, @@ -434,13 +418,9 @@ "players_user_id_users_id_fk": { "name": "players_user_id_users_id_fk", "tableFrom": "players", - "columnsFrom": [ - "user_id" - ], + "columnsFrom": ["user_id"], "tableTo": "users", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "no action", "onDelete": "cascade" } @@ -514,9 +494,7 @@ "indexes": { "idx_room_members_user_id_unique": { "name": "idx_room_members_user_id_unique", - "columns": [ - "user_id" - ], + "columns": ["user_id"], "isUnique": true } }, @@ -524,13 +502,9 @@ "room_members_room_id_arcade_rooms_id_fk": { "name": "room_members_room_id_arcade_rooms_id_fk", "tableFrom": "room_members", - "columnsFrom": [ - "room_id" - ], + "columnsFrom": ["room_id"], "tableTo": "arcade_rooms", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "no action", "onDelete": "cascade" } @@ -605,13 +579,9 @@ "room_member_history_room_id_arcade_rooms_id_fk": { "name": "room_member_history_room_id_arcade_rooms_id_fk", "tableFrom": "room_member_history", - "columnsFrom": [ - "room_id" - ], + "columnsFrom": ["room_id"], "tableTo": "arcade_rooms", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "no action", "onDelete": "cascade" } @@ -713,10 +683,7 @@ "indexes": { "idx_room_invitations_user_room": { "name": "idx_room_invitations_user_room", - "columns": [ - "user_id", - "room_id" - ], + "columns": ["user_id", "room_id"], "isUnique": true } }, @@ -724,13 +691,9 @@ "room_invitations_room_id_arcade_rooms_id_fk": { "name": "room_invitations_room_id_arcade_rooms_id_fk", "tableFrom": "room_invitations", - "columnsFrom": [ - "room_id" - ], + "columnsFrom": ["room_id"], "tableTo": "arcade_rooms", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "no action", "onDelete": "cascade" } @@ -833,13 +796,9 @@ "room_reports_room_id_arcade_rooms_id_fk": { "name": "room_reports_room_id_arcade_rooms_id_fk", "tableFrom": "room_reports", - "columnsFrom": [ - "room_id" - ], + "columnsFrom": ["room_id"], "tableTo": "arcade_rooms", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "no action", "onDelete": "cascade" } @@ -918,10 +877,7 @@ "indexes": { "idx_room_bans_user_room": { "name": "idx_room_bans_user_room", - "columns": [ - "user_id", - "room_id" - ], + "columns": ["user_id", "room_id"], "isUnique": true } }, @@ -929,13 +885,9 @@ "room_bans_room_id_arcade_rooms_id_fk": { "name": "room_bans_room_id_arcade_rooms_id_fk", "tableFrom": "room_bans", - "columnsFrom": [ - "room_id" - ], + "columnsFrom": ["room_id"], "tableTo": "arcade_rooms", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "no action", "onDelete": "cascade" } @@ -998,13 +950,9 @@ "user_stats_user_id_users_id_fk": { "name": "user_stats_user_id_users_id_fk", "tableFrom": "user_stats", - "columnsFrom": [ - "user_id" - ], + "columnsFrom": ["user_id"], "tableTo": "users", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "no action", "onDelete": "cascade" } @@ -1062,16 +1010,12 @@ "indexes": { "users_guest_id_unique": { "name": "users_guest_id_unique", - "columns": [ - "guest_id" - ], + "columns": ["guest_id"], "isUnique": true }, "users_email_unique": { "name": "users_email_unique", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -1091,4 +1035,4 @@ "internal": { "indexes": {} } -} \ No newline at end of file +} diff --git a/apps/web/drizzle/meta/_journal.json b/apps/web/drizzle/meta/_journal.json index 24126acc..1d5eb6c6 100644 --- a/apps/web/drizzle/meta/_journal.json +++ b/apps/web/drizzle/meta/_journal.json @@ -234,4 +234,4 @@ "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/apps/web/src/app/practice/[studentId]/configure/ConfigureClient.tsx b/apps/web/src/app/practice/[studentId]/configure/ConfigureClient.tsx index 77168a99..dabd497d 100644 --- a/apps/web/src/app/practice/[studentId]/configure/ConfigureClient.tsx +++ b/apps/web/src/app/practice/[studentId]/configure/ConfigureClient.tsx @@ -24,7 +24,8 @@ const PURPOSE_WEIGHTS = { focus: DEFAULT_PLAN_CONFIG.focusWeight, reinforce: DEFAULT_PLAN_CONFIG.reinforceWeight, review: DEFAULT_PLAN_CONFIG.reviewWeight, - challenge: DEFAULT_PLAN_CONFIG.challengeWeight, + // Note: Challenge slots are calculated as remainder after focus/reinforce/review + // and use CHALLENGE_RATIO_BY_PART_TYPE in the actual session planner } interface ConfigureClientProps { diff --git a/apps/web/src/db/schema/session-plans.ts b/apps/web/src/db/schema/session-plans.ts index 466df9a3..31eadc3b 100644 --- a/apps/web/src/db/schema/session-plans.ts +++ b/apps/web/src/db/schema/session-plans.ts @@ -2,6 +2,15 @@ import { createId } from '@paralleldrive/cuid2' import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core' import type { SkillSet } from '@/types/tutorial' import { players } from './players' +import { + DEFAULT_SECONDS_PER_PROBLEM, + PART_TIME_WEIGHTS, + PURPOSE_COMPLEXITY_BOUNDS, + PURPOSE_WEIGHTS, + REVIEW_INTERVAL_DAYS, + SESSION_TIMEOUT_HOURS, + TERM_COUNT_RANGES, +} from '@/lib/curriculum/config' // ============================================================================ // Types for JSON fields @@ -451,98 +460,33 @@ export function calculateSessionHealth(plan: SessionPlan, elapsedTimeMs: number) } /** - * Default configuration for plan generation + * Default configuration for plan generation. + * + * All values are imported from @/lib/curriculum/config for centralized tuning. + * Edit those config files to change these defaults. */ export const DEFAULT_PLAN_CONFIG = { - /** Distribution weights for slot purposes (should sum to 1.0) */ - focusWeight: 0.6, - reinforceWeight: 0.2, - reviewWeight: 0.15, - challengeWeight: 0.05, + // Slot purpose distribution (from config/slot-distribution.ts) + // Note: Challenge slots use CHALLENGE_RATIO_BY_PART_TYPE instead of a fixed weight + focusWeight: PURPOSE_WEIGHTS.focus, + reinforceWeight: PURPOSE_WEIGHTS.reinforce, + reviewWeight: PURPOSE_WEIGHTS.review, - /** Distribution weights for session parts (should sum to 1.0) - * Part 1 (abacus): 50% - This is where new skills are built - * Part 2 (visualization): 30% - Mental math with visualization - * Part 3 (linear): 20% - Mental math in sentence format - */ - partTimeWeights: { - abacus: 0.5, - visualization: 0.3, - linear: 0.2, - }, + // Session part time distribution (from config/slot-distribution.ts) + partTimeWeights: PART_TIME_WEIGHTS, - /** Term count range for abacus part (how many numbers per problem) */ - abacusTermCount: { min: 3, max: 6 }, + // Term count ranges (from config/slot-distribution.ts) + abacusTermCount: TERM_COUNT_RANGES.abacus, + visualizationTermCount: TERM_COUNT_RANGES.visualization, + linearTermCount: TERM_COUNT_RANGES.linear, - /** Term count range for visualization part (defaults to 75% of abacus) */ - visualizationTermCount: null as { min: number; max: number } | null, + // Timing (from config/session-timing.ts) + defaultSecondsPerProblem: DEFAULT_SECONDS_PER_PROBLEM, + reviewIntervalDays: REVIEW_INTERVAL_DAYS, + sessionTimeoutHours: SESSION_TIMEOUT_HOURS, - /** Term count range for linear part (same as abacus by default) */ - linearTermCount: null as { min: number; max: number } | null, - - /** Default seconds per problem if no history */ - defaultSecondsPerProblem: 45, - - /** Spaced repetition intervals */ - reviewIntervalDays: { - mastered: 7, - practicing: 3, - }, - - /** Session timeout in hours - sessions older than this are auto-abandoned on next access */ - sessionTimeoutHours: 24, - - /** - * Complexity budget per term for each part type. - * null = no limit (unlimited) - * - * These are evaluated against student-aware costs: - * termCost = Σ(baseCost × masteryMultiplier) - * - * @deprecated Use purposeComplexityBounds instead for per-purpose control - */ - abacusComplexityBudget: null as number | null, - visualizationComplexityBudget: 3 as number | null, - linearComplexityBudget: null as number | null, - - /** - * Complexity bounds per purpose type, per part type. - * - * Each purpose can have different min/max complexity requirements. - * null = no constraint (falls back to part-type defaults above) - * - * Evaluated against student-aware costs: - * termCost = Σ(baseCost × masteryMultiplier) - * - * Example bounds: - * min: 1 = every term must use at least one five-complement - * max: 3 = no term can exceed cost 3 (cap for visualization) - */ - purposeComplexityBounds: { - focus: { - abacus: { min: null, max: null }, - visualization: { min: null, max: 3 }, - linear: { min: null, max: null }, - }, - reinforce: { - abacus: { min: null, max: null }, - visualization: { min: null, max: 3 }, - linear: { min: null, max: null }, - }, - review: { - abacus: { min: null, max: null }, - visualization: { min: null, max: 3 }, - linear: { min: null, max: null }, - }, - challenge: { - abacus: { min: 1, max: null }, - visualization: { min: 1, max: null }, - linear: { min: 1, max: null }, - }, - } as Record< - 'focus' | 'reinforce' | 'review' | 'challenge', - Record<'abacus' | 'visualization' | 'linear', { min: number | null; max: number | null }> - >, + // Per-purpose complexity bounds (from config/complexity-budgets.ts) + purposeComplexityBounds: PURPOSE_COMPLEXITY_BOUNDS, } export type PlanGenerationConfig = typeof DEFAULT_PLAN_CONFIG diff --git a/apps/web/src/lib/curriculum/config/fluency-thresholds.ts b/apps/web/src/lib/curriculum/config/fluency-thresholds.ts index e4897f0d..36e6bab8 100644 --- a/apps/web/src/lib/curriculum/config/fluency-thresholds.ts +++ b/apps/web/src/lib/curriculum/config/fluency-thresholds.ts @@ -5,6 +5,8 @@ * fluency in a skill and how fluency decays over time. */ +console.log('[fluency-thresholds.ts] MODULE LOADING...') + // ============================================================================= // Fluency Achievement Thresholds // ============================================================================= @@ -80,21 +82,23 @@ export const REINFORCEMENT_CONFIG = { /** * Mastery credit multipliers based on help level. - * Full credit only given for unassisted correct answers. + * Used when updating skill mastery after a correct answer. * * - 0 (no help): 1.0 = full credit - * - 1 (hint): 0.8 = 80% credit - * - 2 (decomposition): 0.5 = 50% credit - * - 3 (bead arrows): 0.3 = 30% credit + * - 1 (hint): 1.0 = full credit (hints don't reduce credit) + * - 2 (decomposition): 0.5 = half credit + * - 3 (bead arrows): 0.25 = quarter credit */ - masteryCreditByHelpLevel: { + creditMultipliers: { 0: 1.0, - 1: 0.8, + 1: 1.0, 2: 0.5, - 3: 0.3, - } as Record, + 3: 0.25, + } as Record<0 | 1 | 2 | 3, number>, } as const export type FluencyThresholds = typeof FLUENCY_THRESHOLDS export type FluencyRecency = typeof FLUENCY_RECENCY export type ReinforcementConfig = typeof REINFORCEMENT_CONFIG + +console.log('[fluency-thresholds.ts] MODULE LOADED - REINFORCEMENT_CONFIG:', JSON.stringify(REINFORCEMENT_CONFIG, null, 2)) diff --git a/apps/web/src/lib/curriculum/config/slot-distribution.ts b/apps/web/src/lib/curriculum/config/slot-distribution.ts index e783677f..4e63a3c3 100644 --- a/apps/web/src/lib/curriculum/config/slot-distribution.ts +++ b/apps/web/src/lib/curriculum/config/slot-distribution.ts @@ -29,8 +29,8 @@ export const PURPOSE_WEIGHTS = { reinforce: 0.2, /** Spaced repetition of mastered skills */ review: 0.15, - /** Legacy weight - challenge allocation now uses challengeRatioByPartType */ - challenge: 0.05, + // Note: Challenge slots are allocated using CHALLENGE_RATIO_BY_PART_TYPE (per part type), + // not a fixed weight. See session-planner.ts for the allocation logic. } as const /** diff --git a/apps/web/src/lib/curriculum/progress-manager.ts b/apps/web/src/lib/curriculum/progress-manager.ts index 64666a4f..6dbf6799 100644 --- a/apps/web/src/lib/curriculum/progress-manager.ts +++ b/apps/web/src/lib/curriculum/progress-manager.ts @@ -15,6 +15,14 @@ import { // Import directly from source to avoid circular dependency issues with re-exports import { REINFORCEMENT_CONFIG } from '@/lib/curriculum/config/fluency-thresholds' import type { NewPracticeSession, PracticeSession } from '@/db/schema/practice-sessions' + +// Debug: Log the imported config at module load time +console.log('[progress-manager.ts] MODULE LOAD - REINFORCEMENT_CONFIG debug:', { + exists: typeof REINFORCEMENT_CONFIG !== 'undefined', + type: typeof REINFORCEMENT_CONFIG, + keys: REINFORCEMENT_CONFIG ? Object.keys(REINFORCEMENT_CONFIG) : 'N/A', + fullObject: JSON.stringify(REINFORCEMENT_CONFIG, null, 2), +}) import type { HelpLevel } from '@/db/schema/session-plans' // ============================================================================ diff --git a/apps/web/src/lib/curriculum/session-planner.ts b/apps/web/src/lib/curriculum/session-planner.ts index db5165c1..b86c4e7a 100644 --- a/apps/web/src/lib/curriculum/session-planner.ts +++ b/apps/web/src/lib/curriculum/session-planner.ts @@ -549,9 +549,7 @@ export async function recordSlotResult( // Defensive check: ensure results array exists if (!plan.results || !Array.isArray(plan.results)) { - throw new Error( - `Plan ${planId} has invalid results: ${typeof plan.results} (expected array)` - ) + throw new Error(`Plan ${planId} has invalid results: ${typeof plan.results} (expected array)`) } console.log(`[recordSlotResult] Creating newResult for plan ${planId}`) @@ -593,7 +591,9 @@ export async function recordSlotResult( let updatedHealth try { updatedHealth = calculateSessionHealth({ ...plan, results: updatedResults }, elapsedMs) - console.log(`[recordSlotResult] calculateSessionHealth succeeded: ${JSON.stringify(updatedHealth)}`) + console.log( + `[recordSlotResult] calculateSessionHealth succeeded: ${JSON.stringify(updatedHealth)}` + ) } catch (healthError) { console.error(`[recordSlotResult] calculateSessionHealth FAILED:`, healthError) throw healthError