{"record":{"id":"c8a84a2f4fb42f14","repo":"pcottle/learnGitBranching","slug":"invalid-progress-data-for-level","errorCode":null,"errorMessage":"invalid progress data for level: {}","messagePattern":"invalid progress data for level: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/js/stores/LevelStore.js","lineNumber":79,"sourceCode":"}\n\nfunction _normalizeImportedSolvedMap(progress) {\n  if (!progress || typeof progress !== 'object' || Array.isArray(progress)) {\n    throw new Error('level progress must be a JSON object');\n  }\n\n  var normalized = {};\n  Object.keys(progress).forEach(function(levelID) {\n    var levelData = progress[levelID];\n\n    // Backwards compatibility with the old storage format.\n    if (levelData === true) {\n      normalized[levelID] = true;\n      return;\n    }\n\n    if (!levelData || typeof levelData !== 'object' || Array.isArray(levelData)) {\n      throw new Error('invalid progress data for level: ' + levelID);\n    }\n\n    normalized[levelID] = {\n      solved: levelData.solved === true,\n      best: levelData.best === true\n    };\n  });\n\n  return normalized;\n}\n\nfunction exportLevelProgress() {\n  return JSON.stringify(_cloneSolvedMap());\n}\n\nfunction importLevelProgress(progress) {\n  if (typeof progress === 'string') {\n    progress = JSON.parse(progress);","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/pcottle/learnGitBranching/blob/5b09d0ff9678cad164f5a7488a279a44f90d7256/src/js/stores/LevelStore.js#L61-L97","documentation":"The per-level check inside _normalizeImportedSolvedMap(): each value in the progress object must be the boolean true (legacy shorthand for 'solved') or an object with solved/best booleans. Anything else — null, arrays, strings, numbers — throws Error('invalid progress data for level: ' + levelID) identifying the offending key.","triggerScenarios":"Calling importLevelProgress() with a progress map containing an invalid entry, e.g. {\"intro-1\": \"yes\"}, {\"intro-1\": null}, or {\"intro-1\": [\"solved\"]}.","commonSituations":"Legacy saves where solved was stored as 1 or \"true\"; partially migrated export formats; typos when hand-editing save JSON.","solutions":["Fix the offending entry to true or {solved: true, best: true}","Booleans only: 1/\"true\" are not accepted — coerce before importing","Use the error's levelID to locate the bad key quickly in the JSON"],"exampleFix":"// before\n{\"intro-1\": \"true\"}\n// after\n{\"intro-1\": true}","handlingStrategy":"validation","validationCode":"function isValidLevelEntry(v) {\n  return v === true ||\n    (Boolean(v) && typeof v === 'object' && !Array.isArray(v) &&\n      typeof v.solved === 'boolean' && typeof v.best === 'boolean');\n}\nObject.keys(data).forEach(k => { if (!isValidLevelEntry(data[k])) throw new Error('bad level: ' + k); });","typeGuard":"function isLevelEntry(v) {\n  if (v === true) return true;\n  return v !== null && typeof v === 'object' && !Array.isArray(v) &&\n    typeof v.solved === 'boolean' && typeof v.best === 'boolean';\n}","tryCatchPattern":"try { importLevelProgress(data); } catch (e) { if (/invalid progress data/.test(e.message)) { highlightBadLevel(e.message.split(': ')[1]); } else throw e; }","preventionTips":["Store solved/best strictly as booleans, never 1/\"true\"","Sanitize legacy saves before import, coercing truthy values to true"],"tags":["level-store","import","per-level-validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"5b09d0ff9678cad164f5a7488a279a44f90d7256","analyzedAt":"2026-08-27T13:53:49.057Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}