@vue-skuilder/platform-ui 0.2.16 → 0.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/assets/{About-Kg4cHRA7.js → About-CwB0iumU.js} +2 -2
  2. package/dist/assets/{About-Kg4cHRA7.js.map → About-CwB0iumU.js.map} +1 -1
  3. package/dist/assets/{AdminDashboard-BhifTq30.js → AdminDashboard-yAigleGv.js} +2 -2
  4. package/dist/assets/{AdminDashboard-BhifTq30.js.map → AdminDashboard-yAigleGv.js.map} +1 -1
  5. package/dist/assets/{ClassroomCtrlPanel-DQqDWIxc.js → ClassroomCtrlPanel-V8jZLNQa.js} +2 -2
  6. package/dist/assets/{ClassroomCtrlPanel-DQqDWIxc.js.map → ClassroomCtrlPanel-V8jZLNQa.js.map} +1 -1
  7. package/dist/assets/{Classrooms-8e5Wk1o8.js → Classrooms-C54kNyZN.js} +2 -2
  8. package/dist/assets/{Classrooms-8e5Wk1o8.js.map → Classrooms-C54kNyZN.js.map} +1 -1
  9. package/dist/assets/{CourseRouter-Z_6VfTiW.js → CourseRouter-DKn6eNTp.js} +2 -2
  10. package/dist/assets/{CourseRouter-Z_6VfTiW.js.map → CourseRouter-DKn6eNTp.js.map} +1 -1
  11. package/dist/assets/{CourseWare-BTFRjgBR-BHFtLEh3.js → CourseWare-BTFRjgBR--nCN0cfo.js} +3 -3
  12. package/dist/assets/{CourseWare-BTFRjgBR-BHFtLEh3.js.map → CourseWare-BTFRjgBR--nCN0cfo.js.map} +1 -1
  13. package/dist/assets/{Courses-C6OIEqTq.js → Courses-BUrcih0S.js} +2 -2
  14. package/dist/assets/{Courses-C6OIEqTq.js.map → Courses-BUrcih0S.js.map} +1 -1
  15. package/dist/assets/{EloModeration-Dd53o_8r.js → EloModeration-DUY1aQKv.js} +2 -2
  16. package/dist/assets/{EloModeration-Dd53o_8r.js.map → EloModeration-DUY1aQKv.js.map} +1 -1
  17. package/dist/assets/{JoinCode-B-OjUSym.js → JoinCode-D6LF5gnz.js} +2 -2
  18. package/dist/assets/{JoinCode-B-OjUSym.js.map → JoinCode-D6LF5gnz.js.map} +1 -1
  19. package/dist/assets/{NewCourseDialog-C-cpZLL_.js → NewCourseDialog-S0gU_RA8.js} +2 -2
  20. package/dist/assets/{NewCourseDialog-C-cpZLL_.js.map → NewCourseDialog-S0gU_RA8.js.map} +1 -1
  21. package/dist/assets/{ReleaseNotes-B3FiZzqR.js → ReleaseNotes-CwmAOWai.js} +2 -2
  22. package/dist/assets/{ReleaseNotes-B3FiZzqR.js.map → ReleaseNotes-CwmAOWai.js.map} +1 -1
  23. package/dist/assets/{RequestPasswordReset-DoETVGCN.js → RequestPasswordReset-Bqb0x2tM.js} +2 -2
  24. package/dist/assets/{RequestPasswordReset-DoETVGCN.js.map → RequestPasswordReset-Bqb0x2tM.js.map} +1 -1
  25. package/dist/assets/{ResetPassword-DPT_Ca0b.js → ResetPassword-CxvaqhGo.js} +2 -2
  26. package/dist/assets/{ResetPassword-DPT_Ca0b.js.map → ResetPassword-CxvaqhGo.js.map} +1 -1
  27. package/dist/assets/{Study-BWyV7au5.js → Study-C1F1be1A.js} +2 -2
  28. package/dist/assets/{Study-BWyV7au5.js.map → Study-C1F1be1A.js.map} +1 -1
  29. package/dist/assets/{TagInformation-DpEi6wOX.js → TagInformation-vfUd2oB4.js} +2 -2
  30. package/dist/assets/{TagInformation-DpEi6wOX.js.map → TagInformation-vfUd2oB4.js.map} +1 -1
  31. package/dist/assets/{User-Co2jlwaX.js → User-cJs2HNhF.js} +2 -2
  32. package/dist/assets/{User-Co2jlwaX.js.map → User-cJs2HNhF.js.map} +1 -1
  33. package/dist/assets/{UserStats-D_klmQRZ.js → UserStats-BnrffdJH.js} +2 -2
  34. package/dist/assets/{UserStats-D_klmQRZ.js.map → UserStats-BnrffdJH.js.map} +1 -1
  35. package/dist/assets/{VerifyEmail-Di-_3y5p.js → VerifyEmail-C2lyt7GQ.js} +2 -2
  36. package/dist/assets/{VerifyEmail-Di-_3y5p.js.map → VerifyEmail-C2lyt7GQ.js.map} +1 -1
  37. package/dist/assets/{chess-E2uOAyQS-DN9Rkv-e.js → chess-E2uOAyQS-ZtsDyd69.js} +2 -2
  38. package/dist/assets/{chess-E2uOAyQS-DN9Rkv-e.js.map → chess-E2uOAyQS-ZtsDyd69.js.map} +1 -1
  39. package/dist/assets/chess-Lfm7Fsvs-WjWoJnx2.js +1 -0
  40. package/dist/assets/common-ui.es-BytS0_OW.js +1 -0
  41. package/dist/assets/{common-ui.es-C_nNPpCR.js → common-ui.es-CCnh3LEQ.js} +8 -8
  42. package/dist/assets/{common-ui.es-C_nNPpCR.js.map → common-ui.es-CCnh3LEQ.js.map} +1 -1
  43. package/dist/assets/{dist-DFv5eKpx.js → dist-8LnqWDZ6.js} +14 -14
  44. package/dist/assets/dist-8LnqWDZ6.js.map +1 -0
  45. package/dist/assets/dist-DZaVuA4i.js +1 -0
  46. package/dist/assets/dist-Dp_EURhL.js +1 -0
  47. package/dist/assets/{dist-D2x1mJ2O.js → dist-cLNiV6PS.js} +3 -3
  48. package/dist/assets/{dist-D2x1mJ2O.js.map → dist-cLNiV6PS.js.map} +1 -1
  49. package/dist/assets/{edit-ui.es-Cbuvpf4V.js → edit-ui.es-0NFrNYiI.js} +6 -6
  50. package/dist/assets/{edit-ui.es-Cbuvpf4V.js.map → edit-ui.es-0NFrNYiI.js.map} +1 -1
  51. package/dist/assets/edit-ui.es-BcbaMH6S.js +1 -0
  52. package/dist/assets/{french-Dk7YG8Td-C3NlMZjj.js → french-Dk7YG8Td-RtXtjW9f.js} +2 -2
  53. package/dist/assets/{french-Dk7YG8Td-C3NlMZjj.js.map → french-Dk7YG8Td-RtXtjW9f.js.map} +1 -1
  54. package/dist/assets/french-evUMlbbq-xDYwI1LY.js +1 -0
  55. package/dist/assets/{index-C4T6j65e.js → index-NB-mz58s.js} +4 -4
  56. package/dist/assets/{index-C4T6j65e.js.map → index-NB-mz58s.js.map} +1 -1
  57. package/dist/assets/{math-B4HbgYf6-CsVPXXUh.js → math-B4HbgYf6-Qyc78yuV.js} +2 -2
  58. package/dist/assets/{math-B4HbgYf6-CsVPXXUh.js.map → math-B4HbgYf6-Qyc78yuV.js.map} +1 -1
  59. package/dist/assets/math-DYni7rRl-CAlZX6mR.js +1 -0
  60. package/dist/assets/piano-BN5Btq91-Badmj3zF.js +1 -0
  61. package/dist/assets/{piano-DF1g6yaX-BTSBCU2K.js → piano-DF1g6yaX-DOhXd3vw.js} +2 -2
  62. package/dist/assets/{piano-DF1g6yaX-BTSBCU2K.js.map → piano-DF1g6yaX-DOhXd3vw.js.map} +1 -1
  63. package/dist/assets/{pitch-CgGJFkZ1-BHlLBYH_.js → pitch-CgGJFkZ1-D_dcJ7lI.js} +2 -2
  64. package/dist/assets/{pitch-CgGJFkZ1-BHlLBYH_.js.map → pitch-CgGJFkZ1-D_dcJ7lI.js.map} +1 -1
  65. package/dist/assets/pitch-Dn0iNqiS-yrrImu-e.js +1 -0
  66. package/dist/assets/{server-TgFoAznT.js → server-BTab-t6C.js} +2 -2
  67. package/dist/assets/{server-TgFoAznT.js.map → server-BTab-t6C.js.map} +1 -1
  68. package/dist/assets/{sightsing-BFQ7HRig-C5zm4HmL.js → sightsing-BFQ7HRig-ItgPlPxq.js} +2 -2
  69. package/dist/assets/{sightsing-BFQ7HRig-C5zm4HmL.js.map → sightsing-BFQ7HRig-ItgPlPxq.js.map} +1 -1
  70. package/dist/assets/sightsing-CJX3k0Fd-Dpi8dUqZ.js +1 -0
  71. package/dist/assets/{typing-BevtfWlp-GDFoSOWw.js → typing-BevtfWlp-YCEmifIZ.js} +2 -2
  72. package/dist/assets/{typing-BevtfWlp-GDFoSOWw.js.map → typing-BevtfWlp-YCEmifIZ.js.map} +1 -1
  73. package/dist/assets/typing-k-ojjO7G-Ds2kgQDG.js +1 -0
  74. package/dist/assets/{word-work-BOnRlZgd-DRfYkDSG.js → word-work-BOnRlZgd-CUlHwW3V.js} +2 -2
  75. package/dist/assets/{word-work-BOnRlZgd-DRfYkDSG.js.map → word-work-BOnRlZgd-CUlHwW3V.js.map} +1 -1
  76. package/dist/assets/word-work-Dd6tIKrt-BH_6upKx.js +1 -0
  77. package/dist/index.html +3 -3
  78. package/dist/sw.js +1 -1
  79. package/dist/sw.js.map +1 -1
  80. package/package.json +7 -7
  81. package/dist/assets/chess-Lfm7Fsvs-RUEctCqI.js +0 -1
  82. package/dist/assets/common-ui.es-BET7U3Ss.js +0 -1
  83. package/dist/assets/dist-B3ubsUEd.js +0 -1
  84. package/dist/assets/dist-C41zzhNl.js +0 -1
  85. package/dist/assets/dist-DFv5eKpx.js.map +0 -1
  86. package/dist/assets/edit-ui.es-BNdU4Xoa.js +0 -1
  87. package/dist/assets/french-evUMlbbq-Dg8jfbcg.js +0 -1
  88. package/dist/assets/math-DYni7rRl-WwVJ2oGh.js +0 -1
  89. package/dist/assets/piano-BN5Btq91-BzjcNdzf.js +0 -1
  90. package/dist/assets/pitch-Dn0iNqiS-DkAyhSvi.js +0 -1
  91. package/dist/assets/sightsing-CJX3k0Fd-CzRkhx2h.js +0 -1
  92. package/dist/assets/typing-k-ojjO7G-DS1lIVgX.js +0 -1
  93. package/dist/assets/word-work-Dd6tIKrt-C8tadgOD.js +0 -1
@@ -246,13 +246,13 @@ Example:
246
246
  window.skuilder.pipeline.showLastRun()
247
247
  window.skuilder.pipeline.showRun(1)
248
248
  await window.skuilder.pipeline.diagnoseCardSpace()
249
- `)}},mountPipelineDebugger()}}),SrsDebugger_exports={};__export(SrsDebugger_exports,{captureSrsBacklog:()=>captureSrsBacklog,clearSrsBacklogDebug:()=>clearSrsBacklogDebug,getSrsBacklogDebug:()=>getSrsBacklogDebug});function captureSrsBacklog(e){snapshots.set(e.courseId,e)}function getSrsBacklogDebug(){return[...snapshots.values()].sort((e,t)=>t.timestamp-e.timestamp)}function clearSrsBacklogDebug(){snapshots.clear()}var snapshots,init_SrsDebugger=__esm({"src/core/navigators/SrsDebugger.ts"(){snapshots=new Map}}),CompositeGenerator_exports={};__export(CompositeGenerator_exports,{AggregationMode:()=>AggregationMode,default:()=>CompositeGenerator});function mergeHints(e){let t=e.filter(e=>e!==void 0);if(t.length===0)return;let n={},r={};for(let e of t)for(let[t,n]of Object.entries(e.boostTags??{}))r[t]=(r[t]??1)*n;Object.keys(r).length>0&&(n.boostTags=r);let a={};for(let e of t)for(let[t,n]of Object.entries(e.boostCards??{}))a[t]=(a[t]??1)*n;Object.keys(a).length>0&&(n.boostCards=a);let concatUnique=e=>{let r=t.flatMap(t=>t[e]??[]);r.length>0&&(n[e]=[...new Set(r)])};concatUnique(`requireTags`),concatUnique(`requireCards`),concatUnique(`excludeTags`),concatUnique(`excludeCards`);let o=t.map(e=>e._label).filter(Boolean);return o.length>0&&(n._label=o.join(`; `)),Object.keys(n).length>0?n:void 0}var AggregationMode,DEFAULT_AGGREGATION_MODE,FREQUENCY_BOOST_FACTOR,CompositeGenerator,init_CompositeGenerator=__esm({"src/core/navigators/generators/CompositeGenerator.ts"(){init_navigators(),init_logger(),AggregationMode=(e=>(e.MAX=`max`,e.AVERAGE=`average`,e.FREQUENCY_BOOST=`frequencyBoost`,e))(AggregationMode||{}),DEFAULT_AGGREGATION_MODE=`frequencyBoost`,FREQUENCY_BOOST_FACTOR=.1,CompositeGenerator=class _CompositeGenerator extends ContentNavigator{constructor(e,t=DEFAULT_AGGREGATION_MODE){if(super(),_defineProperty(this,`name`,`Composite Generator`),_defineProperty(this,`generators`,void 0),_defineProperty(this,`aggregationMode`,void 0),this.generators=e,this.aggregationMode=t,e.length===0)throw Error(`CompositeGenerator requires at least one generator`);logger.debug(`[CompositeGenerator] Created with ${e.length} generators, mode: ${t}`)}static async fromStrategies(e,t,n,r=DEFAULT_AGGREGATION_MODE){return new _CompositeGenerator(await Promise.all(n.map(n=>ContentNavigator.create(e,t,n))),r)}async getWeightedCards(e,t){if(!t)throw Error(`CompositeGenerator.getWeightedCards requires a GeneratorContext. It should be called via Pipeline, not directly.`);let n=await Promise.all(this.generators.map(n=>n.getWeightedCards(e,t))),r=[];n.forEach((e,t)=>{let n=e.cards,a=this.generators[t].name||`Generator ${t}`,o=n.filter(e=>e.provenance[0]?.reason?.includes(`new card`)),s=n.filter(e=>e.provenance[0]?.reason?.includes(`review`));if(n.length>0){let e=Math.max(...n.map(e=>e.score)).toFixed(2),t=[];o.length>0&&t.push(`${o.length} new`),s.length>0&&t.push(`${s.length} reviews`);let c=t.length>0?t.join(`, `):`${n.length} cards`;r.push(`${a}: ${c} (top: ${e})`)}else r.push(`${a}: 0 cards`)}),logger.info(`[Composite] Generator breakdown: ${r.join(` | `)}`);let a=new Map;n.forEach((e,n)=>{let r=e.cards,o=this.generators[n],s=o.learnable?.weight??1,c;if(o.learnable&&!o.staticWeight&&t.orchestration){let e=o.strategyId;e&&(s=t.orchestration.getEffectiveWeight(e,o.learnable),c=t.orchestration.getDeviation(e))}for(let e of r){e.provenance.length>0&&(e.provenance[0].effectiveWeight=s,e.provenance[0].deviation=c);let t=a.get(e.cardId)||[];t.push({card:e,weight:s}),a.set(e.cardId,t)}});let o=[];for(let[,e]of a){let t=e.map(e=>e.card),n=this.aggregateScores(e),r=Math.max(0,n),a=t.flatMap(e=>e.provenance),s=t[0].score,c=r>s?`boosted`:r<s?`penalized`:`passed`,l=this.buildAggregationReason(e,r);o.push({...t[0],score:r,provenance:[...a,{strategy:`composite`,strategyName:`Composite Generator`,strategyId:`COMPOSITE_GENERATOR`,action:c,score:r,reason:l}]})}return{cards:o.sort((e,t)=>t.score-e.score).slice(0,e),hints:mergeHints(n.map(e=>e.hints))}}buildAggregationReason(e,t){let n=e.map(e=>e.card),r=n.length,a=n.map(e=>e.score.toFixed(2)).join(`, `);if(r===1){let n=Math.abs(e[0].weight-1)>.001?` (w=${e[0].weight.toFixed(2)})`:``;return`Single generator, score ${t.toFixed(2)}${n}`}let o=n.map(e=>e.provenance[0]?.strategy||`unknown`).join(`, `);switch(this.aggregationMode){case`max`:return`Max of ${r} generators (${o}): scores [${a}] \u2192 ${t.toFixed(2)}`;case`average`:return`Weighted Avg of ${r} generators (${o}): scores [${a}] \u2192 ${t.toFixed(2)}`;case`frequencyBoost`:{let n=e.reduce((e,t)=>e+t.weight,0),a=e.reduce((e,t)=>e+t.card.score*t.weight,0),s=n>0?a/n:0,c=1+FREQUENCY_BOOST_FACTOR*(r-1);return`Frequency boost from ${r} generators (${o}): w-avg ${s.toFixed(2)} \xD7 ${c.toFixed(2)} \u2192 ${t.toFixed(2)}`}default:return`Aggregated from ${r} generators: ${t.toFixed(2)}`}}aggregateScores(e){let t=e.map(e=>e.card.score);switch(this.aggregationMode){case`max`:return Math.max(...t);case`average`:{let t=e.reduce((e,t)=>e+t.weight,0);return t===0?0:e.reduce((e,t)=>e+t.card.score*t.weight,0)/t}case`frequencyBoost`:{let t=e.reduce((e,t)=>e+t.weight,0),n=e.reduce((e,t)=>e+t.card.score*t.weight,0);return(t>0?n/t:0)*(1+FREQUENCY_BOOST_FACTOR*(e.length-1))}default:return t[0]}}}}}),elo_exports={};__export(elo_exports,{default:()=>ELONavigator});var ELO_RELEVANCE_SIGMA,ELONavigator,init_elo=__esm({"src/core/navigators/generators/elo.ts"(){init_navigators(),init_logger(),ELO_RELEVANCE_SIGMA=300,ELONavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`name`,void 0),this.name=n?.name||`ELO`}async getWeightedCards(e,t){let n;n=t?.userElo===void 0?toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo).global.score:t.userElo;let r=await this.user.getActiveCards(),a=(await this.course.getCardsCenteredAtELO({limit:e,elo:`user`},e=>!r.some(t=>e.cardID===t.cardID))).map(e=>({...e,status:`new`})).map(e=>{let t=e.elo??1e3,r=Math.abs(t-n),a=Math.exp(-((r/ELO_RELEVANCE_SIGMA)**2)),o=a*(.5+.5*Math.random());return{cardId:e.cardID,courseId:e.courseID,score:o,provenance:[{strategy:`elo`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-ELO-default`,action:`generated`,score:o,reason:`ELO distance ${Math.round(r)} (card: ${Math.round(t)}, user: ${Math.round(n)}), relevance ${a.toFixed(3)}, key ${o.toFixed(3)}`}]}});a.sort((e,t)=>t.score-e.score);let o=a.slice(0,e);if(o.length>0){let e=o.slice(0,3).map(e=>e.score.toFixed(2)).join(`, `);logger.info(`[ELO] Course ${this.course.getCourseID()}: ${o.length} new cards (top scores: ${e})`)}else logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);return{cards:o}}}}}),generators_exports={},init_generators=__esm({"src/core/navigators/generators/index.ts"(){}}),prescribed_exports={};__export(prescribed_exports,{default:()=>PrescribedCardsGenerator});function dedupe(e){return[...new Set(e)]}function isoNow(){return new Date().toISOString()}function clamp(e,t,n){return Math.max(t,Math.min(n,e))}function matchesTagPattern(e,t){if(t===`*`)return!0;let n=t.replace(/[.+^${}()|[\]\\]/g,`\\$&`).replace(/\*/g,`.*`);return RegExp(`^${n}$`).test(e)}function extractWordStem(e){for(let t of[`c-ml-`,`c-ws-`,`c-spelling-`])if(e.startsWith(t)){let n=e.slice(t.length),r=n.lastIndexOf(`-`);return r>0?n.slice(0,r):n}return e}function shuffleInPlace(e){for(let t=e.length-1;t>0;t--){let n=Math.floor(Math.random()*(t+1));[e[t],e[n]]=[e[n],e[t]]}}function pickTopByScore(e,t){return[...e].sort((e,t)=>t.score-e.score||e.cardId.localeCompare(t.cardId)).slice(0,t)}var DEFAULT_FRESHNESS_WINDOW,DEFAULT_MAX_DIRECT_PER_RUN,DEFAULT_MAX_SUPPORT_PER_RUN,DEFAULT_HIERARCHY_DEPTH,DEFAULT_MIN_COUNT,DEFAULT_PRACTICE_MIN_COUNT,DEFAULT_MAX_PRACTICE_PER_RUN,BASE_TARGET_SCORE,BASE_SUPPORT_SCORE,DISCOVERED_SUPPORT_SCORE,BASE_PRACTICE_SCORE,PRACTICE_BASE_MULT,MAX_PRACTICE_MULTIPLIER,PRACTICE_STALENESS_BUMP_PER_DAY,MAX_TARGET_MULTIPLIER,MAX_SUPPORT_MULTIPLIER,PRESCRIBED_DEBUG_VERSION,PrescribedCardsGenerator,init_prescribed=__esm({"src/core/navigators/generators/prescribed.ts"(){init_navigators(),init_logger(),DEFAULT_FRESHNESS_WINDOW=3,DEFAULT_MAX_DIRECT_PER_RUN=3,DEFAULT_MAX_SUPPORT_PER_RUN=3,DEFAULT_HIERARCHY_DEPTH=2,DEFAULT_MIN_COUNT=3,DEFAULT_PRACTICE_MIN_COUNT=3,DEFAULT_MAX_PRACTICE_PER_RUN=4,BASE_TARGET_SCORE=1,BASE_SUPPORT_SCORE=.8,DISCOVERED_SUPPORT_SCORE=12,BASE_PRACTICE_SCORE=1,PRACTICE_BASE_MULT=2,MAX_PRACTICE_MULTIPLIER=4,PRACTICE_STALENESS_BUMP_PER_DAY=.5,MAX_TARGET_MULTIPLIER=8,MAX_SUPPORT_MULTIPLIER=4,PRESCRIBED_DEBUG_VERSION=`testversion-prescribed-v3`,PrescribedCardsGenerator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`name`,void 0),_defineProperty(this,`config`,void 0),this.name=n.name||`Prescribed Cards`,this.config=this.parseConfig(n.serializedData),logger.debug(`[Prescribed] Initialized with ${this.config.groups.length} groups and ${this.config.groups.reduce((e,t)=>e+t.targetCardIds.length,0)} targets`)}get strategyKey(){return`PrescribedProgress`}async getWeightedCards(e,t){if(this.config.groups.length===0||e<=0)return{cards:[]};let n=this.course.getCourseID(),r=await this.user.getActiveCards(),a=new Set(r.map(e=>e.cardID)),o=await this.user.getSeenCards(n).catch(()=>[]),s=new Set(o),c=await this.getStrategyState()??{updatedAt:isoNow(),groups:{}},l=await this.loadHierarchyConfigs(),u=await this.user.getCourseRegDoc(n).catch(()=>null),d=typeof u?.elo==`number`?u.elo:u?.elo?.global?.score??t?.userElo??1e3,p=typeof u?.elo==`number`?{}:u?.elo?.tags??{},m=dedupe(this.config.groups.flatMap(e=>e.targetCardIds)),g=dedupe(this.config.groups.flatMap(e=>e.supportCardIds??[])),_=dedupe([...m,...g]),v=_.length>0?await this.course.getAppliedTagsBatch(_):new Map,y=await this.course.getCourseTagStubs().catch(()=>({rows:[],offset:0,total_rows:0})),b=new Map;for(let e of y.rows??[]){let t=e.doc;t?.name&&Array.isArray(t.taggedCards)&&b.set(t.name,[...t.taggedCards])}let x={updatedAt:isoNow(),groups:{}},S=[],C=new Set,w=[],T=c.practiceDebt??{},E={};for(let e of this.config.groups){let t=this.buildGroupRuntimeState({group:e,priorState:c.groups[e.id],activeIds:a,seenIds:s,tagsByCard:v,cardsByTag:b,hierarchyConfigs:l,userTagElo:p,userGlobalElo:d});w.push(t),logger.info(`[Prescribed] Group '${e.id}': ${e.targetCardIds.length} targets total, ${t.encounteredTargets.size} encountered, ${t.pendingTargets.length} pending (${t.surfaceableTargets.length} surfaceable, ${t.blockedTargets.length} blocked), ${t.supportCandidates.length} authored support candidates, ${t.discoveredSupportCandidates.length} discovered support candidates, pressure=${t.pressureMultiplier.toFixed(2)}`),t.blockedTargets.length>0&&(logger.info(`[Prescribed] Group '${e.id}' blocked targets: ${t.blockedTargets.join(`, `)}`),logger.info(`[Prescribed] Group '${e.id}' support tags needed: ${t.supportTags.join(`, `)||`(none)`}`),logger.info(`[Prescribed] Group '${e.id}' escalation mode: `+(t.supportCandidates.length>0?`direct-support`:t.discoveredSupportCandidates.length>0?`inserted-support-candidates`:`boost-only`)),t.discoveredSupportCandidates.length>0&&logger.info(`[Prescribed] Group '${e.id}' discovered support candidates: ${t.discoveredSupportCandidates.join(`, `)}`)),x.groups[e.id]=this.buildNextGroupState(t,c.groups[e.id]);let r=this.buildDirectTargetCards(t,n,C),o=this.buildSupportCards(t,n,C),u=this.buildDiscoveredSupportCards(t,n,C),m=this.buildPracticeCards({group:e,courseId:n,emittedIds:C,cardsByTag:b,hierarchyConfigs:l,userTagElo:p,userGlobalElo:d,activeIds:a,seenIds:s,priorPracticeDebt:T,nextPracticeDebt:E});S.push(...r,...o,...u,...m)}x.practiceDebt=E;let D=this.buildSupportHintSummary(w),O=Object.keys(D.boostTags).length>0?{boostTags:D.boostTags,_label:`prescribed-support (${D.supportTags.length} tags; blocked=${D.blockedTargetIds.length}; testversion=${PRESCRIBED_DEBUG_VERSION})`}:void 0;if(O){let e=Object.entries(O.boostTags??{});logger.info(`[Prescribed] Emitting ${e.length} boost hint(s): `+e.map(([e,t])=>`${e}\xD7${t.toFixed(1)}`).join(`, `))}else logger.info(`[Prescribed] No hints to emit (no blocked targets or no support tags)`);if(S.length===0)return logger.info(`[Prescribed] 0 cards emitted (all targets blocked, authored/discovered support candidates exhausted)`+(O?` — boost hints emitted but may not survive filters`:``)),await this.putStrategyState(x).catch(e=>{logger.debug(`[Prescribed] Failed to persist empty-state update: ${e}`)}),O?{cards:[],hints:O}:{cards:[]};let k=pickTopByScore(S,e),A=new Map;for(let e of k){let t=e.provenance[0];if(t?.reason.includes(`mode=practice`))continue;let n=t?.reason.match(/group=([^;]+)/)?.[1],r=t?.reason.includes(`mode=support`)?`supportIds`:`targetIds`;n&&(A.has(n)||A.set(n,{targetIds:[],supportIds:[]}),A.get(n)[r].push(e.cardId))}for(let e of this.config.groups){let t=x.groups[e.id],n=A.get(e.id);n&&(n.targetIds.length>0||n.supportIds.length>0)&&(t.lastSurfacedAt=isoNow(),t.sessionsSinceSurfaced=0,n.supportIds.length>0&&(t.lastSupportAt=isoNow()))}return await this.putStrategyState(x).catch(e=>{logger.debug(`[Prescribed] Failed to persist prescribed progress: ${e}`)}),logger.info(`[Prescribed] Emitting ${k.length} cards (${k.filter(e=>e.provenance[0]?.reason.includes(`mode=target`)).length} target, ${k.filter(e=>e.provenance[0]?.reason.includes(`mode=support`)).length} support, ${k.filter(e=>e.provenance[0]?.reason.includes(`mode=discovered-support`)).length} discovered support)`),O?{cards:k,hints:O}:{cards:k}}buildSupportHintSummary(e){let t={},n=new Set,r=new Set;for(let a of e)if(!(a.blockedTargets.length===0||a.supportTags.length===0)){a.blockedTargets.forEach(e=>n.add(e));for(let e of a.supportTags)r.add(e),t[e]=(t[e]??1)*a.supportMultiplier}return{boostTags:t,blockedTargetIds:[...n].sort(),supportTags:[...r].sort()}}parseConfig(e){try{let t=JSON.parse(e);return{groups:(Array.isArray(t.groups)?t.groups:[]).map((e,t)=>({id:typeof e.id==`string`&&e.id.trim().length>0?e.id:`group-${t+1}`,targetCardIds:dedupe(Array.isArray(e.targetCardIds)?e.targetCardIds.filter(e=>typeof e==`string`):[]),supportCardIds:dedupe(Array.isArray(e.supportCardIds)?e.supportCardIds.filter(e=>typeof e==`string`):[]),supportTagPatterns:dedupe(Array.isArray(e.supportTagPatterns)?e.supportTagPatterns.filter(e=>typeof e==`string`):[]),freshnessWindowSessions:typeof e.freshnessWindowSessions==`number`?e.freshnessWindowSessions:DEFAULT_FRESHNESS_WINDOW,maxDirectTargetsPerRun:typeof e.maxDirectTargetsPerRun==`number`?e.maxDirectTargetsPerRun:DEFAULT_MAX_DIRECT_PER_RUN,maxSupportCardsPerRun:typeof e.maxSupportCardsPerRun==`number`?e.maxSupportCardsPerRun:DEFAULT_MAX_SUPPORT_PER_RUN,hierarchyWalk:{enabled:e.hierarchyWalk?.enabled!==!1,maxDepth:typeof e.hierarchyWalk?.maxDepth==`number`?e.hierarchyWalk.maxDepth:DEFAULT_HIERARCHY_DEPTH},retireOnEncounter:e.retireOnEncounter!==!1,practiceTagPatterns:dedupe(Array.isArray(e.practiceTagPatterns)?e.practiceTagPatterns.filter(e=>typeof e==`string`):[]),practiceMinCount:typeof e.practiceMinCount==`number`?e.practiceMinCount:DEFAULT_PRACTICE_MIN_COUNT,maxPracticeCardsPerRun:typeof e.maxPracticeCardsPerRun==`number`?e.maxPracticeCardsPerRun:DEFAULT_MAX_PRACTICE_PER_RUN})).filter(e=>e.targetCardIds.length>0)}}catch{return{groups:[]}}}async loadHierarchyConfigs(){try{return(await this.course.getAllNavigationStrategies()).filter(e=>e.implementingClass===`hierarchyDefinition`).map(e=>{try{return{prerequisites:JSON.parse(e.serializedData).prerequisites||{}}}catch{return{prerequisites:{}}}})}catch(e){return logger.debug(`[Prescribed] Failed to load hierarchy configs: ${e}`),[]}}buildGroupRuntimeState(e){let{group:t,priorState:n,activeIds:r,seenIds:a,tagsByCard:o,cardsByTag:s,hierarchyConfigs:c,userTagElo:l,userGlobalElo:u}=e,d=new Set;for(let e of t.targetCardIds)(r.has(e)||a.has(e))&&d.add(e);if(n?.encounteredCardIds?.length)for(let e of n.encounteredCardIds)d.add(e);let p=t.targetCardIds.filter(e=>!d.has(e)),m=new Map;for(let e of p)m.set(e,o.get(e)??[]);let g=[],_=[],v=new Set;for(let e of p){let n=m.get(e)??[],r=this.resolveBlockedSupportTags(n,c,l,u,t.hierarchyWalk?.enabled!==!1,t.hierarchyWalk?.maxDepth??DEFAULT_HIERARCHY_DEPTH),a=n.filter(e=>e.startsWith(`gpc:intro:`)),o=new Set(n.filter(e=>e.startsWith(`gpc:expose:`)));for(let e of a){let t=e.slice(10);t&&o.add(`gpc:expose:${t}`)}let s=[...o].filter(e=>{let t=l[e];return!t||t.count<DEFAULT_MIN_COUNT});s.length>0&&s.forEach(e=>v.add(e)),r.blocked||s.length>0?(g.push(e),r.supportTags.forEach(e=>v.add(e))):_.push(e)}let y=dedupe([...t.supportCardIds??[],...this.findSupportCardsByTags(t,o,[...v])]).filter(e=>!r.has(e)&&!a.has(e)),b=g.length>0&&v.size>0&&y.length===0?this.findDiscoveredSupportCards({supportTags:[...v],cardsByTag:s,activeIds:r,seenIds:a,excludedIds:new Set([...t.targetCardIds,...t.supportCardIds??[]]),limit:t.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN}):[];g.length>0&&v.size>0&&b.length===0&&logger.info(`[Prescribed] Group '${t.id}' discovered 0 broader support candidates (blocked=${g.length}; authoredSupport=${y.length})`);let x=n?.sessionsSinceSurfaced??0,S=t.freshnessWindowSessions??DEFAULT_FRESHNESS_WINDOW,C=Math.max(0,x-S),w=p.length===0?1:clamp(1+C*.75+Math.min(2,p.length*.1),1,MAX_TARGET_MULTIPLIER),T=g.length===0?1:clamp(1+C*.5+Math.min(1.5,g.length*.15),1,MAX_SUPPORT_MULTIPLIER);return{group:t,encounteredTargets:d,pendingTargets:p,blockedTargets:g,surfaceableTargets:_,targetTags:m,supportCandidates:y,discoveredSupportCandidates:b,supportTags:[...v],pressureMultiplier:w,supportMultiplier:T,debugVersion:PRESCRIBED_DEBUG_VERSION}}buildNextGroupState(e,t){let n=t?.sessionsSinceSurfaced??0,r=!1;return{encounteredCardIds:[...e.encounteredTargets].sort(),pendingTargetIds:[...e.pendingTargets].sort(),lastSurfacedAt:t?.lastSurfacedAt??null,sessionsSinceSurfaced:n+1,lastSupportAt:t?.lastSupportAt??null,blockedTargetIds:[...e.blockedTargets].sort(),lastResolvedSupportTags:[...e.supportTags].sort()}}buildDirectTargetCards(e,t,n){let r=e.group.maxDirectTargetsPerRun??DEFAULT_MAX_DIRECT_PER_RUN,a=e.surfaceableTargets.filter(e=>!n.has(e)).slice(0,r),o=[];for(let r of a)n.add(r),o.push({cardId:r,courseId:t,score:BASE_TARGET_SCORE*e.pressureMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:BASE_TARGET_SCORE*e.pressureMultiplier,reason:`mode=target;group=${e.group.id};pending=${e.pendingTargets.length};surfaceable=${e.surfaceableTargets.length};blocked=${e.blockedTargets.length};blockedTargets=${e.blockedTargets.join(`|`)||`none`};supportTags=${e.supportTags.join(`|`)||`none`};multiplier=${e.pressureMultiplier.toFixed(2)};testversion=${e.debugVersion}`}]});return o}buildSupportCards(e,t,n){if(e.blockedTargets.length===0||e.supportCandidates.length===0)return[];let r=e.group.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN,a=e.supportCandidates.filter(e=>!n.has(e)).slice(0,r),o=[];for(let r of a)n.add(r),o.push({cardId:r,courseId:t,score:BASE_SUPPORT_SCORE*e.supportMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:BASE_SUPPORT_SCORE*e.supportMultiplier,reason:`mode=support;group=${e.group.id};pending=${e.pendingTargets.length};blocked=${e.blockedTargets.length};blockedTargets=${e.blockedTargets.join(`|`)||`none`};supportCard=${r};supportTags=${e.supportTags.join(`|`)||`none`};multiplier=${e.supportMultiplier.toFixed(2)};testversion=${e.debugVersion}`}]});return o}buildDiscoveredSupportCards(e,t,n){if(e.blockedTargets.length===0||e.discoveredSupportCandidates.length===0)return[];let r=e.group.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN,a=e.discoveredSupportCandidates.filter(e=>!n.has(e)).slice(0,r),o=[];for(let r of a)n.add(r),o.push({cardId:r,courseId:t,score:DISCOVERED_SUPPORT_SCORE*e.supportMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:DISCOVERED_SUPPORT_SCORE*e.supportMultiplier,reason:`mode=discovered-support;group=${e.group.id};pending=${e.pendingTargets.length};blocked=${e.blockedTargets.length};blockedTargets=${e.blockedTargets.join(`|`)||`none`};supportCard=${r};supportTags=${e.supportTags.join(`|`)||`none`};multiplier=${e.supportMultiplier.toFixed(2)};testversion=${e.debugVersion}`}]});return o}buildPracticeCards(e){let{group:t,courseId:n,emittedIds:r,cardsByTag:a,hierarchyConfigs:o,userTagElo:s,userGlobalElo:c,activeIds:l,seenIds:u,priorPracticeDebt:d,nextPracticeDebt:p}=e,m=t.practiceTagPatterns??[];if(m.length===0)return[];let g=t.practiceMinCount??DEFAULT_PRACTICE_MIN_COUNT,_=t.maxPracticeCardsPerRun??DEFAULT_MAX_PRACTICE_PER_RUN,v=[...a.keys()].filter(e=>m.some(t=>matchesTagPattern(e,t))&&this.isUnlockedGatedSkill(e,o,s,c)&&(s[e]?.count??0)<g);if(v.length===0)return[];let y=Date.now(),b=1440*60*1e3,x=new Map;for(let e of v){let t=d[e]??isoNow();p[e]=t;let n=Math.max(0,(y-new Date(t).getTime())/864e5),r=clamp(PRACTICE_BASE_MULT+n*PRACTICE_STALENESS_BUMP_PER_DAY,PRACTICE_BASE_MULT,MAX_PRACTICE_MULTIPLIER);x.set(e,r)}let S=this.findDiscoveredSupportCards({supportTags:v,cardsByTag:a,activeIds:l,seenIds:u,excludedIds:r,limit:_});if(S.length===0)return[];logger.info(`[Prescribed] Group '${t.id}' practice: ${v.length} unlocked under-practiced skill(s), emitting ${S.length} drill card(s)`);let C=[];for(let e of S){r.add(e);let o=PRACTICE_BASE_MULT;for(let t of v)(a.get(t)?.includes(e)??!1)&&(o=Math.max(o,x.get(t)??PRACTICE_BASE_MULT));let s=BASE_PRACTICE_SCORE*o;C.push({cardId:e,courseId:n,score:s,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:s,reason:`mode=practice;group=${t.id};debtMult=\xD7${o.toFixed(2)};underPracticedSkills=${v.length};practiceTags=${v.slice(0,8).join(`|`)}${v.length>8?`|…`:``};testversion=${PRESCRIBED_DEBUG_VERSION}`}]})}return C}isUnlockedGatedSkill(e,t,n,r){let a=t.map(t=>t.prerequisites[e]).filter(e=>Array.isArray(e)&&e.length>0);return a.length===0?!1:a.every(e=>e.every(e=>this.isPrerequisiteMet(e,n[e.tag],r)))}findSupportCardsByTags(e,t,n){if(n.length===0)return[];let r=e.supportCardIds??[],a=e.supportTagPatterns??[];if(r.length===0&&a.length===0)return[];let o=new Set;for(let e of r){let r=t.get(e)??[],s=n.some(e=>r.includes(e)),c=a.some(e=>r.some(t=>matchesTagPattern(t,e)));(s||c)&&o.add(e)}return[...o]}findDiscoveredSupportCards(e){let{supportTags:t,cardsByTag:n,activeIds:r,seenIds:a,excludedIds:o,limit:s}=e,c=new Map;for(let e of t){let t=n.get(e)??[];for(let e of t){if(r.has(e)||a.has(e)||o.has(e))continue;let t=c.get(e);t?t.matches+=1:c.set(e,{cardId:e,matches:1})}}let l=[...c.values()].sort((e,t)=>t.matches-e.matches||e.cardId.localeCompare(t.cardId)),u=new Set,d=[],p=[];for(let e of l){let t=extractWordStem(e.cardId);u.has(t)?p.push(e):(u.add(t),d.push(e))}return shuffleInPlace(d),shuffleInPlace(p),[...d,...p].slice(0,s).map(e=>e.cardId)}resolveBlockedSupportTags(e,t,n,r,a,o){let s=new Set,c=!1;for(let l of e){let e=t.map(e=>e.prerequisites[l]).filter(e=>Array.isArray(e)&&e.length>0);if(e.length!==0&&e.some(e=>e.some(e=>!this.isPrerequisiteMet(e,n[e.tag],r)))){if(c=!0,!a){for(let t of e)for(let e of t)this.isPrerequisiteMet(e,n[e.tag],r)||s.add(e.tag);continue}for(let a of e)for(let e of a)this.isPrerequisiteMet(e,n[e.tag],r)||this.collectSupportTagsRecursive(e.tag,t,n,r,o,new Set,s)}}return{blocked:c,supportTags:[...s]}}collectSupportTagsRecursive(e,t,n,r,a,o,s){if(a<0||o.has(e))return;o.add(e);let c=!1;for(let l of t){let u=l.prerequisites[e];if(!u||u.length===0)continue;let d=u.filter(e=>!this.isPrerequisiteMet(e,n[e.tag],r));if(d.length>0&&a>0){c=!0;for(let e of d)this.collectSupportTagsRecursive(e.tag,t,n,r,a-1,o,s)}}c||s.add(e)}isPrerequisiteMet(e,t,n){if(!t)return!1;let r=e.masteryThreshold?.minCount??DEFAULT_MIN_COUNT;return t.count<r?!1:e.masteryThreshold?.minElo===void 0?e.masteryThreshold?.minCount===void 0?t.score>=n:!0:t.score>=e.masteryThreshold.minElo}}}}),srs_exports={};__export(srs_exports,{default:()=>SRSNavigator});var DEFAULT_HEALTHY_BACKLOG,BACKLOG_GROWTH_RATE,SRSNavigator,init_srs=__esm({"src/core/navigators/generators/srs.ts"(){init_navigators(),init_SrsDebugger(),init_logger(),DEFAULT_HEALTHY_BACKLOG=20,BACKLOG_GROWTH_RATE=2,SRSNavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`name`,void 0),_defineProperty(this,`healthyBacklog`,void 0),this.name=n?.name||`SRS`,this.healthyBacklog=this.parseConfig(n?.serializedData).healthyBacklog??DEFAULT_HEALTHY_BACKLOG}parseConfig(e){if(!e)return{};try{return JSON.parse(e)}catch{return logger.warn(`[SRS] Failed to parse strategy config, using defaults`),{}}}async getWeightedCards(e,t){if(!this.user||!this.course)throw Error(`SRSNavigator requires user and course to be set`);let n=this.course.getCourseID(),r=await this.user.getPendingReviews(n),a=hooks.utc(),o=r.filter(e=>a.isAfter(hooks.utc(e.reviewTime)));if(o.length>0){let e=[...new Set(o.map(e=>e.cardId))],t=await this.course.getAppliedTagsBatch(e),n=[];if(o=o.filter(e=>(t.get(e.cardId)??[]).includes(`srs:skip`)?(n.push(e._id),!1):!0),n.length>0){logger.info(`[SRS] Removing ${n.length} scheduled reviews for srs:skip cards`);for(let e of n)this.user.removeScheduledCardReview(e)}}let s=this.computeBacklogMultiplier(o.length),c=r.filter(e=>!a.isAfter(hooks.utc(e.reviewTime))),l=null;if(c.length>0){let e=c.reduce((e,t)=>hooks.utc(e.reviewTime).isBefore(hooks.utc(t.reviewTime))?e:t),t=hooks.duration(hooks.utc(e.reviewTime).diff(a));l=t.asHours()<1?`${Math.round(t.asMinutes())}m`:t.asHours()<24?`${Math.round(t.asHours())}h`:`${Math.round(t.asDays())}d`}if(o.length>0){let e=s>1?` [backlog pressure: \xD7${s.toFixed(2)}]`:` [healthy backlog]`;logger.info(`[SRS] Course ${n}: ${o.length} reviews due now (of ${r.length} scheduled)${e}`)}else if(r.length>0){let e=[...r].sort((e,t)=>hooks.utc(e.reviewTime).diff(hooks.utc(t.reviewTime)))[0],t=hooks.utc(e.reviewTime),o=hooks.duration(t.diff(a)),s=o.asHours()<1?`${Math.round(o.asMinutes())}m`:o.asHours()<24?`${Math.round(o.asHours())}h`:`${Math.round(o.asDays())}d`;logger.info(`[SRS] Course ${n}: 0 reviews due now (${r.length} scheduled, next in ${s})`)}else logger.info(`[SRS] Course ${n}: No reviews scheduled`);let u=o.map(e=>{let{score:t,reason:n}=this.computeUrgencyScore(e,a,s);return{cardId:e.cardId,courseId:e.courseId,score:t,reviewID:e._id,provenance:[{strategy:`srs`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-SRS-default`,action:`generated`,score:t,reason:n}]}}).sort((e,t)=>t.score-e.score);return captureSrsBacklog({courseId:n,scheduledTotal:r.length,dueNow:o.length,healthyBacklog:this.healthyBacklog,backlogMultiplier:s,backlogGrowthRate:BACKLOG_GROWTH_RATE,topReviewScore:u.length>0?u[0].score:null,nextDueIn:l,timestamp:Date.now()}),{cards:u.slice(0,e)}}computeBacklogMultiplier(e){if(e<=this.healthyBacklog)return 1;let t=(e-this.healthyBacklog)/this.healthyBacklog;return BACKLOG_GROWTH_RATE**+t}computeUrgencyScore(e,t,n){let r=hooks.utc(e.scheduledAt),a=hooks.utc(e.reviewTime),o=Math.max(1,a.diff(r,`hours`)),s=t.diff(a,`hours`),c=s/o,l=.3+.7*Math.exp(-o/720),u=(.5+(Math.min(1,Math.max(0,c))*.5+l*.5)*.45)*n,d=[`${Math.round(s)}h overdue`,`interval: ${Math.round(o)}h`,`relative: ${c.toFixed(2)}`,`recency: ${l.toFixed(2)}`];return n>1&&d.push(`backlog: \xD7${n.toFixed(2)}`),d.push(`review`),{score:u,reason:d.join(`, `)}}}}}),types_exports={},init_types=__esm({"src/core/navigators/generators/types.ts"(){}}),globImport_generators,init_=__esm({'import("./generators/**/*") in src/core/navigators/index.ts'(){globImport_generators=__glob({"./generators/CompositeGenerator.ts":()=>Promise.resolve().then(()=>(init_CompositeGenerator(),CompositeGenerator_exports)),"./generators/elo.ts":()=>Promise.resolve().then(()=>(init_elo(),elo_exports)),"./generators/index.ts":()=>Promise.resolve().then(()=>(init_generators(),generators_exports)),"./generators/prescribed.ts":()=>Promise.resolve().then(()=>(init_prescribed(),prescribed_exports)),"./generators/srs.ts":()=>Promise.resolve().then(()=>(init_srs(),srs_exports)),"./generators/types.ts":()=>Promise.resolve().then(()=>(init_types(),types_exports))})}}),DEFAULT_LEARNABLE_WEIGHT,init_contentNavigationStrategy=__esm({"src/core/types/contentNavigationStrategy.ts"(){DEFAULT_LEARNABLE_WEIGHT={weight:1,confidence:.1,sampleSize:0}}}),WeightedFilter_exports={};__export(WeightedFilter_exports,{WeightedFilter:()=>WeightedFilter});var WeightedFilter,init_WeightedFilter=__esm({"src/core/navigators/filters/WeightedFilter.ts"(){init_contentNavigationStrategy(),WeightedFilter=class{constructor(e,t=DEFAULT_LEARNABLE_WEIGHT,n=!1,r){_defineProperty(this,`name`,void 0),_defineProperty(this,`inner`,void 0),_defineProperty(this,`learnable`,void 0),_defineProperty(this,`staticWeight`,void 0),_defineProperty(this,`strategyId`,void 0),this.inner=e,this.name=e.name,this.learnable=t,this.staticWeight=n,this.strategyId=r}async transform(e,t){let n=this.learnable.weight,r;if(!this.staticWeight&&t.orchestration){let e=this.strategyId||this.inner.strategyId||this.name;n=t.orchestration.getEffectiveWeight(e,this.learnable),r=t.orchestration.getDeviation(e)}if(Math.abs(n-1)<.001)return this.inner.transform(e,t);let a=new Map;for(let t of e)a.set(t.cardId,t.score);return(await this.inner.transform(e,t)).map(e=>{let t=a.get(e.cardId);if(t===void 0||t===0||e.score===0)return e;let o=e.score/t;if(Math.abs(o-1)<1e-4)return e;let s=t*o**+n,c=e.provenance.length-1,l=e.provenance[c];if(l){let t=[...e.provenance];return t[c]={...l,score:s,effectiveWeight:n,deviation:r},{...e,score:s,provenance:t}}return{...e,score:s}})}}}}),eloDistance_exports={};__export(eloDistance_exports,{DEFAULT_HALF_LIFE:()=>DEFAULT_HALF_LIFE,DEFAULT_MAX_MULTIPLIER:()=>DEFAULT_MAX_MULTIPLIER,DEFAULT_MIN_MULTIPLIER:()=>DEFAULT_MIN_MULTIPLIER,createEloDistanceFilter:()=>createEloDistanceFilter});function computeMultiplier(e,t,n,r){let a=e/t,o=Math.exp(-(a*a));return n+(r-n)*o}function createEloDistanceFilter(e){let t=e?.halfLife??DEFAULT_HALF_LIFE,n=e?.minMultiplier??DEFAULT_MIN_MULTIPLIER,r=e?.maxMultiplier??DEFAULT_MAX_MULTIPLIER;return{name:`ELO Distance Filter`,async transform(e,a){let{course:o,userElo:s}=a,c=e.map(e=>e.cardId),l=await o.getCardEloData(c);return e.map((e,a)=>{let o=l[a]?.global?.score??1e3,c=Math.abs(o-s),u=computeMultiplier(c,t,n,r),d=e.score*u,p=u<r-.01?`penalized`:`passed`;return{...e,score:d,provenance:[...e.provenance,{strategy:`eloDistance`,strategyName:`ELO Distance Filter`,strategyId:`ELO_DISTANCE_FILTER`,action:p,score:d,reason:`ELO distance ${Math.round(c)} (card: ${Math.round(o)}, user: ${Math.round(s)}) \u2192 ${u.toFixed(2)}x`}]}})}}}var DEFAULT_HALF_LIFE,DEFAULT_MIN_MULTIPLIER,DEFAULT_MAX_MULTIPLIER,init_eloDistance=__esm({"src/core/navigators/filters/eloDistance.ts"(){DEFAULT_HALF_LIFE=200,DEFAULT_MIN_MULTIPLIER=.3,DEFAULT_MAX_MULTIPLIER=1}}),hierarchyDefinition_exports={};__export(hierarchyDefinition_exports,{default:()=>HierarchyDefinitionNavigator});var DEFAULT_MIN_COUNT2,HierarchyDefinitionNavigator,init_hierarchyDefinition=__esm({"src/core/navigators/filters/hierarchyDefinition.ts"(){init_navigators(),init_logger(),DEFAULT_MIN_COUNT2=3,HierarchyDefinitionNavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`config`,void 0),_defineProperty(this,`name`,void 0),this.config=this.parseConfig(n.serializedData),this.name=n.name||`Hierarchy Definition`}parseConfig(e){try{return{prerequisites:JSON.parse(e).prerequisites||{}}}catch{return{prerequisites:{}}}}isPrerequisiteMet(e,t,n){if(!t)return!1;let r=e.masteryThreshold?.minCount??DEFAULT_MIN_COUNT2;return t.count<r?!1:e.masteryThreshold?.minElo===void 0?e.masteryThreshold?.minCount===void 0?t.score>=n:!0:t.score>=e.masteryThreshold.minElo}async getMasteredTags(e){let t=new Set;try{let n=toCourseElo((await e.user.getCourseRegDoc(e.course.getCourseID())).elo);for(let e of Object.values(this.config.prerequisites))for(let r of e){let e=n.tags[r.tag];this.isPrerequisiteMet(r,e,n.global.score)&&t.add(r.tag)}}catch{}return t}getUnlockedTags(e){let t=new Set;for(let[n,r]of Object.entries(this.config.prerequisites))r.every(t=>e.has(t.tag))&&t.add(n);return t}hasPrerequisites(e){return e in this.config.prerequisites}async checkCardUnlock(e,t,n,r){try{let t=e.tags??[],a=t.filter(e=>this.hasPrerequisites(e)&&!n.has(e));return a.length===0?{isUnlocked:!0,reason:`Prerequisites met, tags: ${t.length>0?t.join(`, `):`none`}`}:{isUnlocked:!1,reason:`Blocked: missing prerequisites ${a.flatMap(e=>(this.config.prerequisites[e]||[]).filter(e=>!r.has(e.tag)).map(e=>e.tag)).join(`, `)} for tags ${a.join(`, `)}`}}catch{return{isUnlocked:!0,reason:`Prerequisites check skipped (tag lookup failed)`}}}getPreReqBoosts(e,t){let n=new Map;for(let[r,a]of Object.entries(this.config.prerequisites))if(!e.has(r))for(let e of a){if(!e.preReqBoost||e.preReqBoost<=1||t.has(e.tag))continue;let r=n.get(e.tag)??1;n.set(e.tag,Math.max(r,e.preReqBoost))}return n}getTargetBoosts(e){let t=new Map,n=Object.keys(this.config.prerequisites),r=[...e];logger.info(`[HierarchyDefinition:targetBoost:trace] ${this.name} | configKeys=${n.length}, unlocked=${r.length} (${r.slice(0,5).join(`, `)}${r.length>5?`...`:``})`);for(let[n,r]of Object.entries(this.config.prerequisites))if(e.has(n)){logger.info(`[HierarchyDefinition:targetBoost:trace] UNLOCKED ${n}: ${r.length} prereqs, raw=${JSON.stringify(r.map(e=>({tag:e.tag,tb:e.targetBoost})))}`);for(let e of r){if(!e.targetBoost||e.targetBoost<=1)continue;let r=t.get(n)??1;t.set(n,Math.max(r,e.targetBoost))}}return t.size>0?logger.info(`[HierarchyDefinition] targetBoosts active: ${[...t.entries()].map(([e,t])=>`${e}=\xD7${t}`).join(`, `)}`):logger.info(`[HierarchyDefinition:targetBoost:trace] no targetBoosts found despite ${r.length} unlocked tags`),t}async transform(e,t){let n=await this.getMasteredTags(t),r=this.getUnlockedTags(n),a=this.getPreReqBoosts(r,n),o=this.getTargetBoosts(r),s=[];for(let c of e){let{isUnlocked:e,reason:l}=await this.checkCardUnlock(c,t.course,r,n),u=e?c.score:c.score*.02,d=e?`passed`:`penalized`,p=l;if(e&&a.size>0){let e=c.tags??[],t=1,n=[];for(let r of e){let e=a.get(r);e&&e>t&&(t=e,n.push(r))}t>1&&(u*=t,d=`boosted`,p=`${l} | preReqBoost \xD7${t.toFixed(2)} for ${n.join(`, `)}`,logger.info(`[HierarchyDefinition] preReqBoost \xD7${t.toFixed(2)} applied to card ${c.cardId} via tags [${n.join(`, `)}] (score: ${c.score.toFixed(3)} \u2192 ${u.toFixed(3)})`))}if(e&&o.size>0){let e=c.tags??[],t=1,n=[];for(let r of e){let e=o.get(r);e&&e>t&&(t=e,n.push(r))}t>1&&(u*=t,d=`boosted`,p=`${p} | targetBoost \xD7${t.toFixed(2)} for ${n.join(`, `)}`,logger.info(`[HierarchyDefinition] targetBoost \xD7${t.toFixed(2)} applied to card ${c.cardId} via tags [${n.join(`, `)}] (score: ${c.score.toFixed(3)} \u2192 ${u.toFixed(3)})`))}s.push({...c,score:u,provenance:[...c.provenance,{strategy:`hierarchyDefinition`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-hierarchy`,action:d,score:u,reason:p}]})}return s}async getWeightedCards(e){throw Error(`HierarchyDefinitionNavigator is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().`)}}}}),userTagPreference_exports={};__export(userTagPreference_exports,{default:()=>UserTagPreferenceFilter});var UserTagPreferenceFilter,init_userTagPreference=__esm({"src/core/navigators/filters/userTagPreference.ts"(){init_navigators(),UserTagPreferenceFilter=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`_strategyData`,void 0),_defineProperty(this,`name`,void 0),this._strategyData=n,this.name=n.name||`User Tag Preferences`}computeMultiplier(e,t){let n=e.map(e=>t[e]).filter(e=>e!==void 0);return n.length===0?1:Math.max(...n)}buildReason(e,t,n){let r=e.filter(e=>t[e]===n);return n===0?`Excluded by user preference: ${r.join(`, `)} (${n}x)`:n<1?`Penalized by user preference: ${r.join(`, `)} (${n.toFixed(2)}x)`:n>1?`Boosted by user preference: ${r.join(`, `)} (${n.toFixed(2)}x)`:`No matching user preferences`}async transform(e,t){let n=await this.getStrategyState();return!n||Object.keys(n.boost).length===0?e.map(e=>({...e,provenance:[...e.provenance,{strategy:`userTagPreference`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||this._strategyData._id,action:`passed`,score:e.score,reason:`No user tag preferences configured`}]})):await Promise.all(e.map(async e=>{let t=e.tags??[],r=this.computeMultiplier(t,n.boost),a=Math.min(1,e.score*r),o;return o=r===0||r<1?`penalized`:r>1?`boosted`:`passed`,{...e,score:a,provenance:[...e.provenance,{strategy:`userTagPreference`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||this._strategyData._id,action:o,score:a,reason:this.buildReason(t,n.boost,r)}]}}))}async getWeightedCards(e){throw Error(`UserTagPreferenceFilter is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().`)}}}}),filters_exports={};__export(filters_exports,{UserTagPreferenceFilter:()=>UserTagPreferenceFilter,createEloDistanceFilter:()=>createEloDistanceFilter});var init_filters=__esm({"src/core/navigators/filters/index.ts"(){init_eloDistance(),init_userTagPreference()}}),inferredPreferenceStub_exports={};__export(inferredPreferenceStub_exports,{INFERRED_PREFERENCE_NAVIGATOR_STUB:()=>INFERRED_PREFERENCE_NAVIGATOR_STUB});var INFERRED_PREFERENCE_NAVIGATOR_STUB,init_inferredPreferenceStub=__esm({"src/core/navigators/filters/inferredPreferenceStub.ts"(){INFERRED_PREFERENCE_NAVIGATOR_STUB=!0}}),interferenceMitigator_exports={};__export(interferenceMitigator_exports,{default:()=>InterferenceMitigatorNavigator});var DEFAULT_MIN_COUNT3,DEFAULT_MIN_ELAPSED_DAYS,DEFAULT_INTERFERENCE_DECAY,InterferenceMitigatorNavigator,init_interferenceMitigator=__esm({"src/core/navigators/filters/interferenceMitigator.ts"(){init_navigators(),DEFAULT_MIN_COUNT3=10,DEFAULT_MIN_ELAPSED_DAYS=3,DEFAULT_INTERFERENCE_DECAY=.8,InterferenceMitigatorNavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`config`,void 0),_defineProperty(this,`name`,void 0),_defineProperty(this,`interferenceMap`,void 0),this.config=this.parseConfig(n.serializedData),this.interferenceMap=this.buildInterferenceMap(),this.name=n.name||`Interference Mitigator`}parseConfig(e){try{let t=JSON.parse(e),n=t.interferenceSets||[];return n.length>0&&Array.isArray(n[0])&&(n=n.map(e=>({tags:e}))),{interferenceSets:n,maturityThreshold:{minCount:t.maturityThreshold?.minCount??DEFAULT_MIN_COUNT3,minElo:t.maturityThreshold?.minElo,minElapsedDays:t.maturityThreshold?.minElapsedDays??DEFAULT_MIN_ELAPSED_DAYS},defaultDecay:t.defaultDecay??DEFAULT_INTERFERENCE_DECAY}}catch{return{interferenceSets:[],maturityThreshold:{minCount:DEFAULT_MIN_COUNT3,minElapsedDays:DEFAULT_MIN_ELAPSED_DAYS},defaultDecay:DEFAULT_INTERFERENCE_DECAY}}}buildInterferenceMap(){let e=new Map;for(let t of this.config.interferenceSets){let n=t.decay??this.config.defaultDecay??DEFAULT_INTERFERENCE_DECAY;for(let r of t.tags){e.has(r)||e.set(r,[]);let a=e.get(r);for(let e of t.tags)if(e!==r){let t=a.find(t=>t.partner===e);t?t.decay=Math.max(t.decay,n):a.push({partner:e,decay:n})}}}return e}async getImmatureTags(e){let t=new Set;try{let n=toCourseElo((await e.user.getCourseRegDoc(e.course.getCourseID())).elo),r=this.config.maturityThreshold?.minCount??DEFAULT_MIN_COUNT3,a=this.config.maturityThreshold?.minElo,o=(this.config.maturityThreshold?.minElapsedDays??DEFAULT_MIN_ELAPSED_DAYS)*2;for(let[e,s]of Object.entries(n.tags)){if(s.count===0)continue;let n=s.count<r,c=a!==void 0&&s.score<a,l=s.count<o;(n||c||l)&&t.add(e)}}catch{}return t}getTagsToAvoid(e){let t=new Map;for(let n of e){let r=this.interferenceMap.get(n);if(r){for(let{partner:n,decay:a}of r)if(!e.has(n)){let e=t.get(n)??0;t.set(n,Math.max(e,a))}}}return t}computeInterferenceEffect(e,t,n){if(t.size===0)return{multiplier:1,interferingTags:[],reason:`No interference detected`};let r=1,a=[];for(let n of e){let e=t.get(n);e!==void 0&&(a.push(n),r*=1-e)}if(a.length===0)return{multiplier:1,interferingTags:[],reason:`No interference detected`};let o=new Set;for(let e of a)for(let t of n)this.interferenceMap.get(t)?.some(t=>t.partner===e)&&o.add(t);let s=`Interferes with immature tags ${Array.from(o).join(`, `)} (tags: ${a.join(`, `)}, multiplier: ${r.toFixed(2)})`;return{multiplier:r,interferingTags:a,reason:s}}async transform(e,t){let n=await this.getImmatureTags(t),r=this.getTagsToAvoid(n),a=[];for(let t of e){let e=t.tags??[],{multiplier:o,reason:s}=this.computeInterferenceEffect(e,r,n),c=t.score*o,l=o<1?`penalized`:o>1?`boosted`:`passed`;a.push({...t,score:c,provenance:[...t.provenance,{strategy:`interferenceMitigator`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-interference`,action:l,score:c,reason:s}]})}return a}async getWeightedCards(e){throw Error(`InterferenceMitigatorNavigator is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().`)}}}}),relativePriority_exports={};__export(relativePriority_exports,{default:()=>RelativePriorityNavigator});var DEFAULT_PRIORITY,DEFAULT_PRIORITY_INFLUENCE,DEFAULT_COMBINE_MODE,RelativePriorityNavigator,init_relativePriority=__esm({"src/core/navigators/filters/relativePriority.ts"(){init_navigators(),DEFAULT_PRIORITY=.5,DEFAULT_PRIORITY_INFLUENCE=.5,DEFAULT_COMBINE_MODE=`max`,RelativePriorityNavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`config`,void 0),_defineProperty(this,`name`,void 0),this.config=this.parseConfig(n.serializedData),this.name=n.name||`Relative Priority`}parseConfig(e){try{let t=JSON.parse(e);return{tagPriorities:t.tagPriorities||{},defaultPriority:t.defaultPriority??DEFAULT_PRIORITY,combineMode:t.combineMode??DEFAULT_COMBINE_MODE,priorityInfluence:t.priorityInfluence??DEFAULT_PRIORITY_INFLUENCE}}catch{return{tagPriorities:{},defaultPriority:DEFAULT_PRIORITY,combineMode:DEFAULT_COMBINE_MODE,priorityInfluence:DEFAULT_PRIORITY_INFLUENCE}}}getTagPriority(e){return this.config.tagPriorities[e]??this.config.defaultPriority??DEFAULT_PRIORITY}computeCardPriority(e){if(e.length===0)return this.config.defaultPriority??DEFAULT_PRIORITY;let t=e.map(e=>this.getTagPriority(e));switch(this.config.combineMode){case`max`:return Math.max(...t);case`min`:return Math.min(...t);case`average`:return t.reduce((e,t)=>e+t,0)/t.length;default:return Math.max(...t)}}computeBoostFactor(e){let t=this.config.priorityInfluence??DEFAULT_PRIORITY_INFLUENCE;return 1+(e-.5)*t}buildPriorityReason(e,t,n,r){if(e.length===0)return`No tags, neutral priority (${t.toFixed(2)})`;let a=e.slice(0,3).join(`, `),o=e.length>3?` (+${e.length-3} more)`:``;return n===1?`Neutral priority (${t.toFixed(2)}) for tags: ${a}${o}`:n>1?`High-priority tags: ${a}${o} (priority ${t.toFixed(2)} \u2192 boost ${n.toFixed(2)}x \u2192 ${r.toFixed(2)})`:`Low-priority tags: ${a}${o} (priority ${t.toFixed(2)} \u2192 reduce ${n.toFixed(2)}x \u2192 ${r.toFixed(2)})`}async transform(e,t){return await Promise.all(e.map(async e=>{let t=e.tags??[],n=this.computeCardPriority(t),r=this.computeBoostFactor(n),a=Math.max(0,e.score*r),o=r>1?`boosted`:r<1?`penalized`:`passed`,s=this.buildPriorityReason(t,n,r,a);return{...e,score:a,provenance:[...e.provenance,{strategy:`relativePriority`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-priority`,action:o,score:a,reason:s}]}}))}async getWeightedCards(e){throw Error(`RelativePriorityNavigator is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().`)}}}}),types_exports2={},init_types2=__esm({"src/core/navigators/filters/types.ts"(){}}),userGoalStub_exports={};__export(userGoalStub_exports,{USER_GOAL_NAVIGATOR_STUB:()=>USER_GOAL_NAVIGATOR_STUB});var USER_GOAL_NAVIGATOR_STUB,init_userGoalStub=__esm({"src/core/navigators/filters/userGoalStub.ts"(){USER_GOAL_NAVIGATOR_STUB=!0}}),globImport_filters,init_2=__esm({'import("./filters/**/*") in src/core/navigators/index.ts'(){globImport_filters=__glob({"./filters/WeightedFilter.ts":()=>Promise.resolve().then(()=>(init_WeightedFilter(),WeightedFilter_exports)),"./filters/eloDistance.ts":()=>Promise.resolve().then(()=>(init_eloDistance(),eloDistance_exports)),"./filters/hierarchyDefinition.ts":()=>Promise.resolve().then(()=>(init_hierarchyDefinition(),hierarchyDefinition_exports)),"./filters/index.ts":()=>Promise.resolve().then(()=>(init_filters(),filters_exports)),"./filters/inferredPreferenceStub.ts":()=>Promise.resolve().then(()=>(init_inferredPreferenceStub(),inferredPreferenceStub_exports)),"./filters/interferenceMitigator.ts":()=>Promise.resolve().then(()=>(init_interferenceMitigator(),interferenceMitigator_exports)),"./filters/relativePriority.ts":()=>Promise.resolve().then(()=>(init_relativePriority(),relativePriority_exports)),"./filters/types.ts":()=>Promise.resolve().then(()=>(init_types2(),types_exports2)),"./filters/userGoalStub.ts":()=>Promise.resolve().then(()=>(init_userGoalStub(),userGoalStub_exports)),"./filters/userTagPreference.ts":()=>Promise.resolve().then(()=>(init_userTagPreference(),userTagPreference_exports))})}});function aggregateOutcomesForGradient(e,t){let n=[];for(let r of e){let e=r.deviations[t];e!==void 0&&n.push({deviation:e,outcomeValue:r.outcomeValue,weight:1})}return logger.debug(`[Orchestration] Aggregated ${n.length} observations for strategy ${t}`),n}function computeStrategyGradient(e){let t=e.length;if(t<3)return logger.debug(`[Orchestration] Insufficient observations for gradient (${t} < 3)`),null;let n=0,r=0,a=0;for(let t of e){let e=t.weight??1;n+=t.deviation*e,r+=t.outcomeValue*e,a+=e}let o=n/a,s=r/a,c=0,l=0,u=0;for(let t of e){let e=t.weight??1,n=t.deviation-o,r=t.outcomeValue-s;c+=e*n*r,l+=e*n*n,u+=e*r*r}if(l<1e-10)return logger.debug(`[Orchestration] No variance in deviations, cannot compute gradient`),{gradient:0,intercept:s,rSquared:0,sampleSize:t};let d=c/l,p=s-d*o,m=0;for(let t of e){let e=t.weight??1,n=d*t.deviation+p,r=t.outcomeValue-n;m+=e*r*r}let g=u>1e-10?1-m/u:0;return logger.debug(`[Orchestration] Computed gradient: ${d.toFixed(4)}, intercept: ${p.toFixed(4)}, R\xB2: ${g.toFixed(4)}, n=${t}`),{gradient:d,intercept:p,rSquared:Math.max(0,Math.min(1,g)),sampleSize:t}}var init_gradient=__esm({"src/core/orchestration/gradient.ts"(){init_logger()}});function updateStrategyWeight(e,t){if(t.sampleSize<MIN_OBSERVATIONS_FOR_UPDATE)return logger.debug(`[Orchestration] Insufficient samples (${t.sampleSize} < ${MIN_OBSERVATIONS_FOR_UPDATE}), keeping current weight`),{...e,sampleSize:e.sampleSize+t.sampleSize};let n=t.rSquared>=MIN_R_SQUARED_FOR_GRADIENT,r=Math.abs(t.gradient)<FLAT_GRADIENT_THRESHOLD,a=e.weight,o=e.confidence;if(!n||r){let n=.05*(1-e.confidence);o=Math.min(1,e.confidence+n),logger.debug(`[Orchestration] Flat/unreliable gradient (|g|=${Math.abs(t.gradient).toFixed(4)}, R\xB2=${t.rSquared.toFixed(4)}). Increasing confidence: ${e.confidence.toFixed(3)} \u2192 ${o.toFixed(3)}`)}else{let n=t.gradient*LEARNING_RATE;n=Math.max(-MAX_WEIGHT_DELTA,Math.min(MAX_WEIGHT_DELTA,n)),a=e.weight+n,a=Math.max(.1,Math.min(3,a));let r=.02*(1-e.confidence);o=Math.min(1,e.confidence+r),logger.debug(`[Orchestration] Adjusting weight: ${e.weight.toFixed(3)} \u2192 ${a.toFixed(3)} (gradient=${t.gradient.toFixed(4)}, delta=${n.toFixed(4)})`)}return{weight:a,confidence:o,sampleSize:e.sampleSize+t.sampleSize}}function updateLearningState(e,t,n,r,a){let o=new Date().toISOString(),s=`STRATEGY_LEARNING_STATE::${e}::${t}`,c={timestamp:o,weight:n.weight,confidence:n.confidence,gradient:r.gradient},l=a?.history??[];return l=[...l,c],l.length>MAX_HISTORY_LENGTH&&(l=l.slice(l.length-MAX_HISTORY_LENGTH)),{_id:s,_rev:a?._rev,docType:`STRATEGY_LEARNING_STATE`,courseId:e,strategyId:t,currentWeight:n,regression:{gradient:r.gradient,intercept:r.intercept,rSquared:r.rSquared,sampleSize:r.sampleSize,computedAt:o},history:l,updatedAt:o}}function runPeriodUpdate(e){let{courseId:t,strategyId:n,currentWeight:r,gradient:a,existingState:o}=e;logger.info(`[Orchestration] Running period update for strategy ${n} (${a.sampleSize} observations)`);let s=updateStrategyWeight(r,a),c=s.weight!==r.weight,l=updateLearningState(t,n,s,a,o);return logger.info(`[Orchestration] Period update complete for ${n}: weight ${r.weight.toFixed(3)} \u2192 ${s.weight.toFixed(3)}, confidence ${r.confidence.toFixed(3)} \u2192 ${s.confidence.toFixed(3)}`),{strategyId:n,previousWeight:r,newWeight:s,gradient:a,learningState:l,updated:c}}function getDefaultLearnableWeight(){return{...DEFAULT_LEARNABLE_WEIGHT}}var MIN_OBSERVATIONS_FOR_UPDATE,LEARNING_RATE,MAX_WEIGHT_DELTA,MIN_R_SQUARED_FOR_GRADIENT,FLAT_GRADIENT_THRESHOLD,MAX_HISTORY_LENGTH,init_learning=__esm({"src/core/orchestration/learning.ts"(){init_contentNavigationStrategy(),init_types_legacy(),init_logger(),MIN_OBSERVATIONS_FOR_UPDATE=10,LEARNING_RATE=.1,MAX_WEIGHT_DELTA=.3,MIN_R_SQUARED_FOR_GRADIENT=.05,FLAT_GRADIENT_THRESHOLD=.02,MAX_HISTORY_LENGTH=100}});function computeOutcomeSignal(e,t={}){if(!e||e.length===0)return null;let n=t.targetAccuracy??.85,r=t.tolerance??.05,a=0;for(let t of e)t.isCorrect&&a++;return scoreAccuracyInZone(a/e.length,n,r)}function scoreAccuracyInZone(e,t,n){let r=Math.abs(e-t);if(r<=n)return 1;let a=r-n;return Math.max(0,1-a*2.5)}var init_signal=__esm({"src/core/orchestration/signal.ts"(){}});async function recordUserOutcome(e,t,n,r,a,o=0,s=0,c){let{user:l,course:u,userId:d}=e,p=u.getCourseID(),m=computeOutcomeSignal(r,c);if(m===null){logger.debug(`[Orchestration] No outcome signal computed for ${d} (insufficient data). Skipping record.`);return}let g={};for(let t of a)g[t]=e.getDeviation(t);let _=`USER_OUTCOME::${p}::${d}::${n}`,v={_id:_,docType:`USER_OUTCOME`,courseId:p,userId:d,periodStart:t,periodEnd:n,outcomeValue:m,deviations:g,metadata:{sessionsCount:1,cardsSeen:r.length,eloStart:o,eloEnd:s,signalType:`accuracy_in_zone`}};try{await l.putUserOutcome(v),logger.debug(`[Orchestration] Recorded outcome ${m.toFixed(3)} for ${d} (doc: ${_})`)}catch(e){logger.error(`[Orchestration] Failed to record outcome: ${e}`)}}var init_recording=__esm({"src/core/orchestration/recording.ts"(){init_signal(),init_types_legacy(),init_logger()}});function fnv1a(e){let t=2166136261;for(let n=0;n<e.length;n++)t^=e.charCodeAt(n),t=Math.imul(t,16777619);return t>>>0}function computeDeviation(e,t,n){return fnv1a(`${e}:${t}:${n}`)/4294967296*2-1}function computeSpread(e){return MAX_SPREAD-Math.max(0,Math.min(1,e))*(MAX_SPREAD-MIN_SPREAD)}function computeEffectiveWeight(e,t,n,r){let a=computeDeviation(t,n,r)*computeSpread(e.confidence)*e.weight,o=e.weight+a;return Math.max(MIN_WEIGHT,Math.min(MAX_WEIGHT,o))}async function createOrchestrationContext(e,t){let n;try{n=await t.getCourseConfig()}catch(e){logger.error(`[Orchestration] Failed to load course config: ${e}`),n={name:`Unknown`,description:``,public:!1,deleted:!1,creator:``,admins:[],moderators:[],dataShapes:[],questionTypes:[],orchestration:{salt:`default`}}}let r=e.getUsername(),a=n.orchestration?.salt||`default_salt`;return{user:e,course:t,userId:r,courseConfig:n,getEffectiveWeight(e,t){return computeEffectiveWeight(t,r,e,a)},getDeviation(e){return computeDeviation(r,e,a)}}}var MIN_SPREAD,MAX_SPREAD,MIN_WEIGHT,MAX_WEIGHT,init_orchestration=__esm({"src/core/orchestration/index.ts"(){init_logger(),init_gradient(),init_learning(),init_signal(),init_recording(),MIN_SPREAD=.1,MAX_SPREAD=.5,MIN_WEIGHT=.1,MAX_WEIGHT=3}}),Pipeline_exports={};__export(Pipeline_exports,{Pipeline:()=>Pipeline,mergeHints:()=>mergeHints2});function globToRegex(e){let t=e.replace(/[.+?^${}()|[\]\\]/g,`\\$&`).replace(/\*/g,`.*`);return RegExp(`^${t}$`)}function globMatch(e,t){return t.includes(`*`)?globToRegex(t).test(e):e===t}function cardMatchesTagPattern(e,t){return(e.tags??[]).some(e=>globMatch(e,t))}function mergeHints2(e){let t=e.filter(e=>e!=null);if(t.length===0)return;let n={},r={};for(let e of t)for(let[t,n]of Object.entries(e.boostTags??{}))r[t]=(r[t]??1)*n;Object.keys(r).length>0&&(n.boostTags=r);let a={};for(let e of t)for(let[t,n]of Object.entries(e.boostCards??{}))a[t]=(a[t]??1)*n;Object.keys(a).length>0&&(n.boostCards=a);let concatUnique=e=>{let r=t.flatMap(t=>t[e]??[]);r.length>0&&(n[e]=[...new Set(r)])};concatUnique(`requireTags`),concatUnique(`requireCards`),concatUnique(`excludeTags`),concatUnique(`excludeCards`);let o=t.map(e=>e._label).filter(Boolean);return o.length>0&&(n._label=o.join(`; `)),Object.keys(n).length>0?n:void 0}function logPipelineConfig(e,t){let n=t.length>0?`
249
+ `)}},mountPipelineDebugger()}}),SrsDebugger_exports={};__export(SrsDebugger_exports,{captureSrsBacklog:()=>captureSrsBacklog,clearSrsBacklogDebug:()=>clearSrsBacklogDebug,getSrsBacklogDebug:()=>getSrsBacklogDebug});function captureSrsBacklog(e){snapshots.set(e.courseId,e)}function getSrsBacklogDebug(){return[...snapshots.values()].sort((e,t)=>t.timestamp-e.timestamp)}function clearSrsBacklogDebug(){snapshots.clear()}var snapshots,init_SrsDebugger=__esm({"src/core/navigators/SrsDebugger.ts"(){snapshots=new Map}}),StrategyPressureDebugger_exports={};__export(StrategyPressureDebugger_exports,{captureStrategyPressure:()=>captureStrategyPressure,clearStrategyPressureDebug:()=>clearStrategyPressureDebug,getStrategyPressureDebug:()=>getStrategyPressureDebug});function captureStrategyPressure(e){snapshots2.set(`${e.source}:${e.courseId}`,e)}function getStrategyPressureDebug(){return[...snapshots2.values()].sort((e,t)=>t.timestamp-e.timestamp)}function clearStrategyPressureDebug(){snapshots2.clear()}var snapshots2,init_StrategyPressureDebugger=__esm({"src/core/navigators/StrategyPressureDebugger.ts"(){snapshots2=new Map}}),CompositeGenerator_exports={};__export(CompositeGenerator_exports,{AggregationMode:()=>AggregationMode,default:()=>CompositeGenerator});function mergeHints(e){let t=e.filter(e=>e!==void 0);if(t.length===0)return;let n={},r={};for(let e of t)for(let[t,n]of Object.entries(e.boostTags??{}))r[t]=(r[t]??1)*n;Object.keys(r).length>0&&(n.boostTags=r);let a={};for(let e of t)for(let[t,n]of Object.entries(e.boostCards??{}))a[t]=(a[t]??1)*n;Object.keys(a).length>0&&(n.boostCards=a);let concatUnique=e=>{let r=t.flatMap(t=>t[e]??[]);r.length>0&&(n[e]=[...new Set(r)])};concatUnique(`requireTags`),concatUnique(`requireCards`),concatUnique(`excludeTags`),concatUnique(`excludeCards`);let o=t.map(e=>e._label).filter(Boolean);return o.length>0&&(n._label=o.join(`; `)),Object.keys(n).length>0?n:void 0}var AggregationMode,DEFAULT_AGGREGATION_MODE,FREQUENCY_BOOST_FACTOR,CompositeGenerator,init_CompositeGenerator=__esm({"src/core/navigators/generators/CompositeGenerator.ts"(){init_navigators(),init_logger(),AggregationMode=(e=>(e.MAX=`max`,e.AVERAGE=`average`,e.FREQUENCY_BOOST=`frequencyBoost`,e))(AggregationMode||{}),DEFAULT_AGGREGATION_MODE=`frequencyBoost`,FREQUENCY_BOOST_FACTOR=.1,CompositeGenerator=class _CompositeGenerator extends ContentNavigator{constructor(e,t=DEFAULT_AGGREGATION_MODE){if(super(),_defineProperty(this,`name`,`Composite Generator`),_defineProperty(this,`generators`,void 0),_defineProperty(this,`aggregationMode`,void 0),this.generators=e,this.aggregationMode=t,e.length===0)throw Error(`CompositeGenerator requires at least one generator`);logger.debug(`[CompositeGenerator] Created with ${e.length} generators, mode: ${t}`)}static async fromStrategies(e,t,n,r=DEFAULT_AGGREGATION_MODE){return new _CompositeGenerator(await Promise.all(n.map(n=>ContentNavigator.create(e,t,n))),r)}async getWeightedCards(e,t){if(!t)throw Error(`CompositeGenerator.getWeightedCards requires a GeneratorContext. It should be called via Pipeline, not directly.`);let n=await Promise.all(this.generators.map(n=>n.getWeightedCards(e,t))),r=[];n.forEach((e,t)=>{let n=e.cards,a=this.generators[t].name||`Generator ${t}`,o=n.filter(e=>e.provenance[0]?.reason?.includes(`new card`)),s=n.filter(e=>e.provenance[0]?.reason?.includes(`review`));if(n.length>0){let e=Math.max(...n.map(e=>e.score)).toFixed(2),t=[];o.length>0&&t.push(`${o.length} new`),s.length>0&&t.push(`${s.length} reviews`);let c=t.length>0?t.join(`, `):`${n.length} cards`;r.push(`${a}: ${c} (top: ${e})`)}else r.push(`${a}: 0 cards`)}),logger.info(`[Composite] Generator breakdown: ${r.join(` | `)}`);let a=new Map;n.forEach((e,n)=>{let r=e.cards,o=this.generators[n],s=o.learnable?.weight??1,c;if(o.learnable&&!o.staticWeight&&t.orchestration){let e=o.strategyId;e&&(s=t.orchestration.getEffectiveWeight(e,o.learnable),c=t.orchestration.getDeviation(e))}for(let e of r){e.provenance.length>0&&(e.provenance[0].effectiveWeight=s,e.provenance[0].deviation=c);let t=a.get(e.cardId)||[];t.push({card:e,weight:s}),a.set(e.cardId,t)}});let o=[];for(let[,e]of a){let t=e.map(e=>e.card),n=this.aggregateScores(e),r=Math.max(0,n),a=t.flatMap(e=>e.provenance),s=t[0].score,c=r>s?`boosted`:r<s?`penalized`:`passed`,l=this.buildAggregationReason(e,r);o.push({...t[0],score:r,provenance:[...a,{strategy:`composite`,strategyName:`Composite Generator`,strategyId:`COMPOSITE_GENERATOR`,action:c,score:r,reason:l}]})}return{cards:o.sort((e,t)=>t.score-e.score).slice(0,e),hints:mergeHints(n.map(e=>e.hints))}}buildAggregationReason(e,t){let n=e.map(e=>e.card),r=n.length,a=n.map(e=>e.score.toFixed(2)).join(`, `);if(r===1){let n=Math.abs(e[0].weight-1)>.001?` (w=${e[0].weight.toFixed(2)})`:``;return`Single generator, score ${t.toFixed(2)}${n}`}let o=n.map(e=>e.provenance[0]?.strategy||`unknown`).join(`, `);switch(this.aggregationMode){case`max`:return`Max of ${r} generators (${o}): scores [${a}] \u2192 ${t.toFixed(2)}`;case`average`:return`Weighted Avg of ${r} generators (${o}): scores [${a}] \u2192 ${t.toFixed(2)}`;case`frequencyBoost`:{let n=e.reduce((e,t)=>e+t.weight,0),a=e.reduce((e,t)=>e+t.card.score*t.weight,0),s=n>0?a/n:0,c=1+FREQUENCY_BOOST_FACTOR*(r-1);return`Frequency boost from ${r} generators (${o}): w-avg ${s.toFixed(2)} \xD7 ${c.toFixed(2)} \u2192 ${t.toFixed(2)}`}default:return`Aggregated from ${r} generators: ${t.toFixed(2)}`}}aggregateScores(e){let t=e.map(e=>e.card.score);switch(this.aggregationMode){case`max`:return Math.max(...t);case`average`:{let t=e.reduce((e,t)=>e+t.weight,0);return t===0?0:e.reduce((e,t)=>e+t.card.score*t.weight,0)/t}case`frequencyBoost`:{let t=e.reduce((e,t)=>e+t.weight,0),n=e.reduce((e,t)=>e+t.card.score*t.weight,0);return(t>0?n/t:0)*(1+FREQUENCY_BOOST_FACTOR*(e.length-1))}default:return t[0]}}}}}),elo_exports={};__export(elo_exports,{default:()=>ELONavigator});var ELO_RELEVANCE_SIGMA,ELONavigator,init_elo=__esm({"src/core/navigators/generators/elo.ts"(){init_navigators(),init_logger(),ELO_RELEVANCE_SIGMA=300,ELONavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`name`,void 0),this.name=n?.name||`ELO`}async getWeightedCards(e,t){let n;n=t?.userElo===void 0?toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo).global.score:t.userElo;let r=await this.user.getActiveCards(),a=(await this.course.getCardsCenteredAtELO({limit:e,elo:`user`},e=>!r.some(t=>e.cardID===t.cardID))).map(e=>({...e,status:`new`})).map(e=>{let t=e.elo??1e3,r=Math.abs(t-n),a=Math.exp(-((r/ELO_RELEVANCE_SIGMA)**2)),o=a*(.5+.5*Math.random());return{cardId:e.cardID,courseId:e.courseID,score:o,provenance:[{strategy:`elo`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-ELO-default`,action:`generated`,score:o,reason:`ELO distance ${Math.round(r)} (card: ${Math.round(t)}, user: ${Math.round(n)}), relevance ${a.toFixed(3)}, key ${o.toFixed(3)}`}]}});a.sort((e,t)=>t.score-e.score);let o=a.slice(0,e);if(o.length>0){let e=o.slice(0,3).map(e=>e.score.toFixed(2)).join(`, `);logger.info(`[ELO] Course ${this.course.getCourseID()}: ${o.length} new cards (top scores: ${e})`)}else logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);return{cards:o}}}}}),generators_exports={},init_generators=__esm({"src/core/navigators/generators/index.ts"(){}}),prescribed_exports={};__export(prescribed_exports,{default:()=>PrescribedCardsGenerator});function dedupe(e){return[...new Set(e)]}function isoNow(){return new Date().toISOString()}function clamp(e,t,n){return Math.max(t,Math.min(n,e))}function matchesTagPattern(e,t){if(t===`*`)return!0;let n=t.replace(/[.+^${}()|[\]\\]/g,`\\$&`).replace(/\*/g,`.*`);return RegExp(`^${n}$`).test(e)}function extractWordStem(e){for(let t of[`c-ml-`,`c-ws-`,`c-spelling-`])if(e.startsWith(t)){let n=e.slice(t.length),r=n.lastIndexOf(`-`);return r>0?n.slice(0,r):n}return e}function shuffleInPlace(e){for(let t=e.length-1;t>0;t--){let n=Math.floor(Math.random()*(t+1));[e[t],e[n]]=[e[n],e[t]]}}function formatAge(e){let t=Math.max(0,Math.round(e/6e4));if(t<60)return`${t}m`;let n=Math.round(t/60);return n<24?`${n}h`:`${Math.round(n/24)}d`}function pickTopByScore(e,t){return[...e].sort((e,t)=>t.score-e.score||e.cardId.localeCompare(t.cardId)).slice(0,t)}var DEFAULT_FRESHNESS_WINDOW,DEFAULT_MAX_DIRECT_PER_RUN,DEFAULT_MAX_SUPPORT_PER_RUN,DEFAULT_HIERARCHY_DEPTH,DEFAULT_MIN_COUNT,DEFAULT_PRACTICE_MIN_COUNT,DEFAULT_MAX_PRACTICE_PER_RUN,BASE_TARGET_SCORE,BASE_SUPPORT_SCORE,DISCOVERED_SUPPORT_SCORE,BASE_PRACTICE_SCORE,PRACTICE_BASE_MULT,MAX_PRACTICE_MULTIPLIER,PRACTICE_STALENESS_BUMP_PER_DAY,MAX_TARGET_MULTIPLIER,MAX_SUPPORT_MULTIPLIER,PRESCRIBED_DEBUG_VERSION,PrescribedCardsGenerator,init_prescribed=__esm({"src/core/navigators/generators/prescribed.ts"(){init_navigators(),init_StrategyPressureDebugger(),init_logger(),DEFAULT_FRESHNESS_WINDOW=3,DEFAULT_MAX_DIRECT_PER_RUN=3,DEFAULT_MAX_SUPPORT_PER_RUN=3,DEFAULT_HIERARCHY_DEPTH=2,DEFAULT_MIN_COUNT=3,DEFAULT_PRACTICE_MIN_COUNT=3,DEFAULT_MAX_PRACTICE_PER_RUN=4,BASE_TARGET_SCORE=1,BASE_SUPPORT_SCORE=.8,DISCOVERED_SUPPORT_SCORE=12,BASE_PRACTICE_SCORE=1,PRACTICE_BASE_MULT=2,MAX_PRACTICE_MULTIPLIER=4,PRACTICE_STALENESS_BUMP_PER_DAY=.5,MAX_TARGET_MULTIPLIER=8,MAX_SUPPORT_MULTIPLIER=4,PRESCRIBED_DEBUG_VERSION=`testversion-prescribed-v3`,PrescribedCardsGenerator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`name`,void 0),_defineProperty(this,`config`,void 0),this.name=n.name||`Prescribed Cards`,this.config=this.parseConfig(n.serializedData),logger.debug(`[Prescribed] Initialized with ${this.config.groups.length} groups and ${this.config.groups.reduce((e,t)=>e+t.targetCardIds.length,0)} targets`)}get strategyKey(){return`PrescribedProgress`}async getWeightedCards(e,t){if(this.config.groups.length===0||e<=0)return{cards:[]};let n=this.course.getCourseID(),r=await this.user.getActiveCards(),a=new Set(r.map(e=>e.cardID)),o=await this.user.getSeenCards(n).catch(()=>[]),s=new Set(o),c=await this.getStrategyState()??{updatedAt:isoNow(),groups:{}},l=await this.loadHierarchyConfigs(),u=await this.user.getCourseRegDoc(n).catch(()=>null),d=typeof u?.elo==`number`?u.elo:u?.elo?.global?.score??t?.userElo??1e3,p=typeof u?.elo==`number`?{}:u?.elo?.tags??{},m=dedupe(this.config.groups.flatMap(e=>e.targetCardIds)),g=dedupe(this.config.groups.flatMap(e=>e.supportCardIds??[])),_=dedupe([...m,...g]),v=_.length>0?await this.course.getAppliedTagsBatch(_):new Map,y=await this.course.getCourseTagStubs().catch(()=>({rows:[],offset:0,total_rows:0})),b=new Map;for(let e of y.rows??[]){let t=e.doc;t?.name&&Array.isArray(t.taggedCards)&&b.set(t.name,[...t.taggedCards])}let x={updatedAt:isoNow(),groups:{}},S=[],C=new Set,w=[],T=c.practiceDebt??{},E={},D=new Map;for(let e of this.config.groups){let t=this.buildGroupRuntimeState({group:e,priorState:c.groups[e.id],activeIds:a,seenIds:s,tagsByCard:v,cardsByTag:b,hierarchyConfigs:l,userTagElo:p,userGlobalElo:d});w.push(t),logger.info(`[Prescribed] Group '${e.id}': ${e.targetCardIds.length} targets total, ${t.encounteredTargets.size} encountered, ${t.pendingTargets.length} pending (${t.surfaceableTargets.length} surfaceable, ${t.blockedTargets.length} blocked), ${t.supportCandidates.length} authored support candidates, ${t.discoveredSupportCandidates.length} discovered support candidates, pressure=${t.pressureMultiplier.toFixed(2)}`),t.blockedTargets.length>0&&(logger.info(`[Prescribed] Group '${e.id}' blocked targets: ${t.blockedTargets.join(`, `)}`),logger.info(`[Prescribed] Group '${e.id}' support tags needed: ${t.supportTags.join(`, `)||`(none)`}`),logger.info(`[Prescribed] Group '${e.id}' escalation mode: `+(t.supportCandidates.length>0?`direct-support`:t.discoveredSupportCandidates.length>0?`inserted-support-candidates`:`boost-only`)),t.discoveredSupportCandidates.length>0&&logger.info(`[Prescribed] Group '${e.id}' discovered support candidates: ${t.discoveredSupportCandidates.join(`, `)}`)),x.groups[e.id]=this.buildNextGroupState(t,c.groups[e.id]);let r=this.buildDirectTargetCards(t,n,C),o=this.buildSupportCards(t,n,C),u=this.buildDiscoveredSupportCards(t,n,C),m=this.buildPracticeCards({group:e,courseId:n,emittedIds:C,cardsByTag:b,hierarchyConfigs:l,userTagElo:p,userGlobalElo:d,activeIds:a,seenIds:s,priorPracticeDebt:T,nextPracticeDebt:E});D.set(e.id,m.debts),S.push(...r,...o,...u,...m.cards)}x.practiceDebt=E;let O=this.buildSupportHintSummary(w),k=Object.keys(O.boostTags).length>0?{boostTags:O.boostTags,_label:`prescribed-support (${O.supportTags.length} tags; blocked=${O.blockedTargetIds.length}; testversion=${PRESCRIBED_DEBUG_VERSION})`}:void 0;if(k){let e=Object.entries(k.boostTags??{});logger.info(`[Prescribed] Emitting ${e.length} boost hint(s): `+e.map(([e,t])=>`${e}\xD7${t.toFixed(1)}`).join(`, `))}else logger.info(`[Prescribed] No hints to emit (no blocked targets or no support tags)`);if(this.capturePressureSnapshot(n,w,D,S,k),S.length===0)return logger.info(`[Prescribed] 0 cards emitted (all targets blocked, authored/discovered support candidates exhausted)`+(k?` — boost hints emitted but may not survive filters`:``)),await this.putStrategyState(x).catch(e=>{logger.debug(`[Prescribed] Failed to persist empty-state update: ${e}`)}),k?{cards:[],hints:k}:{cards:[]};let A=pickTopByScore(S,e),j=new Map;for(let e of A){let t=e.provenance[0];if(t?.reason.includes(`mode=practice`))continue;let n=t?.reason.match(/group=([^;]+)/)?.[1],r=t?.reason.includes(`mode=support`)?`supportIds`:`targetIds`;n&&(j.has(n)||j.set(n,{targetIds:[],supportIds:[]}),j.get(n)[r].push(e.cardId))}for(let e of this.config.groups){let t=x.groups[e.id],n=j.get(e.id);n&&(n.targetIds.length>0||n.supportIds.length>0)&&(t.lastSurfacedAt=isoNow(),t.sessionsSinceSurfaced=0,n.supportIds.length>0&&(t.lastSupportAt=isoNow()))}return await this.putStrategyState(x).catch(e=>{logger.debug(`[Prescribed] Failed to persist prescribed progress: ${e}`)}),logger.info(`[Prescribed] Emitting ${A.length} cards (${A.filter(e=>e.provenance[0]?.reason.includes(`mode=target`)).length} target, ${A.filter(e=>e.provenance[0]?.reason.includes(`mode=support`)).length} support, ${A.filter(e=>e.provenance[0]?.reason.includes(`mode=discovered-support`)).length} discovered support)`),k?{cards:A,hints:k}:{cards:A}}buildSupportHintSummary(e){let t={},n=new Set,r=new Set;for(let a of e)if(!(a.blockedTargets.length===0||a.supportTags.length===0)){a.blockedTargets.forEach(e=>n.add(e));for(let e of a.supportTags)r.add(e),t[e]=(t[e]??1)*a.supportMultiplier}return{boostTags:t,blockedTargetIds:[...n].sort(),supportTags:[...r].sort()}}capturePressureSnapshot(e,t,n,r,a){let o=Date.now(),s=[];for(let e of t){let t=e.group,r=t.freshnessWindowSessions??DEFAULT_FRESHNESS_WINDOW;if(s.push({id:`group:${t.id}:target`,label:`${t.id} targets`,multiplier:e.pressureMultiplier,max:MAX_TARGET_MULTIPLIER,detail:`${e.pendingTargets.length} pending (${e.surfaceableTargets.length} surfaceable, ${e.blockedTargets.length} blocked) \xB7 sinceSurfaced ${e.sessionsSinceSurfaced}/${r}`,items:e.blockedTargets.map(e=>({label:e,value:`blocked`}))}),e.blockedTargets.length>0){let n=e.supportCandidates.length>0?`direct-support`:e.discoveredSupportCandidates.length>0?`inserted-support`:`boost-only`;s.push({id:`group:${t.id}:support`,label:`${t.id} support`,multiplier:e.supportMultiplier,max:MAX_SUPPORT_MULTIPLIER,detail:`mode=${n} \xB7 ${e.supportTags.length} support tag(s)`,items:e.supportTags.map(e=>({label:e}))})}let a=n.get(t.id)??[];a.length>0&&s.push({id:`group:${t.id}:practice-debt`,label:`${t.id} practice debt`,multiplier:Math.max(...a.map(e=>e.multiplier)),max:MAX_PRACTICE_MULTIPLIER,detail:`${a.length} under-practiced skill(s)`,items:a.map(e=>({label:e.tag,value:`\xD7${e.multiplier.toFixed(2)} \xB7 open ${formatAge(o-new Date(e.firstOwedAt).getTime())}`}))})}captureStrategyPressure({source:`prescribed`,courseId:e,gauges:s,topScore:r.length>0?Math.max(...r.map(e=>e.score)):null,hintsLabel:a?._label,timestamp:o})}parseConfig(e){try{let t=JSON.parse(e);return{groups:(Array.isArray(t.groups)?t.groups:[]).map((e,t)=>({id:typeof e.id==`string`&&e.id.trim().length>0?e.id:`group-${t+1}`,targetCardIds:dedupe(Array.isArray(e.targetCardIds)?e.targetCardIds.filter(e=>typeof e==`string`):[]),supportCardIds:dedupe(Array.isArray(e.supportCardIds)?e.supportCardIds.filter(e=>typeof e==`string`):[]),supportTagPatterns:dedupe(Array.isArray(e.supportTagPatterns)?e.supportTagPatterns.filter(e=>typeof e==`string`):[]),freshnessWindowSessions:typeof e.freshnessWindowSessions==`number`?e.freshnessWindowSessions:DEFAULT_FRESHNESS_WINDOW,maxDirectTargetsPerRun:typeof e.maxDirectTargetsPerRun==`number`?e.maxDirectTargetsPerRun:DEFAULT_MAX_DIRECT_PER_RUN,maxSupportCardsPerRun:typeof e.maxSupportCardsPerRun==`number`?e.maxSupportCardsPerRun:DEFAULT_MAX_SUPPORT_PER_RUN,hierarchyWalk:{enabled:e.hierarchyWalk?.enabled!==!1,maxDepth:typeof e.hierarchyWalk?.maxDepth==`number`?e.hierarchyWalk.maxDepth:DEFAULT_HIERARCHY_DEPTH},retireOnEncounter:e.retireOnEncounter!==!1,practiceTagPatterns:dedupe(Array.isArray(e.practiceTagPatterns)?e.practiceTagPatterns.filter(e=>typeof e==`string`):[]),practiceMinCount:typeof e.practiceMinCount==`number`?e.practiceMinCount:DEFAULT_PRACTICE_MIN_COUNT,maxPracticeCardsPerRun:typeof e.maxPracticeCardsPerRun==`number`?e.maxPracticeCardsPerRun:DEFAULT_MAX_PRACTICE_PER_RUN})).filter(e=>e.targetCardIds.length>0)}}catch{return{groups:[]}}}async loadHierarchyConfigs(){try{return(await this.course.getAllNavigationStrategies()).filter(e=>e.implementingClass===`hierarchyDefinition`).map(e=>{try{return{prerequisites:JSON.parse(e.serializedData).prerequisites||{}}}catch{return{prerequisites:{}}}})}catch(e){return logger.debug(`[Prescribed] Failed to load hierarchy configs: ${e}`),[]}}buildGroupRuntimeState(e){let{group:t,priorState:n,activeIds:r,seenIds:a,tagsByCard:o,cardsByTag:s,hierarchyConfigs:c,userTagElo:l,userGlobalElo:u}=e,d=new Set;for(let e of t.targetCardIds)(r.has(e)||a.has(e))&&d.add(e);if(n?.encounteredCardIds?.length)for(let e of n.encounteredCardIds)d.add(e);let p=t.targetCardIds.filter(e=>!d.has(e)),m=new Map;for(let e of p)m.set(e,o.get(e)??[]);let g=[],_=[],v=new Set;for(let e of p){let n=m.get(e)??[],r=this.resolveBlockedSupportTags(n,c,l,u,t.hierarchyWalk?.enabled!==!1,t.hierarchyWalk?.maxDepth??DEFAULT_HIERARCHY_DEPTH),a=n.filter(e=>e.startsWith(`gpc:intro:`)),o=new Set(n.filter(e=>e.startsWith(`gpc:expose:`)));for(let e of a){let t=e.slice(10);t&&o.add(`gpc:expose:${t}`)}let s=[...o].filter(e=>{let t=l[e];return!t||t.count<DEFAULT_MIN_COUNT});s.length>0&&s.forEach(e=>v.add(e)),r.blocked||s.length>0?(g.push(e),r.supportTags.forEach(e=>v.add(e))):_.push(e)}let y=dedupe([...t.supportCardIds??[],...this.findSupportCardsByTags(t,o,[...v])]).filter(e=>!r.has(e)&&!a.has(e)),b=g.length>0&&v.size>0&&y.length===0?this.findDiscoveredSupportCards({supportTags:[...v],cardsByTag:s,activeIds:r,seenIds:a,excludedIds:new Set([...t.targetCardIds,...t.supportCardIds??[]]),limit:t.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN}):[];g.length>0&&v.size>0&&b.length===0&&logger.info(`[Prescribed] Group '${t.id}' discovered 0 broader support candidates (blocked=${g.length}; authoredSupport=${y.length})`);let x=n?.sessionsSinceSurfaced??0,S=t.freshnessWindowSessions??DEFAULT_FRESHNESS_WINDOW,C=Math.max(0,x-S),w=p.length===0?1:clamp(1+C*.75+Math.min(2,p.length*.1),1,MAX_TARGET_MULTIPLIER),T=g.length===0?1:clamp(1+C*.5+Math.min(1.5,g.length*.15),1,MAX_SUPPORT_MULTIPLIER);return{group:t,encounteredTargets:d,pendingTargets:p,blockedTargets:g,surfaceableTargets:_,targetTags:m,supportCandidates:y,discoveredSupportCandidates:b,supportTags:[...v],pressureMultiplier:w,supportMultiplier:T,sessionsSinceSurfaced:x,debugVersion:PRESCRIBED_DEBUG_VERSION}}buildNextGroupState(e,t){let n=t?.sessionsSinceSurfaced??0,r=!1;return{encounteredCardIds:[...e.encounteredTargets].sort(),pendingTargetIds:[...e.pendingTargets].sort(),lastSurfacedAt:t?.lastSurfacedAt??null,sessionsSinceSurfaced:n+1,lastSupportAt:t?.lastSupportAt??null,blockedTargetIds:[...e.blockedTargets].sort(),lastResolvedSupportTags:[...e.supportTags].sort()}}buildDirectTargetCards(e,t,n){let r=e.group.maxDirectTargetsPerRun??DEFAULT_MAX_DIRECT_PER_RUN,a=e.surfaceableTargets.filter(e=>!n.has(e)).slice(0,r),o=[];for(let r of a)n.add(r),o.push({cardId:r,courseId:t,score:BASE_TARGET_SCORE*e.pressureMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:BASE_TARGET_SCORE*e.pressureMultiplier,reason:`mode=target;group=${e.group.id};pending=${e.pendingTargets.length};surfaceable=${e.surfaceableTargets.length};blocked=${e.blockedTargets.length};blockedTargets=${e.blockedTargets.join(`|`)||`none`};supportTags=${e.supportTags.join(`|`)||`none`};multiplier=${e.pressureMultiplier.toFixed(2)};testversion=${e.debugVersion}`}]});return o}buildSupportCards(e,t,n){if(e.blockedTargets.length===0||e.supportCandidates.length===0)return[];let r=e.group.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN,a=e.supportCandidates.filter(e=>!n.has(e)).slice(0,r),o=[];for(let r of a)n.add(r),o.push({cardId:r,courseId:t,score:BASE_SUPPORT_SCORE*e.supportMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:BASE_SUPPORT_SCORE*e.supportMultiplier,reason:`mode=support;group=${e.group.id};pending=${e.pendingTargets.length};blocked=${e.blockedTargets.length};blockedTargets=${e.blockedTargets.join(`|`)||`none`};supportCard=${r};supportTags=${e.supportTags.join(`|`)||`none`};multiplier=${e.supportMultiplier.toFixed(2)};testversion=${e.debugVersion}`}]});return o}buildDiscoveredSupportCards(e,t,n){if(e.blockedTargets.length===0||e.discoveredSupportCandidates.length===0)return[];let r=e.group.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN,a=e.discoveredSupportCandidates.filter(e=>!n.has(e)).slice(0,r),o=[];for(let r of a)n.add(r),o.push({cardId:r,courseId:t,score:DISCOVERED_SUPPORT_SCORE*e.supportMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:DISCOVERED_SUPPORT_SCORE*e.supportMultiplier,reason:`mode=discovered-support;group=${e.group.id};pending=${e.pendingTargets.length};blocked=${e.blockedTargets.length};blockedTargets=${e.blockedTargets.join(`|`)||`none`};supportCard=${r};supportTags=${e.supportTags.join(`|`)||`none`};multiplier=${e.supportMultiplier.toFixed(2)};testversion=${e.debugVersion}`}]});return o}buildPracticeCards(e){let{group:t,courseId:n,emittedIds:r,cardsByTag:a,hierarchyConfigs:o,userTagElo:s,userGlobalElo:c,activeIds:l,seenIds:u,priorPracticeDebt:d,nextPracticeDebt:p}=e,m=t.practiceTagPatterns??[];if(m.length===0)return{cards:[],debts:[]};let g=t.practiceMinCount??DEFAULT_PRACTICE_MIN_COUNT,_=t.maxPracticeCardsPerRun??DEFAULT_MAX_PRACTICE_PER_RUN,v=[...a.keys()].filter(e=>m.some(t=>matchesTagPattern(e,t))&&this.isUnlockedGatedSkill(e,o,s,c)&&(s[e]?.count??0)<g);if(v.length===0)return{cards:[],debts:[]};let y=Date.now(),b=1440*60*1e3,x=new Map;for(let e of v){let t=d[e]??isoNow();p[e]=t;let n=Math.max(0,(y-new Date(t).getTime())/864e5),r=clamp(PRACTICE_BASE_MULT+n*PRACTICE_STALENESS_BUMP_PER_DAY,PRACTICE_BASE_MULT,MAX_PRACTICE_MULTIPLIER);x.set(e,r)}let S=v.map(e=>({tag:e,multiplier:x.get(e)??PRACTICE_BASE_MULT,firstOwedAt:p[e]})).sort((e,t)=>t.multiplier-e.multiplier||e.tag.localeCompare(t.tag)),C=this.findDiscoveredSupportCards({supportTags:v,cardsByTag:a,activeIds:l,seenIds:u,excludedIds:r,limit:_});if(C.length===0)return{cards:[],debts:S};logger.info(`[Prescribed] Group '${t.id}' practice: ${v.length} unlocked under-practiced skill(s), emitting ${C.length} drill card(s)`);let w=[];for(let e of C){r.add(e);let o=PRACTICE_BASE_MULT;for(let t of v)(a.get(t)?.includes(e)??!1)&&(o=Math.max(o,x.get(t)??PRACTICE_BASE_MULT));let s=BASE_PRACTICE_SCORE*o;w.push({cardId:e,courseId:n,score:s,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:s,reason:`mode=practice;group=${t.id};debtMult=\xD7${o.toFixed(2)};underPracticedSkills=${v.length};practiceTags=${v.slice(0,8).join(`|`)}${v.length>8?`|…`:``};testversion=${PRESCRIBED_DEBUG_VERSION}`}]})}return{cards:w,debts:S}}isUnlockedGatedSkill(e,t,n,r){let a=t.map(t=>t.prerequisites[e]).filter(e=>Array.isArray(e)&&e.length>0);return a.length===0?!1:a.every(e=>e.every(e=>this.isPrerequisiteMet(e,n[e.tag],r)))}findSupportCardsByTags(e,t,n){if(n.length===0)return[];let r=e.supportCardIds??[],a=e.supportTagPatterns??[];if(r.length===0&&a.length===0)return[];let o=new Set;for(let e of r){let r=t.get(e)??[],s=n.some(e=>r.includes(e)),c=a.some(e=>r.some(t=>matchesTagPattern(t,e)));(s||c)&&o.add(e)}return[...o]}findDiscoveredSupportCards(e){let{supportTags:t,cardsByTag:n,activeIds:r,seenIds:a,excludedIds:o,limit:s}=e,c=new Map;for(let e of t){let t=n.get(e)??[];for(let e of t){if(r.has(e)||a.has(e)||o.has(e))continue;let t=c.get(e);t?t.matches+=1:c.set(e,{cardId:e,matches:1})}}let l=[...c.values()].sort((e,t)=>t.matches-e.matches||e.cardId.localeCompare(t.cardId)),u=new Set,d=[],p=[];for(let e of l){let t=extractWordStem(e.cardId);u.has(t)?p.push(e):(u.add(t),d.push(e))}return shuffleInPlace(d),shuffleInPlace(p),[...d,...p].slice(0,s).map(e=>e.cardId)}resolveBlockedSupportTags(e,t,n,r,a,o){let s=new Set,c=!1;for(let l of e){let e=t.map(e=>e.prerequisites[l]).filter(e=>Array.isArray(e)&&e.length>0);if(e.length!==0&&e.some(e=>e.some(e=>!this.isPrerequisiteMet(e,n[e.tag],r)))){if(c=!0,!a){for(let t of e)for(let e of t)this.isPrerequisiteMet(e,n[e.tag],r)||s.add(e.tag);continue}for(let a of e)for(let e of a)this.isPrerequisiteMet(e,n[e.tag],r)||this.collectSupportTagsRecursive(e.tag,t,n,r,o,new Set,s)}}return{blocked:c,supportTags:[...s]}}collectSupportTagsRecursive(e,t,n,r,a,o,s){if(a<0||o.has(e))return;o.add(e);let c=!1;for(let l of t){let u=l.prerequisites[e];if(!u||u.length===0)continue;let d=u.filter(e=>!this.isPrerequisiteMet(e,n[e.tag],r));if(d.length>0&&a>0){c=!0;for(let e of d)this.collectSupportTagsRecursive(e.tag,t,n,r,a-1,o,s)}}c||s.add(e)}isPrerequisiteMet(e,t,n){if(!t)return!1;let r=e.masteryThreshold?.minCount??DEFAULT_MIN_COUNT;return t.count<r?!1:e.masteryThreshold?.minElo===void 0?e.masteryThreshold?.minCount===void 0?t.score>=n:!0:t.score>=e.masteryThreshold.minElo}}}}),srs_exports={};__export(srs_exports,{default:()=>SRSNavigator});var DEFAULT_HEALTHY_BACKLOG,BACKLOG_GROWTH_RATE,SRSNavigator,init_srs=__esm({"src/core/navigators/generators/srs.ts"(){init_navigators(),init_SrsDebugger(),init_logger(),DEFAULT_HEALTHY_BACKLOG=20,BACKLOG_GROWTH_RATE=2,SRSNavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`name`,void 0),_defineProperty(this,`healthyBacklog`,void 0),this.name=n?.name||`SRS`,this.healthyBacklog=this.parseConfig(n?.serializedData).healthyBacklog??DEFAULT_HEALTHY_BACKLOG}parseConfig(e){if(!e)return{};try{return JSON.parse(e)}catch{return logger.warn(`[SRS] Failed to parse strategy config, using defaults`),{}}}async getWeightedCards(e,t){if(!this.user||!this.course)throw Error(`SRSNavigator requires user and course to be set`);let n=this.course.getCourseID(),r=await this.user.getPendingReviews(n),a=hooks.utc(),o=r.filter(e=>a.isAfter(hooks.utc(e.reviewTime)));if(o.length>0){let e=[...new Set(o.map(e=>e.cardId))],t=await this.course.getAppliedTagsBatch(e),n=[];if(o=o.filter(e=>(t.get(e.cardId)??[]).includes(`srs:skip`)?(n.push(e._id),!1):!0),n.length>0){logger.info(`[SRS] Removing ${n.length} scheduled reviews for srs:skip cards`);for(let e of n)this.user.removeScheduledCardReview(e)}}let s=this.computeBacklogMultiplier(o.length),c=r.filter(e=>!a.isAfter(hooks.utc(e.reviewTime))),l=null;if(c.length>0){let e=c.reduce((e,t)=>hooks.utc(e.reviewTime).isBefore(hooks.utc(t.reviewTime))?e:t),t=hooks.duration(hooks.utc(e.reviewTime).diff(a));l=t.asHours()<1?`${Math.round(t.asMinutes())}m`:t.asHours()<24?`${Math.round(t.asHours())}h`:`${Math.round(t.asDays())}d`}if(o.length>0){let e=s>1?` [backlog pressure: \xD7${s.toFixed(2)}]`:` [healthy backlog]`;logger.info(`[SRS] Course ${n}: ${o.length} reviews due now (of ${r.length} scheduled)${e}`)}else if(r.length>0){let e=[...r].sort((e,t)=>hooks.utc(e.reviewTime).diff(hooks.utc(t.reviewTime)))[0],t=hooks.utc(e.reviewTime),o=hooks.duration(t.diff(a)),s=o.asHours()<1?`${Math.round(o.asMinutes())}m`:o.asHours()<24?`${Math.round(o.asHours())}h`:`${Math.round(o.asDays())}d`;logger.info(`[SRS] Course ${n}: 0 reviews due now (${r.length} scheduled, next in ${s})`)}else logger.info(`[SRS] Course ${n}: No reviews scheduled`);let u=o.map(e=>{let{score:t,reason:n}=this.computeUrgencyScore(e,a,s);return{cardId:e.cardId,courseId:e.courseId,score:t,reviewID:e._id,provenance:[{strategy:`srs`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-SRS-default`,action:`generated`,score:t,reason:n}]}}).sort((e,t)=>t.score-e.score);return captureSrsBacklog({courseId:n,scheduledTotal:r.length,dueNow:o.length,healthyBacklog:this.healthyBacklog,backlogMultiplier:s,backlogGrowthRate:BACKLOG_GROWTH_RATE,topReviewScore:u.length>0?u[0].score:null,nextDueIn:l,timestamp:Date.now()}),{cards:u.slice(0,e)}}computeBacklogMultiplier(e){if(e<=this.healthyBacklog)return 1;let t=(e-this.healthyBacklog)/this.healthyBacklog;return BACKLOG_GROWTH_RATE**+t}computeUrgencyScore(e,t,n){let r=hooks.utc(e.scheduledAt),a=hooks.utc(e.reviewTime),o=Math.max(1,a.diff(r,`hours`)),s=t.diff(a,`hours`),c=s/o,l=.3+.7*Math.exp(-o/720),u=(.5+(Math.min(1,Math.max(0,c))*.5+l*.5)*.45)*n,d=[`${Math.round(s)}h overdue`,`interval: ${Math.round(o)}h`,`relative: ${c.toFixed(2)}`,`recency: ${l.toFixed(2)}`];return n>1&&d.push(`backlog: \xD7${n.toFixed(2)}`),d.push(`review`),{score:u,reason:d.join(`, `)}}}}}),types_exports={},init_types=__esm({"src/core/navigators/generators/types.ts"(){}}),globImport_generators,init_=__esm({'import("./generators/**/*") in src/core/navigators/index.ts'(){globImport_generators=__glob({"./generators/CompositeGenerator.ts":()=>Promise.resolve().then(()=>(init_CompositeGenerator(),CompositeGenerator_exports)),"./generators/elo.ts":()=>Promise.resolve().then(()=>(init_elo(),elo_exports)),"./generators/index.ts":()=>Promise.resolve().then(()=>(init_generators(),generators_exports)),"./generators/prescribed.ts":()=>Promise.resolve().then(()=>(init_prescribed(),prescribed_exports)),"./generators/srs.ts":()=>Promise.resolve().then(()=>(init_srs(),srs_exports)),"./generators/types.ts":()=>Promise.resolve().then(()=>(init_types(),types_exports))})}}),DEFAULT_LEARNABLE_WEIGHT,init_contentNavigationStrategy=__esm({"src/core/types/contentNavigationStrategy.ts"(){DEFAULT_LEARNABLE_WEIGHT={weight:1,confidence:.1,sampleSize:0}}}),WeightedFilter_exports={};__export(WeightedFilter_exports,{WeightedFilter:()=>WeightedFilter});var WeightedFilter,init_WeightedFilter=__esm({"src/core/navigators/filters/WeightedFilter.ts"(){init_contentNavigationStrategy(),WeightedFilter=class{constructor(e,t=DEFAULT_LEARNABLE_WEIGHT,n=!1,r){_defineProperty(this,`name`,void 0),_defineProperty(this,`inner`,void 0),_defineProperty(this,`learnable`,void 0),_defineProperty(this,`staticWeight`,void 0),_defineProperty(this,`strategyId`,void 0),this.inner=e,this.name=e.name,this.learnable=t,this.staticWeight=n,this.strategyId=r}async transform(e,t){let n=this.learnable.weight,r;if(!this.staticWeight&&t.orchestration){let e=this.strategyId||this.inner.strategyId||this.name;n=t.orchestration.getEffectiveWeight(e,this.learnable),r=t.orchestration.getDeviation(e)}if(Math.abs(n-1)<.001)return this.inner.transform(e,t);let a=new Map;for(let t of e)a.set(t.cardId,t.score);return(await this.inner.transform(e,t)).map(e=>{let t=a.get(e.cardId);if(t===void 0||t===0||e.score===0)return e;let o=e.score/t;if(Math.abs(o-1)<1e-4)return e;let s=t*o**+n,c=e.provenance.length-1,l=e.provenance[c];if(l){let t=[...e.provenance];return t[c]={...l,score:s,effectiveWeight:n,deviation:r},{...e,score:s,provenance:t}}return{...e,score:s}})}}}}),eloDistance_exports={};__export(eloDistance_exports,{DEFAULT_HALF_LIFE:()=>DEFAULT_HALF_LIFE,DEFAULT_MAX_MULTIPLIER:()=>DEFAULT_MAX_MULTIPLIER,DEFAULT_MIN_MULTIPLIER:()=>DEFAULT_MIN_MULTIPLIER,createEloDistanceFilter:()=>createEloDistanceFilter});function computeMultiplier(e,t,n,r){let a=e/t,o=Math.exp(-(a*a));return n+(r-n)*o}function createEloDistanceFilter(e){let t=e?.halfLife??DEFAULT_HALF_LIFE,n=e?.minMultiplier??DEFAULT_MIN_MULTIPLIER,r=e?.maxMultiplier??DEFAULT_MAX_MULTIPLIER;return{name:`ELO Distance Filter`,async transform(e,a){let{course:o,userElo:s}=a,c=e.map(e=>e.cardId),l=await o.getCardEloData(c);return e.map((e,a)=>{let o=l[a]?.global?.score??1e3,c=Math.abs(o-s),u=computeMultiplier(c,t,n,r),d=e.score*u,p=u<r-.01?`penalized`:`passed`;return{...e,score:d,provenance:[...e.provenance,{strategy:`eloDistance`,strategyName:`ELO Distance Filter`,strategyId:`ELO_DISTANCE_FILTER`,action:p,score:d,reason:`ELO distance ${Math.round(c)} (card: ${Math.round(o)}, user: ${Math.round(s)}) \u2192 ${u.toFixed(2)}x`}]}})}}}var DEFAULT_HALF_LIFE,DEFAULT_MIN_MULTIPLIER,DEFAULT_MAX_MULTIPLIER,init_eloDistance=__esm({"src/core/navigators/filters/eloDistance.ts"(){DEFAULT_HALF_LIFE=200,DEFAULT_MIN_MULTIPLIER=.3,DEFAULT_MAX_MULTIPLIER=1}}),hierarchyDefinition_exports={};__export(hierarchyDefinition_exports,{default:()=>HierarchyDefinitionNavigator});var DEFAULT_MIN_COUNT2,HierarchyDefinitionNavigator,init_hierarchyDefinition=__esm({"src/core/navigators/filters/hierarchyDefinition.ts"(){init_navigators(),init_logger(),DEFAULT_MIN_COUNT2=3,HierarchyDefinitionNavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`config`,void 0),_defineProperty(this,`name`,void 0),this.config=this.parseConfig(n.serializedData),this.name=n.name||`Hierarchy Definition`}parseConfig(e){try{return{prerequisites:JSON.parse(e).prerequisites||{}}}catch{return{prerequisites:{}}}}isPrerequisiteMet(e,t,n){if(!t)return!1;let r=e.masteryThreshold?.minCount??DEFAULT_MIN_COUNT2;return t.count<r?!1:e.masteryThreshold?.minElo===void 0?e.masteryThreshold?.minCount===void 0?t.score>=n:!0:t.score>=e.masteryThreshold.minElo}async getMasteredTags(e){let t=new Set;try{let n=toCourseElo((await e.user.getCourseRegDoc(e.course.getCourseID())).elo);for(let e of Object.values(this.config.prerequisites))for(let r of e){let e=n.tags[r.tag];this.isPrerequisiteMet(r,e,n.global.score)&&t.add(r.tag)}}catch{}return t}getUnlockedTags(e){let t=new Set;for(let[n,r]of Object.entries(this.config.prerequisites))r.every(t=>e.has(t.tag))&&t.add(n);return t}hasPrerequisites(e){return e in this.config.prerequisites}async checkCardUnlock(e,t,n,r){try{let t=e.tags??[],a=t.filter(e=>this.hasPrerequisites(e)&&!n.has(e));return a.length===0?{isUnlocked:!0,reason:`Prerequisites met, tags: ${t.length>0?t.join(`, `):`none`}`}:{isUnlocked:!1,reason:`Blocked: missing prerequisites ${a.flatMap(e=>(this.config.prerequisites[e]||[]).filter(e=>!r.has(e.tag)).map(e=>e.tag)).join(`, `)} for tags ${a.join(`, `)}`}}catch{return{isUnlocked:!0,reason:`Prerequisites check skipped (tag lookup failed)`}}}getPreReqBoosts(e,t){let n=new Map;for(let[r,a]of Object.entries(this.config.prerequisites))if(!e.has(r))for(let e of a){if(!e.preReqBoost||e.preReqBoost<=1||t.has(e.tag))continue;let r=n.get(e.tag)??1;n.set(e.tag,Math.max(r,e.preReqBoost))}return n}getTargetBoosts(e){let t=new Map,n=Object.keys(this.config.prerequisites),r=[...e];logger.info(`[HierarchyDefinition:targetBoost:trace] ${this.name} | configKeys=${n.length}, unlocked=${r.length} (${r.slice(0,5).join(`, `)}${r.length>5?`...`:``})`);for(let[n,r]of Object.entries(this.config.prerequisites))if(e.has(n)){logger.info(`[HierarchyDefinition:targetBoost:trace] UNLOCKED ${n}: ${r.length} prereqs, raw=${JSON.stringify(r.map(e=>({tag:e.tag,tb:e.targetBoost})))}`);for(let e of r){if(!e.targetBoost||e.targetBoost<=1)continue;let r=t.get(n)??1;t.set(n,Math.max(r,e.targetBoost))}}return t.size>0?logger.info(`[HierarchyDefinition] targetBoosts active: ${[...t.entries()].map(([e,t])=>`${e}=\xD7${t}`).join(`, `)}`):logger.info(`[HierarchyDefinition:targetBoost:trace] no targetBoosts found despite ${r.length} unlocked tags`),t}async transform(e,t){let n=await this.getMasteredTags(t),r=this.getUnlockedTags(n),a=this.getPreReqBoosts(r,n),o=this.getTargetBoosts(r),s=[];for(let c of e){let{isUnlocked:e,reason:l}=await this.checkCardUnlock(c,t.course,r,n),u=e?c.score:c.score*.02,d=e?`passed`:`penalized`,p=l;if(e&&a.size>0){let e=c.tags??[],t=1,n=[];for(let r of e){let e=a.get(r);e&&e>t&&(t=e,n.push(r))}t>1&&(u*=t,d=`boosted`,p=`${l} | preReqBoost \xD7${t.toFixed(2)} for ${n.join(`, `)}`,logger.info(`[HierarchyDefinition] preReqBoost \xD7${t.toFixed(2)} applied to card ${c.cardId} via tags [${n.join(`, `)}] (score: ${c.score.toFixed(3)} \u2192 ${u.toFixed(3)})`))}if(e&&o.size>0){let e=c.tags??[],t=1,n=[];for(let r of e){let e=o.get(r);e&&e>t&&(t=e,n.push(r))}t>1&&(u*=t,d=`boosted`,p=`${p} | targetBoost \xD7${t.toFixed(2)} for ${n.join(`, `)}`,logger.info(`[HierarchyDefinition] targetBoost \xD7${t.toFixed(2)} applied to card ${c.cardId} via tags [${n.join(`, `)}] (score: ${c.score.toFixed(3)} \u2192 ${u.toFixed(3)})`))}s.push({...c,score:u,provenance:[...c.provenance,{strategy:`hierarchyDefinition`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-hierarchy`,action:d,score:u,reason:p}]})}return s}async getWeightedCards(e){throw Error(`HierarchyDefinitionNavigator is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().`)}}}}),userTagPreference_exports={};__export(userTagPreference_exports,{default:()=>UserTagPreferenceFilter});var UserTagPreferenceFilter,init_userTagPreference=__esm({"src/core/navigators/filters/userTagPreference.ts"(){init_navigators(),UserTagPreferenceFilter=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`_strategyData`,void 0),_defineProperty(this,`name`,void 0),this._strategyData=n,this.name=n.name||`User Tag Preferences`}computeMultiplier(e,t){let n=e.map(e=>t[e]).filter(e=>e!==void 0);return n.length===0?1:Math.max(...n)}buildReason(e,t,n){let r=e.filter(e=>t[e]===n);return n===0?`Excluded by user preference: ${r.join(`, `)} (${n}x)`:n<1?`Penalized by user preference: ${r.join(`, `)} (${n.toFixed(2)}x)`:n>1?`Boosted by user preference: ${r.join(`, `)} (${n.toFixed(2)}x)`:`No matching user preferences`}async transform(e,t){let n=await this.getStrategyState();return!n||Object.keys(n.boost).length===0?e.map(e=>({...e,provenance:[...e.provenance,{strategy:`userTagPreference`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||this._strategyData._id,action:`passed`,score:e.score,reason:`No user tag preferences configured`}]})):await Promise.all(e.map(async e=>{let t=e.tags??[],r=this.computeMultiplier(t,n.boost),a=Math.min(1,e.score*r),o;return o=r===0||r<1?`penalized`:r>1?`boosted`:`passed`,{...e,score:a,provenance:[...e.provenance,{strategy:`userTagPreference`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||this._strategyData._id,action:o,score:a,reason:this.buildReason(t,n.boost,r)}]}}))}async getWeightedCards(e){throw Error(`UserTagPreferenceFilter is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().`)}}}}),filters_exports={};__export(filters_exports,{UserTagPreferenceFilter:()=>UserTagPreferenceFilter,createEloDistanceFilter:()=>createEloDistanceFilter});var init_filters=__esm({"src/core/navigators/filters/index.ts"(){init_eloDistance(),init_userTagPreference()}}),inferredPreferenceStub_exports={};__export(inferredPreferenceStub_exports,{INFERRED_PREFERENCE_NAVIGATOR_STUB:()=>INFERRED_PREFERENCE_NAVIGATOR_STUB});var INFERRED_PREFERENCE_NAVIGATOR_STUB,init_inferredPreferenceStub=__esm({"src/core/navigators/filters/inferredPreferenceStub.ts"(){INFERRED_PREFERENCE_NAVIGATOR_STUB=!0}}),interferenceMitigator_exports={};__export(interferenceMitigator_exports,{default:()=>InterferenceMitigatorNavigator});var DEFAULT_MIN_COUNT3,DEFAULT_MIN_ELAPSED_DAYS,DEFAULT_INTERFERENCE_DECAY,InterferenceMitigatorNavigator,init_interferenceMitigator=__esm({"src/core/navigators/filters/interferenceMitigator.ts"(){init_navigators(),DEFAULT_MIN_COUNT3=10,DEFAULT_MIN_ELAPSED_DAYS=3,DEFAULT_INTERFERENCE_DECAY=.8,InterferenceMitigatorNavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`config`,void 0),_defineProperty(this,`name`,void 0),_defineProperty(this,`interferenceMap`,void 0),this.config=this.parseConfig(n.serializedData),this.interferenceMap=this.buildInterferenceMap(),this.name=n.name||`Interference Mitigator`}parseConfig(e){try{let t=JSON.parse(e),n=t.interferenceSets||[];return n.length>0&&Array.isArray(n[0])&&(n=n.map(e=>({tags:e}))),{interferenceSets:n,maturityThreshold:{minCount:t.maturityThreshold?.minCount??DEFAULT_MIN_COUNT3,minElo:t.maturityThreshold?.minElo,minElapsedDays:t.maturityThreshold?.minElapsedDays??DEFAULT_MIN_ELAPSED_DAYS},defaultDecay:t.defaultDecay??DEFAULT_INTERFERENCE_DECAY}}catch{return{interferenceSets:[],maturityThreshold:{minCount:DEFAULT_MIN_COUNT3,minElapsedDays:DEFAULT_MIN_ELAPSED_DAYS},defaultDecay:DEFAULT_INTERFERENCE_DECAY}}}buildInterferenceMap(){let e=new Map;for(let t of this.config.interferenceSets){let n=t.decay??this.config.defaultDecay??DEFAULT_INTERFERENCE_DECAY;for(let r of t.tags){e.has(r)||e.set(r,[]);let a=e.get(r);for(let e of t.tags)if(e!==r){let t=a.find(t=>t.partner===e);t?t.decay=Math.max(t.decay,n):a.push({partner:e,decay:n})}}}return e}async getImmatureTags(e){let t=new Set;try{let n=toCourseElo((await e.user.getCourseRegDoc(e.course.getCourseID())).elo),r=this.config.maturityThreshold?.minCount??DEFAULT_MIN_COUNT3,a=this.config.maturityThreshold?.minElo,o=(this.config.maturityThreshold?.minElapsedDays??DEFAULT_MIN_ELAPSED_DAYS)*2;for(let[e,s]of Object.entries(n.tags)){if(s.count===0)continue;let n=s.count<r,c=a!==void 0&&s.score<a,l=s.count<o;(n||c||l)&&t.add(e)}}catch{}return t}getTagsToAvoid(e){let t=new Map;for(let n of e){let r=this.interferenceMap.get(n);if(r){for(let{partner:n,decay:a}of r)if(!e.has(n)){let e=t.get(n)??0;t.set(n,Math.max(e,a))}}}return t}computeInterferenceEffect(e,t,n){if(t.size===0)return{multiplier:1,interferingTags:[],reason:`No interference detected`};let r=1,a=[];for(let n of e){let e=t.get(n);e!==void 0&&(a.push(n),r*=1-e)}if(a.length===0)return{multiplier:1,interferingTags:[],reason:`No interference detected`};let o=new Set;for(let e of a)for(let t of n)this.interferenceMap.get(t)?.some(t=>t.partner===e)&&o.add(t);let s=`Interferes with immature tags ${Array.from(o).join(`, `)} (tags: ${a.join(`, `)}, multiplier: ${r.toFixed(2)})`;return{multiplier:r,interferingTags:a,reason:s}}async transform(e,t){let n=await this.getImmatureTags(t),r=this.getTagsToAvoid(n),a=[];for(let t of e){let e=t.tags??[],{multiplier:o,reason:s}=this.computeInterferenceEffect(e,r,n),c=t.score*o,l=o<1?`penalized`:o>1?`boosted`:`passed`;a.push({...t,score:c,provenance:[...t.provenance,{strategy:`interferenceMitigator`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-interference`,action:l,score:c,reason:s}]})}return a}async getWeightedCards(e){throw Error(`InterferenceMitigatorNavigator is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().`)}}}}),relativePriority_exports={};__export(relativePriority_exports,{default:()=>RelativePriorityNavigator});var DEFAULT_PRIORITY,DEFAULT_PRIORITY_INFLUENCE,DEFAULT_COMBINE_MODE,RelativePriorityNavigator,init_relativePriority=__esm({"src/core/navigators/filters/relativePriority.ts"(){init_navigators(),DEFAULT_PRIORITY=.5,DEFAULT_PRIORITY_INFLUENCE=.5,DEFAULT_COMBINE_MODE=`max`,RelativePriorityNavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`config`,void 0),_defineProperty(this,`name`,void 0),this.config=this.parseConfig(n.serializedData),this.name=n.name||`Relative Priority`}parseConfig(e){try{let t=JSON.parse(e);return{tagPriorities:t.tagPriorities||{},defaultPriority:t.defaultPriority??DEFAULT_PRIORITY,combineMode:t.combineMode??DEFAULT_COMBINE_MODE,priorityInfluence:t.priorityInfluence??DEFAULT_PRIORITY_INFLUENCE}}catch{return{tagPriorities:{},defaultPriority:DEFAULT_PRIORITY,combineMode:DEFAULT_COMBINE_MODE,priorityInfluence:DEFAULT_PRIORITY_INFLUENCE}}}getTagPriority(e){return this.config.tagPriorities[e]??this.config.defaultPriority??DEFAULT_PRIORITY}computeCardPriority(e){if(e.length===0)return this.config.defaultPriority??DEFAULT_PRIORITY;let t=e.map(e=>this.getTagPriority(e));switch(this.config.combineMode){case`max`:return Math.max(...t);case`min`:return Math.min(...t);case`average`:return t.reduce((e,t)=>e+t,0)/t.length;default:return Math.max(...t)}}computeBoostFactor(e){let t=this.config.priorityInfluence??DEFAULT_PRIORITY_INFLUENCE;return 1+(e-.5)*t}buildPriorityReason(e,t,n,r){if(e.length===0)return`No tags, neutral priority (${t.toFixed(2)})`;let a=e.slice(0,3).join(`, `),o=e.length>3?` (+${e.length-3} more)`:``;return n===1?`Neutral priority (${t.toFixed(2)}) for tags: ${a}${o}`:n>1?`High-priority tags: ${a}${o} (priority ${t.toFixed(2)} \u2192 boost ${n.toFixed(2)}x \u2192 ${r.toFixed(2)})`:`Low-priority tags: ${a}${o} (priority ${t.toFixed(2)} \u2192 reduce ${n.toFixed(2)}x \u2192 ${r.toFixed(2)})`}async transform(e,t){return await Promise.all(e.map(async e=>{let t=e.tags??[],n=this.computeCardPriority(t),r=this.computeBoostFactor(n),a=Math.max(0,e.score*r),o=r>1?`boosted`:r<1?`penalized`:`passed`,s=this.buildPriorityReason(t,n,r,a);return{...e,score:a,provenance:[...e.provenance,{strategy:`relativePriority`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-priority`,action:o,score:a,reason:s}]}}))}async getWeightedCards(e){throw Error(`RelativePriorityNavigator is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().`)}}}}),types_exports2={},init_types2=__esm({"src/core/navigators/filters/types.ts"(){}}),userGoalStub_exports={};__export(userGoalStub_exports,{USER_GOAL_NAVIGATOR_STUB:()=>USER_GOAL_NAVIGATOR_STUB});var USER_GOAL_NAVIGATOR_STUB,init_userGoalStub=__esm({"src/core/navigators/filters/userGoalStub.ts"(){USER_GOAL_NAVIGATOR_STUB=!0}}),globImport_filters,init_2=__esm({'import("./filters/**/*") in src/core/navigators/index.ts'(){globImport_filters=__glob({"./filters/WeightedFilter.ts":()=>Promise.resolve().then(()=>(init_WeightedFilter(),WeightedFilter_exports)),"./filters/eloDistance.ts":()=>Promise.resolve().then(()=>(init_eloDistance(),eloDistance_exports)),"./filters/hierarchyDefinition.ts":()=>Promise.resolve().then(()=>(init_hierarchyDefinition(),hierarchyDefinition_exports)),"./filters/index.ts":()=>Promise.resolve().then(()=>(init_filters(),filters_exports)),"./filters/inferredPreferenceStub.ts":()=>Promise.resolve().then(()=>(init_inferredPreferenceStub(),inferredPreferenceStub_exports)),"./filters/interferenceMitigator.ts":()=>Promise.resolve().then(()=>(init_interferenceMitigator(),interferenceMitigator_exports)),"./filters/relativePriority.ts":()=>Promise.resolve().then(()=>(init_relativePriority(),relativePriority_exports)),"./filters/types.ts":()=>Promise.resolve().then(()=>(init_types2(),types_exports2)),"./filters/userGoalStub.ts":()=>Promise.resolve().then(()=>(init_userGoalStub(),userGoalStub_exports)),"./filters/userTagPreference.ts":()=>Promise.resolve().then(()=>(init_userTagPreference(),userTagPreference_exports))})}});function aggregateOutcomesForGradient(e,t){let n=[];for(let r of e){let e=r.deviations[t];e!==void 0&&n.push({deviation:e,outcomeValue:r.outcomeValue,weight:1})}return logger.debug(`[Orchestration] Aggregated ${n.length} observations for strategy ${t}`),n}function computeStrategyGradient(e){let t=e.length;if(t<3)return logger.debug(`[Orchestration] Insufficient observations for gradient (${t} < 3)`),null;let n=0,r=0,a=0;for(let t of e){let e=t.weight??1;n+=t.deviation*e,r+=t.outcomeValue*e,a+=e}let o=n/a,s=r/a,c=0,l=0,u=0;for(let t of e){let e=t.weight??1,n=t.deviation-o,r=t.outcomeValue-s;c+=e*n*r,l+=e*n*n,u+=e*r*r}if(l<1e-10)return logger.debug(`[Orchestration] No variance in deviations, cannot compute gradient`),{gradient:0,intercept:s,rSquared:0,sampleSize:t};let d=c/l,p=s-d*o,m=0;for(let t of e){let e=t.weight??1,n=d*t.deviation+p,r=t.outcomeValue-n;m+=e*r*r}let g=u>1e-10?1-m/u:0;return logger.debug(`[Orchestration] Computed gradient: ${d.toFixed(4)}, intercept: ${p.toFixed(4)}, R\xB2: ${g.toFixed(4)}, n=${t}`),{gradient:d,intercept:p,rSquared:Math.max(0,Math.min(1,g)),sampleSize:t}}var init_gradient=__esm({"src/core/orchestration/gradient.ts"(){init_logger()}});function updateStrategyWeight(e,t){if(t.sampleSize<MIN_OBSERVATIONS_FOR_UPDATE)return logger.debug(`[Orchestration] Insufficient samples (${t.sampleSize} < ${MIN_OBSERVATIONS_FOR_UPDATE}), keeping current weight`),{...e,sampleSize:e.sampleSize+t.sampleSize};let n=t.rSquared>=MIN_R_SQUARED_FOR_GRADIENT,r=Math.abs(t.gradient)<FLAT_GRADIENT_THRESHOLD,a=e.weight,o=e.confidence;if(!n||r){let n=.05*(1-e.confidence);o=Math.min(1,e.confidence+n),logger.debug(`[Orchestration] Flat/unreliable gradient (|g|=${Math.abs(t.gradient).toFixed(4)}, R\xB2=${t.rSquared.toFixed(4)}). Increasing confidence: ${e.confidence.toFixed(3)} \u2192 ${o.toFixed(3)}`)}else{let n=t.gradient*LEARNING_RATE;n=Math.max(-MAX_WEIGHT_DELTA,Math.min(MAX_WEIGHT_DELTA,n)),a=e.weight+n,a=Math.max(.1,Math.min(3,a));let r=.02*(1-e.confidence);o=Math.min(1,e.confidence+r),logger.debug(`[Orchestration] Adjusting weight: ${e.weight.toFixed(3)} \u2192 ${a.toFixed(3)} (gradient=${t.gradient.toFixed(4)}, delta=${n.toFixed(4)})`)}return{weight:a,confidence:o,sampleSize:e.sampleSize+t.sampleSize}}function updateLearningState(e,t,n,r,a){let o=new Date().toISOString(),s=`STRATEGY_LEARNING_STATE::${e}::${t}`,c={timestamp:o,weight:n.weight,confidence:n.confidence,gradient:r.gradient},l=a?.history??[];return l=[...l,c],l.length>MAX_HISTORY_LENGTH&&(l=l.slice(l.length-MAX_HISTORY_LENGTH)),{_id:s,_rev:a?._rev,docType:`STRATEGY_LEARNING_STATE`,courseId:e,strategyId:t,currentWeight:n,regression:{gradient:r.gradient,intercept:r.intercept,rSquared:r.rSquared,sampleSize:r.sampleSize,computedAt:o},history:l,updatedAt:o}}function runPeriodUpdate(e){let{courseId:t,strategyId:n,currentWeight:r,gradient:a,existingState:o}=e;logger.info(`[Orchestration] Running period update for strategy ${n} (${a.sampleSize} observations)`);let s=updateStrategyWeight(r,a),c=s.weight!==r.weight,l=updateLearningState(t,n,s,a,o);return logger.info(`[Orchestration] Period update complete for ${n}: weight ${r.weight.toFixed(3)} \u2192 ${s.weight.toFixed(3)}, confidence ${r.confidence.toFixed(3)} \u2192 ${s.confidence.toFixed(3)}`),{strategyId:n,previousWeight:r,newWeight:s,gradient:a,learningState:l,updated:c}}function getDefaultLearnableWeight(){return{...DEFAULT_LEARNABLE_WEIGHT}}var MIN_OBSERVATIONS_FOR_UPDATE,LEARNING_RATE,MAX_WEIGHT_DELTA,MIN_R_SQUARED_FOR_GRADIENT,FLAT_GRADIENT_THRESHOLD,MAX_HISTORY_LENGTH,init_learning=__esm({"src/core/orchestration/learning.ts"(){init_contentNavigationStrategy(),init_types_legacy(),init_logger(),MIN_OBSERVATIONS_FOR_UPDATE=10,LEARNING_RATE=.1,MAX_WEIGHT_DELTA=.3,MIN_R_SQUARED_FOR_GRADIENT=.05,FLAT_GRADIENT_THRESHOLD=.02,MAX_HISTORY_LENGTH=100}});function computeOutcomeSignal(e,t={}){if(!e||e.length===0)return null;let n=t.targetAccuracy??.85,r=t.tolerance??.05,a=0;for(let t of e)t.isCorrect&&a++;return scoreAccuracyInZone(a/e.length,n,r)}function scoreAccuracyInZone(e,t,n){let r=Math.abs(e-t);if(r<=n)return 1;let a=r-n;return Math.max(0,1-a*2.5)}var init_signal=__esm({"src/core/orchestration/signal.ts"(){}});async function recordUserOutcome(e,t,n,r,a,o=0,s=0,c){let{user:l,course:u,userId:d}=e,p=u.getCourseID(),m=computeOutcomeSignal(r,c);if(m===null){logger.debug(`[Orchestration] No outcome signal computed for ${d} (insufficient data). Skipping record.`);return}let g={};for(let t of a)g[t]=e.getDeviation(t);let _=`USER_OUTCOME::${p}::${d}::${n}`,v={_id:_,docType:`USER_OUTCOME`,courseId:p,userId:d,periodStart:t,periodEnd:n,outcomeValue:m,deviations:g,metadata:{sessionsCount:1,cardsSeen:r.length,eloStart:o,eloEnd:s,signalType:`accuracy_in_zone`}};try{await l.putUserOutcome(v),logger.debug(`[Orchestration] Recorded outcome ${m.toFixed(3)} for ${d} (doc: ${_})`)}catch(e){logger.error(`[Orchestration] Failed to record outcome: ${e}`)}}var init_recording=__esm({"src/core/orchestration/recording.ts"(){init_signal(),init_types_legacy(),init_logger()}});function fnv1a(e){let t=2166136261;for(let n=0;n<e.length;n++)t^=e.charCodeAt(n),t=Math.imul(t,16777619);return t>>>0}function computeDeviation(e,t,n){return fnv1a(`${e}:${t}:${n}`)/4294967296*2-1}function computeSpread(e){return MAX_SPREAD-Math.max(0,Math.min(1,e))*(MAX_SPREAD-MIN_SPREAD)}function computeEffectiveWeight(e,t,n,r){let a=computeDeviation(t,n,r)*computeSpread(e.confidence)*e.weight,o=e.weight+a;return Math.max(MIN_WEIGHT,Math.min(MAX_WEIGHT,o))}async function createOrchestrationContext(e,t){let n;try{n=await t.getCourseConfig()}catch(e){logger.error(`[Orchestration] Failed to load course config: ${e}`),n={name:`Unknown`,description:``,public:!1,deleted:!1,creator:``,admins:[],moderators:[],dataShapes:[],questionTypes:[],orchestration:{salt:`default`}}}let r=e.getUsername(),a=n.orchestration?.salt||`default_salt`;return{user:e,course:t,userId:r,courseConfig:n,getEffectiveWeight(e,t){return computeEffectiveWeight(t,r,e,a)},getDeviation(e){return computeDeviation(r,e,a)}}}var MIN_SPREAD,MAX_SPREAD,MIN_WEIGHT,MAX_WEIGHT,init_orchestration=__esm({"src/core/orchestration/index.ts"(){init_logger(),init_gradient(),init_learning(),init_signal(),init_recording(),MIN_SPREAD=.1,MAX_SPREAD=.5,MIN_WEIGHT=.1,MAX_WEIGHT=3}}),Pipeline_exports={};__export(Pipeline_exports,{Pipeline:()=>Pipeline,mergeHints:()=>mergeHints2});function globToRegex(e){let t=e.replace(/[.+?^${}()|[\]\\]/g,`\\$&`).replace(/\*/g,`.*`);return RegExp(`^${t}$`)}function globMatch(e,t){return t.includes(`*`)?globToRegex(t).test(e):e===t}function cardMatchesTagPattern(e,t){return(e.tags??[]).some(e=>globMatch(e,t))}function mergeHints2(e){let t=e.filter(e=>e!=null);if(t.length===0)return;let n={},r={};for(let e of t)for(let[t,n]of Object.entries(e.boostTags??{}))r[t]=(r[t]??1)*n;Object.keys(r).length>0&&(n.boostTags=r);let a={};for(let e of t)for(let[t,n]of Object.entries(e.boostCards??{}))a[t]=(a[t]??1)*n;Object.keys(a).length>0&&(n.boostCards=a);let concatUnique=e=>{let r=t.flatMap(t=>t[e]??[]);r.length>0&&(n[e]=[...new Set(r)])};concatUnique(`requireTags`),concatUnique(`requireCards`),concatUnique(`excludeTags`),concatUnique(`excludeCards`);let o=t.map(e=>e._label).filter(Boolean);return o.length>0&&(n._label=o.join(`; `)),Object.keys(n).length>0?n:void 0}function logPipelineConfig(e,t){let n=t.length>0?`
250
250
  - `+t.map(e=>e.name).join(`
251
251
  - `):` none`;logger.info(`[Pipeline] Configuration:
252
252
  Generator: ${e.name}
253
253
  Filters:${n}`)}function logTagHydration(e,t){let n=Array.from(t.values()).reduce((e,t)=>e+t.length,0),r=Array.from(t.values()).filter(e=>e.length>0).length;logger.debug(`[Pipeline] Tag hydration: ${e.length} cards, ${r} have tags (${n} total tags) - single batch query`)}function logExecutionSummary(e,t,n,r,a,o){let s=a.length>0?a.map(e=>e.toFixed(2)).join(`, `):`none`,c=``;o.length>0&&(c=`
254
254
  Filter impact: ${o.map(e=>{let t=[];return e.boosted>0&&t.push(`+${e.boosted}`),e.penalized>0&&t.push(`-${e.penalized}`),e.passed>0&&t.push(`=${e.passed}`),`${e.name}: ${t.join(`/`)}`}).join(`, `)}`),logger.info(`[Pipeline] Execution: ${e} produced ${t} \u2192 ${n} filters \u2192 ${r} results (top scores: ${s})`+c+`
255
- 💡 Inspect: window.skuilder.pipeline`)}function logResultCards(e){if(!(!VERBOSE_RESULTS||e.length===0)){logger.info(`[Pipeline] Results (${e.length} cards):`);for(let t=0;t<e.length;t++){let n=e[t],r=n.tags?.slice(0,3).join(`, `)||``,a=n.provenance.filter(e=>e.strategy===`hierarchyDefinition`||e.strategy===`priorityDefinition`||e.strategy===`interferenceFilter`||e.strategy===`letterGating`||e.strategy===`ephemeralHint`||e.strategy===`diversityRerank`).map(e=>{let t=e.action===`boosted`?`↑`:e.action===`penalized`?`↓`:`=`;return`${e.strategyName}${t}${e.score.toFixed(2)}`}).join(` | `);logger.info(`[Pipeline] ${String(t+1).padStart(2)}. ${n.score.toFixed(4)} ${n.cardId} [${r}]${a?` {${a}}`:``}`)}}}function logCardProvenance(e,t=3){let n=e.slice(0,t);logger.debug(`[Pipeline] Provenance for top ${n.length} cards:`);for(let e of n){logger.debug(`[Pipeline] ${e.cardId} (final score: ${e.score.toFixed(3)}):`);for(let t of e.provenance){let e=t.score.toFixed(3),n=t.action.padEnd(9);logger.debug(`[Pipeline] ${n} ${e} - ${t.strategyName}: ${t.reason}`)}}}var VERBOSE_RESULTS,Pipeline,init_Pipeline=__esm({"src/core/navigators/Pipeline.ts"(){init_navigators(),init_logger(),init_orchestration(),init_PipelineDebugger(),init_diversityRerank(),VERBOSE_RESULTS=!0,Pipeline=class extends ContentNavigator{constructor(e,t,n,r){super(),_defineProperty(this,`generator`,void 0),_defineProperty(this,`filters`,void 0),_defineProperty(this,`_cachedOrchestration`,null),_defineProperty(this,`_tagCache`,new Map),_defineProperty(this,`_ephemeralHints`,null),this.generator=e,this.filters=t,this.user=n,this.course=r,r.getCourseConfig().then(e=>{logger.debug(`[pipeline] Crated pipeline for ${e.name}`)}).catch(e=>{logger.error(`[pipeline] Failed to lookup courseCfg: ${e}`)}),logPipelineConfig(e,t),registerPipelineForDebug(this)}setEphemeralHints(e){this._ephemeralHints=e,logger.info(`[Pipeline] Ephemeral hints set: ${JSON.stringify(e)}`)}async getWeightedCards(e){let t=performance.now(),n=await this.buildContext(),r=performance.now(),a=500;logger.debug(`[Pipeline] Fetching 500 candidates from generator '${this.generator.name}'`);let o=await this.generator.getWeightedCards(500,n),s=o.cards,c=performance.now(),l=s.length;this._ephemeralHints=mergeHints2([this._ephemeralHints,o.hints])??null;let u;if(this.generator.generators){let e=new Map;for(let t of s){let n=t.provenance[0];if(n){let r=n.strategyName;e.has(r)||e.set(r,{cards:[]}),e.get(r).cards.push(t)}}u=Array.from(e.entries()).map(([e,t])=>{let n=t.cards.filter(e=>e.provenance[0]?.reason?.includes(`new card`)),r=t.cards.filter(e=>e.provenance[0]?.reason?.includes(`review`));return{name:e,cardCount:t.cards.length,newCount:n.length,reviewCount:r.length,topScore:Math.max(...t.cards.map(e=>e.score),0)}})}logger.debug(`[Pipeline] Generator returned ${l} candidates`),s=await this.hydrateTags(s);let d=performance.now(),p=[...s],m=this._ephemeralHints;if(m?.requireCards?.length){let e=new Set(p.map(e=>e.cardId)),t=m.requireCards.filter(t=>!t.includes(`*`)&&!e.has(t));if(t.length>0){let e=await this.course.getAppliedTagsBatch(t),n=this.course.getCourseID();for(let r of t)p.push({cardId:r,courseId:n,score:1,tags:e.get(r)??[],provenance:[]});logger.info(`[Pipeline] Pre-fetched ${t.length} required card(s) into pool: ${t.join(`, `)}`)}}let g=new Set(s.filter(e=>e.provenance.some(e=>e.strategy===`prescribed`)).map(e=>e.cardId)),_=[];for(let e of this.filters){let t=s.length,r=new Map(s.map(e=>[e.cardId,e.score]));s=await e.transform(s,n);let a=0,o=0,c=0,l=t-s.length;for(let e of s){let t=r.get(e.cardId)??0;e.score>t?a++:e.score<t?o++:c++}if(_.push({name:e.name,boosted:a,penalized:o,passed:c,removed:l}),g.size>0){let t=new Set(s.map(e=>e.cardId)),n=[...g].filter(e=>!t.has(e)),r=s.filter(e=>g.has(e.cardId)&&e.score===0).map(e=>e.cardId);(n.length>0||r.length>0)&&(logger.info(`[Pipeline] Filter '${e.name}' impact on prescribed cards: `+(n.length>0?`removed=[${n.join(`, `)}] `:``)+(r.length>0?`zeroed=[${r.join(`, `)}]`:``)),n.forEach(e=>g.delete(e)))}logger.debug(`[Pipeline] Filter '${e.name}': ${r.size} \u2192 ${s.length} cards (\u2191${a} \u2193${o} =${c})`)}s=s.filter(e=>e.score>0);let v=this._ephemeralHints;v&&(this._ephemeralHints=null,s=this.applyHints(s,v,p)),s=diversityRerank(s),s.sort((e,t)=>t.score-e.score);let y=performance.now(),b=s.slice(0,e);logger.info(`[Pipeline:timing] total=${(y-t).toFixed(0)}ms (context=${(r-t).toFixed(0)} generate=${(c-r).toFixed(0)} hydrate=${(d-c).toFixed(0)} filter=${(y-d).toFixed(0)})`);let x=b.slice(0,3).map(e=>e.score);logExecutionSummary(this.generator.name,l,this.filters.length,b.length,x,_),logResultCards(b),logCardProvenance(b,3);try{let e=await this.course?.getCourseConfig().then(e=>e.name).catch(()=>void 0);captureRun(buildRunReport(this.course?.getCourseID()||`unknown`,e,this.generator.name,u,l,_,s,b,n.userElo,v??void 0))}catch(e){logger.debug(`[Pipeline] Failed to capture debug run: ${e}`)}return{cards:b}}async hydrateTags(e){if(e.length===0)return e;let t=[];for(let n of e)this._tagCache.has(n.cardId)||t.push(n.cardId);if(t.length>0){let e=await this.course.getAppliedTagsBatch(t);for(let[t,n]of e)this._tagCache.set(t,n)}let n=new Map;for(let t of e)n.set(t.cardId,this._tagCache.get(t.cardId)??[]);return logTagHydration(e,n),e.map(e=>({...e,tags:this._tagCache.get(e.cardId)??[]}))}applyHints(e,t,n){let r=e.length;if(t.excludeCards?.length&&(e=e.filter(e=>!t.excludeCards.some(t=>globMatch(e.cardId,t)))),t.excludeTags?.length&&(e=e.filter(e=>!t.excludeTags.some(t=>cardMatchesTagPattern(e,t)))),t.boostTags)for(let[n,r]of Object.entries(t.boostTags))for(let a of e)cardMatchesTagPattern(a,n)&&(a.score*=r,a.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:t._label?`Replan Hint (${t._label})`:`Replan Hint`,action:`boosted`,score:a.score,reason:`boostTag ${n} \xD7${r}`}));if(t.boostCards)for(let[n,r]of Object.entries(t.boostCards))for(let a of e)globMatch(a.cardId,n)&&(a.score*=r,a.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:t._label?`Replan Hint (${t._label})`:`Replan Hint`,action:`boosted`,score:a.score,reason:`boostCard ${n} \xD7${r}`}));let a=new Set(e.map(e=>e.cardId)),o=new Map(e.map(e=>[e.cardId,e])),s=t._label?`Replan Hint (${t._label})`:`Replan Hint`,applyRequirement=(t,n)=>{let r=1/0,c=o.get(t.cardId);c?c.score<r&&(c.score=r,c.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:s,action:`boosted`,score:r,reason:`${n} (upgrade to mandatory score)`})):(e.push({...t,score:r,provenance:[...t.provenance,{strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:s,action:`boosted`,score:r,reason:n}]}),a.add(t.cardId),o.set(t.cardId,e[e.length-1]))};if(t.requireCards?.length)for(let e of t.requireCards){for(let t of a)globMatch(t,e)&&applyRequirement(o.get(t),`requireCard ${e}`);for(let t of n)globMatch(t.cardId,e)&&applyRequirement(t,`requireCard ${e}`)}if(t.requireTags?.length)for(let e of t.requireTags){for(let t of a){let n=o.get(t);cardMatchesTagPattern(n,e)&&applyRequirement(n,`requireTag ${e}`)}for(let t of n)cardMatchesTagPattern(t,e)&&applyRequirement(t,`requireTag ${e}`)}return logger.info(`[Pipeline] Hints applied: ${r} \u2192 ${e.length} cards`),e}async buildContext(){let e=1e3;try{e=toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo).global.score}catch(e){logger.debug(`[Pipeline] Could not get user ELO, using default: ${e}`)}this._cachedOrchestration||(this._cachedOrchestration=await createOrchestrationContext(this.user,this.course));let t=this._cachedOrchestration;return{user:this.user,course:this.course,userElo:e,orchestration:t}}getCourseID(){return this.course.getCourseID()}async getOrchestrationContext(){return createOrchestrationContext(this.user,this.course)}getStrategyIds(){let e=[],extractId=e=>e.strategyId?e.strategyId:null,t=extractId(this.generator);t&&e.push(t),this.generator.generators&&Array.isArray(this.generator.generators)&&this.generator.generators.forEach(t=>{let n=extractId(t);n&&e.push(n)});for(let t of this.filters){let n=extractId(t);n&&e.push(n)}return[...new Set(e)]}async getTagEloStatus(e){let t=toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo),n={};if(e){let r=Array.isArray(e)?e:[e];for(let e of r){let r=globToRegex(e);for(let[e,a]of Object.entries(t.tags))r.test(e)&&(n[e]={score:a.score,count:a.count})}}else for(let[e,r]of Object.entries(t.tags))n[e]={score:r.score,count:r.count};return n}async forecast(e){let t=e?.threshold??.1,n=e?.unseenOnly??!0,r=this.course.getCourseID(),a=(await this.course.getAllCardIds()).map(e=>({cardId:e,courseId:r,score:1,provenance:[]}));a=await this.hydrateTags(a);let o=a.slice(),s=await this.buildContext();for(let e of this.filters)a=await e.transform(a,s);if(e?.hints&&(a=this.applyHints(a,e.hints,o)),a=a.filter(e=>e.score>=t),n){let e;try{e=new Set(await this.user.getSeenCards(r))}catch{e=new Set}a=a.filter(t=>!e.has(t.cardId))}return a.sort((e,t)=>t.score-e.score),e?.limit?a.slice(0,e.limit):a}async diagnoseCardSpace(e){let t=e?.threshold??.1,n=performance.now(),r=await this.course.getAllCardIds(),a=r.map(e=>({cardId:e,courseId:this.course.getCourseID(),score:1,provenance:[]}));a=await this.hydrateTags(a);let o=await this.buildContext(),s=[];for(let e of this.filters){a=await e.transform(a,o);let n=a.filter(e=>e.score>=t).length;s.push({name:e.name,wellIndicated:n})}let c=a.filter(e=>e.score>=t),l=new Set(c.map(e=>e.cardId)),u;try{let e=this.course.getCourseID(),t=await this.user.getSeenCards(e);u=new Set(t)}catch{u=new Set}let d=c.filter(e=>!u.has(e.cardId)),p=new Map;for(let e of a){let n=e.cardId.split(`-`)[1]||`unknown`;p.has(n)||p.set(n,{total:0,wellIndicated:0,new:0});let r=p.get(n);r.total++,e.score>=t&&(r.wellIndicated++,u.has(e.cardId)||r.new++)}let m=performance.now()-n,g={totalCards:r.length,threshold:t,wellIndicated:l.size,encountered:u.size,wellIndicatedNew:d.length,byType:Object.fromEntries(p),filterBreakdown:s,elapsedMs:Math.round(m)};logger.info(`[Pipeline:diagnose] Card space scan (${g.elapsedMs}ms):`),logger.info(`[Pipeline:diagnose] Total cards: ${g.totalCards}`),logger.info(`[Pipeline:diagnose] Well-indicated (score >= ${t}): ${g.wellIndicated}`),logger.info(`[Pipeline:diagnose] Encountered: ${g.encountered}`),logger.info(`[Pipeline:diagnose] Well-indicated & new: ${g.wellIndicatedNew}`),logger.info(`[Pipeline:diagnose] By type:`);for(let[e,t]of p)logger.info(`[Pipeline:diagnose] ${e}: ${t.wellIndicated}/${t.total} well-indicated, ${t.new} new`);logger.info(`[Pipeline:diagnose] After each filter:`);for(let e of s)logger.info(`[Pipeline:diagnose] ${e.name}: ${e.wellIndicated} well-indicated`);return g}}}}),defaults_exports={};__export(defaults_exports,{createDefaultEloStrategy:()=>createDefaultEloStrategy,createDefaultPipeline:()=>createDefaultPipeline,createDefaultSrsStrategy:()=>createDefaultSrsStrategy});function createDefaultEloStrategy(e){return{_id:`NAVIGATION_STRATEGY-ELO-default`,docType:`NAVIGATION_STRATEGY`,name:`ELO (default)`,description:`Default ELO-based navigation strategy for new cards`,implementingClass:`elo`,course:e,serializedData:``}}function createDefaultSrsStrategy(e){return{_id:`NAVIGATION_STRATEGY-SRS-default`,docType:`NAVIGATION_STRATEGY`,name:`SRS (default)`,description:`Default SRS-based navigation strategy for reviews`,implementingClass:`srs`,course:e,serializedData:``}}function createDefaultPipeline(e,t){let n=t.getCourseID(),r=new ELONavigator(e,t,createDefaultEloStrategy(n)),a=new SRSNavigator(e,t,createDefaultSrsStrategy(n)),o=new CompositeGenerator([r,a]),s=createEloDistanceFilter();return new Pipeline(o,[s],e,t)}var init_defaults=__esm({"src/core/navigators/defaults.ts"(){init_navigators(),init_Pipeline(),init_CompositeGenerator(),init_elo(),init_srs(),init_eloDistance(),init_types_legacy()}}),PipelineAssembler_exports={};__export(PipelineAssembler_exports,{PipelineAssembler:()=>PipelineAssembler});var PipelineAssembler,init_PipelineAssembler=__esm({"src/core/navigators/PipelineAssembler.ts"(){init_navigators(),init_WeightedFilter(),init_Pipeline(),init_logger(),init_CompositeGenerator(),init_defaults(),PipelineAssembler=class{async assemble(e){let{strategies:t,user:n,course:r}=e,a=[];if(t.length===0)return{pipeline:null,generatorStrategies:[],filterStrategies:[],warnings:a};let o=[],s=[];for(let e of t)isGenerator(e.implementingClass)?o.push(e):isFilter(e.implementingClass)?s.push(e):a.push(`Unknown strategy type '${e.implementingClass}', skipping: ${e.name}`);let c=r.getCourseID(),l=o.some(e=>e.implementingClass===`elo`),u=o.some(e=>e.implementingClass===`srs`);if(l||(logger.debug(`[PipelineAssembler] No ELO generator configured, adding default`),o.push(createDefaultEloStrategy(c))),u||(logger.debug(`[PipelineAssembler] No SRS generator configured, adding default`),o.push(createDefaultSrsStrategy(c))),o.length===0)return a.push(`No generator strategy found`),{pipeline:null,generatorStrategies:[],filterStrategies:[],warnings:a};let d;o.length===1?(d=await ContentNavigator.create(n,r,o[0]),logger.debug(`[PipelineAssembler] Using single generator: ${o[0].name}`)):(logger.debug(`[PipelineAssembler] Using CompositeGenerator for ${o.length} generators: ${o.map(e=>e.name).join(`, `)}`),d=await CompositeGenerator.fromStrategies(n,r,o));let p=[],m=[...s].sort((e,t)=>e.name.localeCompare(t.name));for(let e of m)try{let t=await ContentNavigator.create(n,r,e);if(`transform`in t&&typeof t.transform==`function`){let n=t;e.learnable&&(n=new WeightedFilter(n,e.learnable,e.staticWeight,e._id)),p.push(n),logger.debug(`[PipelineAssembler] Added filter: ${e.name}`)}else a.push(`Filter '${e.name}' does not implement CardFilter.transform(), skipping`)}catch(t){a.push(`Failed to instantiate filter '${e.name}': ${t}`)}let g=new Pipeline(d,p,n,r);return logger.debug(`[PipelineAssembler] Assembled pipeline with ${o.length} generator(s) and ${p.length} filter(s)`),{pipeline:g,generatorStrategies:o,filterStrategies:m,warnings:a}}}}}),globImport,init_3=__esm({'import("./**/*") in src/core/navigators/index.ts'(){globImport=__glob({"./Pipeline.ts":()=>Promise.resolve().then(()=>(init_Pipeline(),Pipeline_exports)),"./PipelineAssembler.ts":()=>Promise.resolve().then(()=>(init_PipelineAssembler(),PipelineAssembler_exports)),"./PipelineDebugger.ts":()=>Promise.resolve().then(()=>(init_PipelineDebugger(),PipelineDebugger_exports)),"./SrsDebugger.ts":()=>Promise.resolve().then(()=>(init_SrsDebugger(),SrsDebugger_exports)),"./defaults.ts":()=>Promise.resolve().then(()=>(init_defaults(),defaults_exports)),"./diversityRerank.ts":()=>Promise.resolve().then(()=>(init_diversityRerank(),diversityRerank_exports)),"./filters/WeightedFilter.ts":()=>Promise.resolve().then(()=>(init_WeightedFilter(),WeightedFilter_exports)),"./filters/eloDistance.ts":()=>Promise.resolve().then(()=>(init_eloDistance(),eloDistance_exports)),"./filters/hierarchyDefinition.ts":()=>Promise.resolve().then(()=>(init_hierarchyDefinition(),hierarchyDefinition_exports)),"./filters/index.ts":()=>Promise.resolve().then(()=>(init_filters(),filters_exports)),"./filters/inferredPreferenceStub.ts":()=>Promise.resolve().then(()=>(init_inferredPreferenceStub(),inferredPreferenceStub_exports)),"./filters/interferenceMitigator.ts":()=>Promise.resolve().then(()=>(init_interferenceMitigator(),interferenceMitigator_exports)),"./filters/relativePriority.ts":()=>Promise.resolve().then(()=>(init_relativePriority(),relativePriority_exports)),"./filters/types.ts":()=>Promise.resolve().then(()=>(init_types2(),types_exports2)),"./filters/userGoalStub.ts":()=>Promise.resolve().then(()=>(init_userGoalStub(),userGoalStub_exports)),"./filters/userTagPreference.ts":()=>Promise.resolve().then(()=>(init_userTagPreference(),userTagPreference_exports)),"./generators/CompositeGenerator.ts":()=>Promise.resolve().then(()=>(init_CompositeGenerator(),CompositeGenerator_exports)),"./generators/elo.ts":()=>Promise.resolve().then(()=>(init_elo(),elo_exports)),"./generators/index.ts":()=>Promise.resolve().then(()=>(init_generators(),generators_exports)),"./generators/prescribed.ts":()=>Promise.resolve().then(()=>(init_prescribed(),prescribed_exports)),"./generators/srs.ts":()=>Promise.resolve().then(()=>(init_srs(),srs_exports)),"./generators/types.ts":()=>Promise.resolve().then(()=>(init_types(),types_exports)),"./index.ts":()=>Promise.resolve().then(()=>(init_navigators(),navigators_exports))})}}),navigators_exports={};__export(navigators_exports,{ContentNavigator:()=>ContentNavigator,DIVERSITY_FLOOR:()=>DIVERSITY_FLOOR,DIVERSITY_STRENGTH:()=>DIVERSITY_STRENGTH,NavigatorRole:()=>NavigatorRole,NavigatorRoles:()=>NavigatorRoles,Navigators:()=>Navigators,clearSrsBacklogDebug:()=>clearSrsBacklogDebug,diversityRerank:()=>diversityRerank,getActivePipeline:()=>getActivePipeline,getCardOrigin:()=>getCardOrigin,getRegisteredNavigator:()=>getRegisteredNavigator,getRegisteredNavigatorNames:()=>getRegisteredNavigatorNames,getRegisteredNavigatorRole:()=>getRegisteredNavigatorRole,getSrsBacklogDebug:()=>getSrsBacklogDebug,hasRegisteredNavigator:()=>hasRegisteredNavigator,initializeNavigatorRegistry:()=>initializeNavigatorRegistry,isFilter:()=>isFilter,isGenerator:()=>isGenerator,mountPipelineDebugger:()=>mountPipelineDebugger,pipelineDebugAPI:()=>pipelineDebugAPI,registerNavigator:()=>registerNavigator});function registerNavigator(e,t,n){navigatorRegistry.set(e,{constructor:t,role:n}),logger.debug(`[NavigatorRegistry] Registered: ${e}${n?` (${n})`:``}`)}function getRegisteredNavigator(e){return navigatorRegistry.get(e)?.constructor}function hasRegisteredNavigator(e){return navigatorRegistry.has(e)}function getRegisteredNavigatorRole(e){return navigatorRegistry.get(e)?.role}function getRegisteredNavigatorNames(){return Array.from(navigatorRegistry.keys())}async function initializeNavigatorRegistry(){logger.debug(`[NavigatorRegistry] Initializing built-in navigators...`);let[e,t]=await Promise.all([Promise.resolve().then(()=>(init_elo(),elo_exports)),Promise.resolve().then(()=>(init_srs(),srs_exports))]),n=await Promise.resolve().then(()=>(init_prescribed(),prescribed_exports));registerNavigator(`elo`,e.default),registerNavigator(`srs`,t.default),registerNavigator(`prescribed`,n.default);let[r,a,o,s]=await Promise.all([Promise.resolve().then(()=>(init_hierarchyDefinition(),hierarchyDefinition_exports)),Promise.resolve().then(()=>(init_interferenceMitigator(),interferenceMitigator_exports)),Promise.resolve().then(()=>(init_relativePriority(),relativePriority_exports)),Promise.resolve().then(()=>(init_userTagPreference(),userTagPreference_exports))]);registerNavigator(`hierarchyDefinition`,r.default),registerNavigator(`interferenceMitigator`,a.default),registerNavigator(`relativePriority`,o.default),registerNavigator(`userTagPreference`,s.default),logger.debug(`[NavigatorRegistry] Initialized ${navigatorRegistry.size} navigators: ${getRegisteredNavigatorNames().join(`, `)}`)}function getCardOrigin(e){if(e.provenance.length===0)throw Error(`Card has no provenance - cannot determine origin`);let t=e.provenance[0].reason.toLowerCase();return t.includes(`failed`)?`failed`:t.includes(`review`)?`review`:`new`}function isGenerator(e){return NavigatorRoles[e]===`generator`?!0:getRegisteredNavigatorRole(e)===`generator`}function isFilter(e){return NavigatorRoles[e]===`filter`?!0:getRegisteredNavigatorRole(e)===`filter`}var navigatorRegistry,Navigators,NavigatorRole,NavigatorRoles,ContentNavigator,init_navigators=__esm({"src/core/navigators/index.ts"(){init_diversityRerank(),init_PipelineDebugger(),init_SrsDebugger(),init_logger(),init_(),init_2(),init_3(),navigatorRegistry=new Map,Navigators=(e=>(e.ELO=`elo`,e.SRS=`srs`,e.PRESCRIBED=`prescribed`,e.HIERARCHY=`hierarchyDefinition`,e.INTERFERENCE=`interferenceMitigator`,e.RELATIVE_PRIORITY=`relativePriority`,e.USER_TAG_PREFERENCE=`userTagPreference`,e))(Navigators||{}),NavigatorRole=(e=>(e.GENERATOR=`generator`,e.FILTER=`filter`,e))(NavigatorRole||{}),NavigatorRoles={elo:`generator`,srs:`generator`,prescribed:`generator`,hierarchyDefinition:`filter`,interferenceMitigator:`filter`,relativePriority:`filter`,userTagPreference:`filter`},ContentNavigator=class{constructor(e,t,n){_defineProperty(this,`user`,void 0),_defineProperty(this,`course`,void 0),_defineProperty(this,`strategyName`,void 0),_defineProperty(this,`strategyId`,void 0),_defineProperty(this,`learnable`,void 0),_defineProperty(this,`staticWeight`,void 0),this.user=e,this.course=t,n&&(this.strategyName=n.name,this.strategyId=n._id,this.learnable=n.learnable,this.staticWeight=n.staticWeight)}get strategyKey(){return this.constructor.name}async getStrategyState(){if(!this.user||!this.course)throw Error(`Cannot get strategy state: navigator not properly initialized. Ensure user and course are provided to constructor.`);return this.user.getStrategyState(this.course.getCourseID(),this.strategyKey)}async putStrategyState(e){if(!this.user||!this.course)throw Error(`Cannot put strategy state: navigator not properly initialized. Ensure user and course are provided to constructor.`);return this.user.putStrategyState(this.course.getCourseID(),this.strategyKey,e)}static async create(e,t,n){let r=n.implementingClass,a=getRegisteredNavigator(r);if(a)return logger.debug(`[ContentNavigator.create] Using registered navigator: ${r}`),new a(e,t,n);logger.debug(`[ContentNavigator.create] Navigator not in registry, attempting dynamic import: ${r}`);let o;for(let e of[`.ts`,`.js`,``]){try{if(o=(await globImport_generators(`./generators/${r}${e}`)).default,o)break}catch(t){logger.debug(`Failed to load generator ${r}${e}:`,t)}try{if(o=(await globImport_filters(`./filters/${r}${e}`)).default,o)break}catch(t){logger.debug(`Failed to load filter ${r}${e}:`,t)}try{if(o=(await globImport(`./${r}${e}`)).default,o)break}catch(t){logger.debug(`Failed to load legacy ${r}${e}:`,t)}if(o)break}if(!o)throw Error(`Could not load navigator implementation for: ${r}`);return new o(e,t,n)}async getWeightedCards(e){throw Error(`${this.constructor.name} must implement getWeightedCards(). `)}setEphemeralHints(e){}}}});function randIntWeightedTowardZero(e){return Math.floor(Math.random()*Math.random()*Math.random()*e)}async function getCourseTagStubs(e){logger.debug(`Getting tag stubs for course: ${e}`);let t=await filterAllDocsByPrefix2(getCourseDB2(e),`TAG`.valueOf()+`-`);return t.rows.forEach(e=>{logger.debug(` Tag stub for doc: ${e.id}`)}),t}async function createTag(e,t,n){logger.debug(`Creating tag: ${t}...`);let r=getTagID(t);return await getCourseDB2(e).put({course:e,docType:`TAG`,name:t,snippet:``,taggedCards:[],wiki:``,author:n,_id:r})}async function updateTag(e){let t=await getTag(e.course,e.name);return await getCourseDB2(e.course).put({...e,_rev:t._rev})}async function getTag(e,t){let n=getTagID(t);return getCourseDB2(e).get(n)}async function removeTagFromCard(e,t,n){n=getTagID(n);let r=getCourseDB2(e),a=await r.get(n);return a.taggedCards=a.taggedCards.filter(e=>t!==e),r.put(a)}async function getAppliedTags(e,t){return await getCourseDB2(e).query(`getTags`,{startkey:t,endkey:t})}async function updateCredentialledCourseConfig(e,t){logger.debug(`Updating course config:
255
+ 💡 Inspect: window.skuilder.pipeline`)}function logResultCards(e){if(!(!VERBOSE_RESULTS||e.length===0)){logger.info(`[Pipeline] Results (${e.length} cards):`);for(let t=0;t<e.length;t++){let n=e[t],r=n.tags?.slice(0,3).join(`, `)||``,a=n.provenance.filter(e=>e.strategy===`hierarchyDefinition`||e.strategy===`priorityDefinition`||e.strategy===`interferenceFilter`||e.strategy===`letterGating`||e.strategy===`ephemeralHint`||e.strategy===`diversityRerank`).map(e=>{let t=e.action===`boosted`?`↑`:e.action===`penalized`?`↓`:`=`;return`${e.strategyName}${t}${e.score.toFixed(2)}`}).join(` | `);logger.info(`[Pipeline] ${String(t+1).padStart(2)}. ${n.score.toFixed(4)} ${n.cardId} [${r}]${a?` {${a}}`:``}`)}}}function logCardProvenance(e,t=3){let n=e.slice(0,t);logger.debug(`[Pipeline] Provenance for top ${n.length} cards:`);for(let e of n){logger.debug(`[Pipeline] ${e.cardId} (final score: ${e.score.toFixed(3)}):`);for(let t of e.provenance){let e=t.score.toFixed(3),n=t.action.padEnd(9);logger.debug(`[Pipeline] ${n} ${e} - ${t.strategyName}: ${t.reason}`)}}}var VERBOSE_RESULTS,Pipeline,init_Pipeline=__esm({"src/core/navigators/Pipeline.ts"(){init_navigators(),init_logger(),init_orchestration(),init_PipelineDebugger(),init_diversityRerank(),VERBOSE_RESULTS=!0,Pipeline=class extends ContentNavigator{constructor(e,t,n,r){super(),_defineProperty(this,`generator`,void 0),_defineProperty(this,`filters`,void 0),_defineProperty(this,`_cachedOrchestration`,null),_defineProperty(this,`_tagCache`,new Map),_defineProperty(this,`_ephemeralHints`,null),this.generator=e,this.filters=t,this.user=n,this.course=r,r.getCourseConfig().then(e=>{logger.debug(`[pipeline] Crated pipeline for ${e.name}`)}).catch(e=>{logger.error(`[pipeline] Failed to lookup courseCfg: ${e}`)}),logPipelineConfig(e,t),registerPipelineForDebug(this)}setEphemeralHints(e){this._ephemeralHints=e,logger.info(`[Pipeline] Ephemeral hints set: ${JSON.stringify(e)}`)}async getWeightedCards(e){let t=performance.now(),n=await this.buildContext(),r=performance.now(),a=500;logger.debug(`[Pipeline] Fetching 500 candidates from generator '${this.generator.name}'`);let o=await this.generator.getWeightedCards(500,n),s=o.cards,c=performance.now(),l=s.length;this._ephemeralHints=mergeHints2([this._ephemeralHints,o.hints])??null;let u;if(this.generator.generators){let e=new Map;for(let t of s){let n=t.provenance[0];if(n){let r=n.strategyName;e.has(r)||e.set(r,{cards:[]}),e.get(r).cards.push(t)}}u=Array.from(e.entries()).map(([e,t])=>{let n=t.cards.filter(e=>e.provenance[0]?.reason?.includes(`new card`)),r=t.cards.filter(e=>e.provenance[0]?.reason?.includes(`review`));return{name:e,cardCount:t.cards.length,newCount:n.length,reviewCount:r.length,topScore:Math.max(...t.cards.map(e=>e.score),0)}})}logger.debug(`[Pipeline] Generator returned ${l} candidates`),s=await this.hydrateTags(s);let d=performance.now(),p=[...s],m=this._ephemeralHints;if(m?.requireCards?.length){let e=new Set(p.map(e=>e.cardId)),t=m.requireCards.filter(t=>!t.includes(`*`)&&!e.has(t));if(t.length>0){let e=await this.course.getAppliedTagsBatch(t),n=this.course.getCourseID();for(let r of t)p.push({cardId:r,courseId:n,score:1,tags:e.get(r)??[],provenance:[]});logger.info(`[Pipeline] Pre-fetched ${t.length} required card(s) into pool: ${t.join(`, `)}`)}}let g=new Set(s.filter(e=>e.provenance.some(e=>e.strategy===`prescribed`)).map(e=>e.cardId)),_=[];for(let e of this.filters){let t=s.length,r=new Map(s.map(e=>[e.cardId,e.score]));s=await e.transform(s,n);let a=0,o=0,c=0,l=t-s.length;for(let e of s){let t=r.get(e.cardId)??0;e.score>t?a++:e.score<t?o++:c++}if(_.push({name:e.name,boosted:a,penalized:o,passed:c,removed:l}),g.size>0){let t=new Set(s.map(e=>e.cardId)),n=[...g].filter(e=>!t.has(e)),r=s.filter(e=>g.has(e.cardId)&&e.score===0).map(e=>e.cardId);(n.length>0||r.length>0)&&(logger.info(`[Pipeline] Filter '${e.name}' impact on prescribed cards: `+(n.length>0?`removed=[${n.join(`, `)}] `:``)+(r.length>0?`zeroed=[${r.join(`, `)}]`:``)),n.forEach(e=>g.delete(e)))}logger.debug(`[Pipeline] Filter '${e.name}': ${r.size} \u2192 ${s.length} cards (\u2191${a} \u2193${o} =${c})`)}s=s.filter(e=>e.score>0);let v=this._ephemeralHints;v&&(this._ephemeralHints=null,s=this.applyHints(s,v,p)),s=diversityRerank(s),s.sort((e,t)=>t.score-e.score);let y=performance.now(),b=s.slice(0,e);logger.info(`[Pipeline:timing] total=${(y-t).toFixed(0)}ms (context=${(r-t).toFixed(0)} generate=${(c-r).toFixed(0)} hydrate=${(d-c).toFixed(0)} filter=${(y-d).toFixed(0)})`);let x=b.slice(0,3).map(e=>e.score);logExecutionSummary(this.generator.name,l,this.filters.length,b.length,x,_),logResultCards(b),logCardProvenance(b,3);try{let e=await this.course?.getCourseConfig().then(e=>e.name).catch(()=>void 0);captureRun(buildRunReport(this.course?.getCourseID()||`unknown`,e,this.generator.name,u,l,_,s,b,n.userElo,v??void 0))}catch(e){logger.debug(`[Pipeline] Failed to capture debug run: ${e}`)}return{cards:b}}async hydrateTags(e){if(e.length===0)return e;let t=[];for(let n of e)this._tagCache.has(n.cardId)||t.push(n.cardId);if(t.length>0){let e=await this.course.getAppliedTagsBatch(t);for(let[t,n]of e)this._tagCache.set(t,n)}let n=new Map;for(let t of e)n.set(t.cardId,this._tagCache.get(t.cardId)??[]);return logTagHydration(e,n),e.map(e=>({...e,tags:this._tagCache.get(e.cardId)??[]}))}applyHints(e,t,n){let r=e.length;if(t.excludeCards?.length&&(e=e.filter(e=>!t.excludeCards.some(t=>globMatch(e.cardId,t)))),t.excludeTags?.length&&(e=e.filter(e=>!t.excludeTags.some(t=>cardMatchesTagPattern(e,t)))),t.boostTags)for(let[n,r]of Object.entries(t.boostTags))for(let a of e)cardMatchesTagPattern(a,n)&&(a.score*=r,a.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:t._label?`Replan Hint (${t._label})`:`Replan Hint`,action:`boosted`,score:a.score,reason:`boostTag ${n} \xD7${r}`}));if(t.boostCards)for(let[n,r]of Object.entries(t.boostCards))for(let a of e)globMatch(a.cardId,n)&&(a.score*=r,a.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:t._label?`Replan Hint (${t._label})`:`Replan Hint`,action:`boosted`,score:a.score,reason:`boostCard ${n} \xD7${r}`}));let a=new Set(e.map(e=>e.cardId)),o=new Map(e.map(e=>[e.cardId,e])),s=t._label?`Replan Hint (${t._label})`:`Replan Hint`,applyRequirement=(t,n)=>{let r=1/0,c=o.get(t.cardId);c?c.score<r&&(c.score=r,c.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:s,action:`boosted`,score:r,reason:`${n} (upgrade to mandatory score)`})):(e.push({...t,score:r,provenance:[...t.provenance,{strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:s,action:`boosted`,score:r,reason:n}]}),a.add(t.cardId),o.set(t.cardId,e[e.length-1]))};if(t.requireCards?.length)for(let e of t.requireCards){for(let t of a)globMatch(t,e)&&applyRequirement(o.get(t),`requireCard ${e}`);for(let t of n)globMatch(t.cardId,e)&&applyRequirement(t,`requireCard ${e}`)}if(t.requireTags?.length)for(let e of t.requireTags){for(let t of a){let n=o.get(t);cardMatchesTagPattern(n,e)&&applyRequirement(n,`requireTag ${e}`)}for(let t of n)cardMatchesTagPattern(t,e)&&applyRequirement(t,`requireTag ${e}`)}return logger.info(`[Pipeline] Hints applied: ${r} \u2192 ${e.length} cards`),e}async buildContext(){let e=1e3;try{e=toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo).global.score}catch(e){logger.debug(`[Pipeline] Could not get user ELO, using default: ${e}`)}this._cachedOrchestration||(this._cachedOrchestration=await createOrchestrationContext(this.user,this.course));let t=this._cachedOrchestration;return{user:this.user,course:this.course,userElo:e,orchestration:t}}getCourseID(){return this.course.getCourseID()}async getOrchestrationContext(){return createOrchestrationContext(this.user,this.course)}getStrategyIds(){let e=[],extractId=e=>e.strategyId?e.strategyId:null,t=extractId(this.generator);t&&e.push(t),this.generator.generators&&Array.isArray(this.generator.generators)&&this.generator.generators.forEach(t=>{let n=extractId(t);n&&e.push(n)});for(let t of this.filters){let n=extractId(t);n&&e.push(n)}return[...new Set(e)]}async getTagEloStatus(e){let t=toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo),n={};if(e){let r=Array.isArray(e)?e:[e];for(let e of r){let r=globToRegex(e);for(let[e,a]of Object.entries(t.tags))r.test(e)&&(n[e]={score:a.score,count:a.count})}}else for(let[e,r]of Object.entries(t.tags))n[e]={score:r.score,count:r.count};return n}async forecast(e){let t=e?.threshold??.1,n=e?.unseenOnly??!0,r=this.course.getCourseID(),a=(await this.course.getAllCardIds()).map(e=>({cardId:e,courseId:r,score:1,provenance:[]}));a=await this.hydrateTags(a);let o=a.slice(),s=await this.buildContext();for(let e of this.filters)a=await e.transform(a,s);if(e?.hints&&(a=this.applyHints(a,e.hints,o)),a=a.filter(e=>e.score>=t),n){let e;try{e=new Set(await this.user.getSeenCards(r))}catch{e=new Set}a=a.filter(t=>!e.has(t.cardId))}return a.sort((e,t)=>t.score-e.score),e?.limit?a.slice(0,e.limit):a}async diagnoseCardSpace(e){let t=e?.threshold??.1,n=performance.now(),r=await this.course.getAllCardIds(),a=r.map(e=>({cardId:e,courseId:this.course.getCourseID(),score:1,provenance:[]}));a=await this.hydrateTags(a);let o=await this.buildContext(),s=[];for(let e of this.filters){a=await e.transform(a,o);let n=a.filter(e=>e.score>=t).length;s.push({name:e.name,wellIndicated:n})}let c=a.filter(e=>e.score>=t),l=new Set(c.map(e=>e.cardId)),u;try{let e=this.course.getCourseID(),t=await this.user.getSeenCards(e);u=new Set(t)}catch{u=new Set}let d=c.filter(e=>!u.has(e.cardId)),p=new Map;for(let e of a){let n=e.cardId.split(`-`)[1]||`unknown`;p.has(n)||p.set(n,{total:0,wellIndicated:0,new:0});let r=p.get(n);r.total++,e.score>=t&&(r.wellIndicated++,u.has(e.cardId)||r.new++)}let m=performance.now()-n,g={totalCards:r.length,threshold:t,wellIndicated:l.size,encountered:u.size,wellIndicatedNew:d.length,byType:Object.fromEntries(p),filterBreakdown:s,elapsedMs:Math.round(m)};logger.info(`[Pipeline:diagnose] Card space scan (${g.elapsedMs}ms):`),logger.info(`[Pipeline:diagnose] Total cards: ${g.totalCards}`),logger.info(`[Pipeline:diagnose] Well-indicated (score >= ${t}): ${g.wellIndicated}`),logger.info(`[Pipeline:diagnose] Encountered: ${g.encountered}`),logger.info(`[Pipeline:diagnose] Well-indicated & new: ${g.wellIndicatedNew}`),logger.info(`[Pipeline:diagnose] By type:`);for(let[e,t]of p)logger.info(`[Pipeline:diagnose] ${e}: ${t.wellIndicated}/${t.total} well-indicated, ${t.new} new`);logger.info(`[Pipeline:diagnose] After each filter:`);for(let e of s)logger.info(`[Pipeline:diagnose] ${e.name}: ${e.wellIndicated} well-indicated`);return g}}}}),defaults_exports={};__export(defaults_exports,{createDefaultEloStrategy:()=>createDefaultEloStrategy,createDefaultPipeline:()=>createDefaultPipeline,createDefaultSrsStrategy:()=>createDefaultSrsStrategy});function createDefaultEloStrategy(e){return{_id:`NAVIGATION_STRATEGY-ELO-default`,docType:`NAVIGATION_STRATEGY`,name:`ELO (default)`,description:`Default ELO-based navigation strategy for new cards`,implementingClass:`elo`,course:e,serializedData:``}}function createDefaultSrsStrategy(e){return{_id:`NAVIGATION_STRATEGY-SRS-default`,docType:`NAVIGATION_STRATEGY`,name:`SRS (default)`,description:`Default SRS-based navigation strategy for reviews`,implementingClass:`srs`,course:e,serializedData:``}}function createDefaultPipeline(e,t){let n=t.getCourseID(),r=new ELONavigator(e,t,createDefaultEloStrategy(n)),a=new SRSNavigator(e,t,createDefaultSrsStrategy(n)),o=new CompositeGenerator([r,a]),s=createEloDistanceFilter();return new Pipeline(o,[s],e,t)}var init_defaults=__esm({"src/core/navigators/defaults.ts"(){init_navigators(),init_Pipeline(),init_CompositeGenerator(),init_elo(),init_srs(),init_eloDistance(),init_types_legacy()}}),PipelineAssembler_exports={};__export(PipelineAssembler_exports,{PipelineAssembler:()=>PipelineAssembler});var PipelineAssembler,init_PipelineAssembler=__esm({"src/core/navigators/PipelineAssembler.ts"(){init_navigators(),init_WeightedFilter(),init_Pipeline(),init_logger(),init_CompositeGenerator(),init_defaults(),PipelineAssembler=class{async assemble(e){let{strategies:t,user:n,course:r}=e,a=[];if(t.length===0)return{pipeline:null,generatorStrategies:[],filterStrategies:[],warnings:a};let o=[],s=[];for(let e of t)isGenerator(e.implementingClass)?o.push(e):isFilter(e.implementingClass)?s.push(e):a.push(`Unknown strategy type '${e.implementingClass}', skipping: ${e.name}`);let c=r.getCourseID(),l=o.some(e=>e.implementingClass===`elo`),u=o.some(e=>e.implementingClass===`srs`);if(l||(logger.debug(`[PipelineAssembler] No ELO generator configured, adding default`),o.push(createDefaultEloStrategy(c))),u||(logger.debug(`[PipelineAssembler] No SRS generator configured, adding default`),o.push(createDefaultSrsStrategy(c))),o.length===0)return a.push(`No generator strategy found`),{pipeline:null,generatorStrategies:[],filterStrategies:[],warnings:a};let d;o.length===1?(d=await ContentNavigator.create(n,r,o[0]),logger.debug(`[PipelineAssembler] Using single generator: ${o[0].name}`)):(logger.debug(`[PipelineAssembler] Using CompositeGenerator for ${o.length} generators: ${o.map(e=>e.name).join(`, `)}`),d=await CompositeGenerator.fromStrategies(n,r,o));let p=[],m=[...s].sort((e,t)=>e.name.localeCompare(t.name));for(let e of m)try{let t=await ContentNavigator.create(n,r,e);if(`transform`in t&&typeof t.transform==`function`){let n=t;e.learnable&&(n=new WeightedFilter(n,e.learnable,e.staticWeight,e._id)),p.push(n),logger.debug(`[PipelineAssembler] Added filter: ${e.name}`)}else a.push(`Filter '${e.name}' does not implement CardFilter.transform(), skipping`)}catch(t){a.push(`Failed to instantiate filter '${e.name}': ${t}`)}let g=new Pipeline(d,p,n,r);return logger.debug(`[PipelineAssembler] Assembled pipeline with ${o.length} generator(s) and ${p.length} filter(s)`),{pipeline:g,generatorStrategies:o,filterStrategies:m,warnings:a}}}}}),globImport,init_3=__esm({'import("./**/*") in src/core/navigators/index.ts'(){globImport=__glob({"./Pipeline.ts":()=>Promise.resolve().then(()=>(init_Pipeline(),Pipeline_exports)),"./PipelineAssembler.ts":()=>Promise.resolve().then(()=>(init_PipelineAssembler(),PipelineAssembler_exports)),"./PipelineDebugger.ts":()=>Promise.resolve().then(()=>(init_PipelineDebugger(),PipelineDebugger_exports)),"./SrsDebugger.ts":()=>Promise.resolve().then(()=>(init_SrsDebugger(),SrsDebugger_exports)),"./StrategyPressureDebugger.ts":()=>Promise.resolve().then(()=>(init_StrategyPressureDebugger(),StrategyPressureDebugger_exports)),"./defaults.ts":()=>Promise.resolve().then(()=>(init_defaults(),defaults_exports)),"./diversityRerank.ts":()=>Promise.resolve().then(()=>(init_diversityRerank(),diversityRerank_exports)),"./filters/WeightedFilter.ts":()=>Promise.resolve().then(()=>(init_WeightedFilter(),WeightedFilter_exports)),"./filters/eloDistance.ts":()=>Promise.resolve().then(()=>(init_eloDistance(),eloDistance_exports)),"./filters/hierarchyDefinition.ts":()=>Promise.resolve().then(()=>(init_hierarchyDefinition(),hierarchyDefinition_exports)),"./filters/index.ts":()=>Promise.resolve().then(()=>(init_filters(),filters_exports)),"./filters/inferredPreferenceStub.ts":()=>Promise.resolve().then(()=>(init_inferredPreferenceStub(),inferredPreferenceStub_exports)),"./filters/interferenceMitigator.ts":()=>Promise.resolve().then(()=>(init_interferenceMitigator(),interferenceMitigator_exports)),"./filters/relativePriority.ts":()=>Promise.resolve().then(()=>(init_relativePriority(),relativePriority_exports)),"./filters/types.ts":()=>Promise.resolve().then(()=>(init_types2(),types_exports2)),"./filters/userGoalStub.ts":()=>Promise.resolve().then(()=>(init_userGoalStub(),userGoalStub_exports)),"./filters/userTagPreference.ts":()=>Promise.resolve().then(()=>(init_userTagPreference(),userTagPreference_exports)),"./generators/CompositeGenerator.ts":()=>Promise.resolve().then(()=>(init_CompositeGenerator(),CompositeGenerator_exports)),"./generators/elo.ts":()=>Promise.resolve().then(()=>(init_elo(),elo_exports)),"./generators/index.ts":()=>Promise.resolve().then(()=>(init_generators(),generators_exports)),"./generators/prescribed.ts":()=>Promise.resolve().then(()=>(init_prescribed(),prescribed_exports)),"./generators/srs.ts":()=>Promise.resolve().then(()=>(init_srs(),srs_exports)),"./generators/types.ts":()=>Promise.resolve().then(()=>(init_types(),types_exports)),"./index.ts":()=>Promise.resolve().then(()=>(init_navigators(),navigators_exports))})}}),navigators_exports={};__export(navigators_exports,{ContentNavigator:()=>ContentNavigator,DIVERSITY_FLOOR:()=>DIVERSITY_FLOOR,DIVERSITY_STRENGTH:()=>DIVERSITY_STRENGTH,NavigatorRole:()=>NavigatorRole,NavigatorRoles:()=>NavigatorRoles,Navigators:()=>Navigators,captureStrategyPressure:()=>captureStrategyPressure,clearSrsBacklogDebug:()=>clearSrsBacklogDebug,clearStrategyPressureDebug:()=>clearStrategyPressureDebug,diversityRerank:()=>diversityRerank,getActivePipeline:()=>getActivePipeline,getCardOrigin:()=>getCardOrigin,getRegisteredNavigator:()=>getRegisteredNavigator,getRegisteredNavigatorNames:()=>getRegisteredNavigatorNames,getRegisteredNavigatorRole:()=>getRegisteredNavigatorRole,getSrsBacklogDebug:()=>getSrsBacklogDebug,getStrategyPressureDebug:()=>getStrategyPressureDebug,hasRegisteredNavigator:()=>hasRegisteredNavigator,initializeNavigatorRegistry:()=>initializeNavigatorRegistry,isFilter:()=>isFilter,isGenerator:()=>isGenerator,mountPipelineDebugger:()=>mountPipelineDebugger,pipelineDebugAPI:()=>pipelineDebugAPI,registerNavigator:()=>registerNavigator});function registerNavigator(e,t,n){navigatorRegistry.set(e,{constructor:t,role:n}),logger.debug(`[NavigatorRegistry] Registered: ${e}${n?` (${n})`:``}`)}function getRegisteredNavigator(e){return navigatorRegistry.get(e)?.constructor}function hasRegisteredNavigator(e){return navigatorRegistry.has(e)}function getRegisteredNavigatorRole(e){return navigatorRegistry.get(e)?.role}function getRegisteredNavigatorNames(){return Array.from(navigatorRegistry.keys())}async function initializeNavigatorRegistry(){logger.debug(`[NavigatorRegistry] Initializing built-in navigators...`);let[e,t]=await Promise.all([Promise.resolve().then(()=>(init_elo(),elo_exports)),Promise.resolve().then(()=>(init_srs(),srs_exports))]),n=await Promise.resolve().then(()=>(init_prescribed(),prescribed_exports));registerNavigator(`elo`,e.default),registerNavigator(`srs`,t.default),registerNavigator(`prescribed`,n.default);let[r,a,o,s]=await Promise.all([Promise.resolve().then(()=>(init_hierarchyDefinition(),hierarchyDefinition_exports)),Promise.resolve().then(()=>(init_interferenceMitigator(),interferenceMitigator_exports)),Promise.resolve().then(()=>(init_relativePriority(),relativePriority_exports)),Promise.resolve().then(()=>(init_userTagPreference(),userTagPreference_exports))]);registerNavigator(`hierarchyDefinition`,r.default),registerNavigator(`interferenceMitigator`,a.default),registerNavigator(`relativePriority`,o.default),registerNavigator(`userTagPreference`,s.default),logger.debug(`[NavigatorRegistry] Initialized ${navigatorRegistry.size} navigators: ${getRegisteredNavigatorNames().join(`, `)}`)}function getCardOrigin(e){if(e.provenance.length===0)throw Error(`Card has no provenance - cannot determine origin`);let t=e.provenance[0].reason.toLowerCase();return t.includes(`failed`)?`failed`:t.includes(`review`)?`review`:`new`}function isGenerator(e){return NavigatorRoles[e]===`generator`?!0:getRegisteredNavigatorRole(e)===`generator`}function isFilter(e){return NavigatorRoles[e]===`filter`?!0:getRegisteredNavigatorRole(e)===`filter`}var navigatorRegistry,Navigators,NavigatorRole,NavigatorRoles,ContentNavigator,init_navigators=__esm({"src/core/navigators/index.ts"(){init_diversityRerank(),init_PipelineDebugger(),init_SrsDebugger(),init_StrategyPressureDebugger(),init_logger(),init_(),init_2(),init_3(),navigatorRegistry=new Map,Navigators=(e=>(e.ELO=`elo`,e.SRS=`srs`,e.PRESCRIBED=`prescribed`,e.HIERARCHY=`hierarchyDefinition`,e.INTERFERENCE=`interferenceMitigator`,e.RELATIVE_PRIORITY=`relativePriority`,e.USER_TAG_PREFERENCE=`userTagPreference`,e))(Navigators||{}),NavigatorRole=(e=>(e.GENERATOR=`generator`,e.FILTER=`filter`,e))(NavigatorRole||{}),NavigatorRoles={elo:`generator`,srs:`generator`,prescribed:`generator`,hierarchyDefinition:`filter`,interferenceMitigator:`filter`,relativePriority:`filter`,userTagPreference:`filter`},ContentNavigator=class{constructor(e,t,n){_defineProperty(this,`user`,void 0),_defineProperty(this,`course`,void 0),_defineProperty(this,`strategyName`,void 0),_defineProperty(this,`strategyId`,void 0),_defineProperty(this,`learnable`,void 0),_defineProperty(this,`staticWeight`,void 0),this.user=e,this.course=t,n&&(this.strategyName=n.name,this.strategyId=n._id,this.learnable=n.learnable,this.staticWeight=n.staticWeight)}get strategyKey(){return this.constructor.name}async getStrategyState(){if(!this.user||!this.course)throw Error(`Cannot get strategy state: navigator not properly initialized. Ensure user and course are provided to constructor.`);return this.user.getStrategyState(this.course.getCourseID(),this.strategyKey)}async putStrategyState(e){if(!this.user||!this.course)throw Error(`Cannot put strategy state: navigator not properly initialized. Ensure user and course are provided to constructor.`);return this.user.putStrategyState(this.course.getCourseID(),this.strategyKey,e)}static async create(e,t,n){let r=n.implementingClass,a=getRegisteredNavigator(r);if(a)return logger.debug(`[ContentNavigator.create] Using registered navigator: ${r}`),new a(e,t,n);logger.debug(`[ContentNavigator.create] Navigator not in registry, attempting dynamic import: ${r}`);let o;for(let e of[`.ts`,`.js`,``]){try{if(o=(await globImport_generators(`./generators/${r}${e}`)).default,o)break}catch(t){logger.debug(`Failed to load generator ${r}${e}:`,t)}try{if(o=(await globImport_filters(`./filters/${r}${e}`)).default,o)break}catch(t){logger.debug(`Failed to load filter ${r}${e}:`,t)}try{if(o=(await globImport(`./${r}${e}`)).default,o)break}catch(t){logger.debug(`Failed to load legacy ${r}${e}:`,t)}if(o)break}if(!o)throw Error(`Could not load navigator implementation for: ${r}`);return new o(e,t,n)}async getWeightedCards(e){throw Error(`${this.constructor.name} must implement getWeightedCards(). `)}setEphemeralHints(e){}}}});function randIntWeightedTowardZero(e){return Math.floor(Math.random()*Math.random()*Math.random()*e)}async function getCourseTagStubs(e){logger.debug(`Getting tag stubs for course: ${e}`);let t=await filterAllDocsByPrefix2(getCourseDB2(e),`TAG`.valueOf()+`-`);return t.rows.forEach(e=>{logger.debug(` Tag stub for doc: ${e.id}`)}),t}async function createTag(e,t,n){logger.debug(`Creating tag: ${t}...`);let r=getTagID(t);return await getCourseDB2(e).put({course:e,docType:`TAG`,name:t,snippet:``,taggedCards:[],wiki:``,author:n,_id:r})}async function updateTag(e){let t=await getTag(e.course,e.name);return await getCourseDB2(e.course).put({...e,_rev:t._rev})}async function getTag(e,t){let n=getTagID(t);return getCourseDB2(e).get(n)}async function removeTagFromCard(e,t,n){n=getTagID(n);let r=getCourseDB2(e),a=await r.get(n);return a.taggedCards=a.taggedCards.filter(e=>t!==e),r.put(a)}async function getAppliedTags(e,t){return await getCourseDB2(e).query(`getTags`,{startkey:t,endkey:t})}async function updateCredentialledCourseConfig(e,t){logger.debug(`Updating course config:
256
256
 
257
257
  ${JSON.stringify(t)}
258
258
  `);let n=getCourseDB2(e),r=await getCredentialledCourseConfig(e);return await n.put({...t,_rev:r._rev})}function isSuccessRow(e){return`doc`in e&&e.doc!==null&&e.doc!==void 0}var CoursesDB,CourseDB,init_courseDB=__esm({"src/impl/couch/courseDB.ts"(){init_couch(),init_updateQueue(),init_types_legacy(),init_logger(),init_clientCache(),init_courseAPI(),init_courseLookupDB(),init_navigators(),init_PipelineAssembler(),init_defaults(),CoursesDB=class{constructor(e){_defineProperty(this,`_courseIDs`,void 0),e&&e.length>0?this._courseIDs=e:this._courseIDs=void 0}async getCourseList(){let e=await CourseLookup.allCourseWare();return logger.debug(`AllCourses: ${e.map(e=>e.name+`, `+e._id+`
@@ -268,17 +268,17 @@ ${o.rows.map(e=>` ${e.id}-${e.key}
268
268
  `+l),c}async getCourseConfig(){let e=await getCredentialledCourseConfig(this.id);if(e)return e;throw Error(`Course config not found for course ID: ${this.id}`)}async updateCourseConfig(e){logger.debug(`Updating: ${JSON.stringify(e)}`);try{return await updateCredentialledCourseConfig(this.id,e)}catch(e){throw logger.error(`Error updating course config in course DB: ${e}`),e}}async updateCardElo(e,t){if(!t)throw Error(`Cannot update card elo with null or undefined value for card ID: ${e}`);try{return{ok:!0,id:e,rev:(await this.updateQueue.update(e,e=>(logger.debug(`Replacing ${JSON.stringify(e.elo)} with ${JSON.stringify(t)}`),e.elo=t,e)))._rev}}catch(t){throw logger.error(`Failed to update card elo for card ID: ${e}`,t),Error(`Failed to update card elo for card ID: ${e}`)}}async getAppliedTags(e){let t=await getAppliedTags(this.id,e);if(t)return t;throw Error(`Failed to find tags for card ${this.id}-${e}`)}async getAppliedTagsBatch(e){if(e.length===0)return new Map;let t=await this.db.query(`getTags`,{keys:e,include_docs:!1}),n=new Map;for(let t of e)n.set(t,[]);for(let e of t.rows){let t=e.key,r=e.value?.name;r&&n.has(t)&&n.get(t).push(r)}return n}async getAllCardIds(){let e=`${DocTypePrefixes.CARD}-`;return(await this.db.allDocs({startkey:e,endkey:`${e}\uFFF0`,include_docs:!1})).rows.map(e=>e.id)}async addTagToCard(e,t,n){return await addTagToCard(this.id,e,t,(await this._getCurrentUser()).getUsername(),n)}async removeTagFromCard(e,t){return await removeTagFromCard(this.id,e,t)}async createTag(e,t){return await createTag(this.id,e,t)}async getTag(e){return await getTag(this.id,e)}async updateTag(e){if(e.course!==this.id)throw Error(`Tag ${JSON.stringify(e)} does not belong to course ${this.id}`);return await updateTag(e)}async getCourseTagStubs(){return getCourseTagStubs(this.id)}async addNote(e,t,n,r,a,o,s=blankCourseElo()){try{let c=await addNote55(this.id,e,t,n,r,a,o,s);return c.ok?c.cardCreationFailed?(logger.warn(`[courseDB.addNote] Note added but card creation failed: ${c.cardCreationError}`),{status:Status.error,message:`Note was added but no cards were created: ${c.cardCreationError}`,id:c.id}):{status:Status.ok,message:``,id:c.id}:{status:Status.error,message:`Unexpected error adding note`}}catch(e){let t=e;return logger.error(`[addNote] error ${t.name}
269
269
  reason: ${t.reason}
270
270
  message: ${t.message}`),{status:Status.error,message:`Error adding note to course. ${e.reason||t.message}`}}}async getCourseDoc(e,t){return await this.db.get(e,t??{})}async getCourseDocs(e,t={}){return await this.db.allDocs({...t,keys:e})}getNavigationStrategy(e){if(logger.debug(`[courseDB] Getting navigation strategy: ${e}`),e==``){let e={_id:`NAVIGATION_STRATEGY-ELO`,docType:`NAVIGATION_STRATEGY`,name:`ELO`,description:`ELO-based navigation strategy for ordering content by difficulty`,implementingClass:`elo`,course:this.id,serializedData:``};return Promise.resolve(e)}else return this.db.get(e)}async getAllNavigationStrategies(){let e=DocTypePrefixes.NAVIGATION_STRATEGY;return(await this.db.allDocs({startkey:e,endkey:`${e}\uFFF0`,include_docs:!0})).rows.map(e=>e.doc)}async addNavigationStrategy(e){return logger.debug(`[courseDB] Adding navigation strategy: ${e._id}`),this.invalidateNavigatorCache(),this.remoteDB.put(e).then(()=>{})}updateNavigationStrategy(e,t){return logger.debug(`[courseDB] Updating navigation strategy: ${e}`),logger.debug(JSON.stringify(t)),Promise.resolve()}async createNavigator(e){try{let t=await this.getAllNavigationStrategies();if(t.length===0)return logger.debug(`[courseDB] No strategy documents found, using default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])`),createDefaultPipeline(e,this);let{pipeline:n,generatorStrategies:r,filterStrategies:a,warnings:o}=await new PipelineAssembler().assemble({strategies:t,user:e,course:this});for(let e of o)logger.warn(`[PipelineAssembler] ${e}`);return n?(logger.debug(`[courseDB] Using assembled pipeline with ${r.length} generator(s) and ${a.length} filter(s)`),n):(logger.debug(`[courseDB] Pipeline assembly failed, using default pipeline`),createDefaultPipeline(e,this))}catch(e){let t=e instanceof Error?`${e.message}
271
- ${e.stack}`:JSON.stringify(e);throw logger.error(`[courseDB] Error creating navigator: ${t}`),e}}setEphemeralHints(e){this._pendingHints=e}async getWeightedCards(e){let t=await this._getCurrentUser();try{let{navigator:n}=await this._getCachedNavigator(t);return this._pendingHints&&(n.setEphemeralHints(this._pendingHints),this._pendingHints=null),await n.getWeightedCards(e)}catch(e){throw logger.error(`[courseDB] Error getting weighted cards: ${e}`),e}}async _getCachedNavigator(e){let t=e.getUsername(),n=Date.now();if(this._cachedNavigator&&this._cachedNavigator.userId===t&&n-this._cachedNavigator.builtAt<this._navigatorTtlMs)return{navigator:this._cachedNavigator.navigator,cacheStatus:`hit`};let r=await this.createNavigator(e);return this._cachedNavigator={navigator:r,userId:t,builtAt:n},{navigator:r,cacheStatus:`miss`}}invalidateNavigatorCache(){this._cachedNavigator=null}async getCardsCenteredAtELO(e={limit:99,elo:`user`},t){let n;if(e.elo===`user`){let e=await this._getCurrentUser();n=-1;try{n=EloToNumber((await e.getCourseRegistrationsDoc()).courses.find(e=>e.courseID===this.id).elo)}catch{n=1e3}}else if(e.elo===`random`){let e=await GET_CACHED(`elo-bounds-${this.id}`,()=>this.getELOBounds());n=Math.round(e.low+Math.random()*(e.high-e.low))}else n=e.elo;let r=Math.max(2e3,e.limit*4),a=Date.now(),o=`hit`;if(!this._eloPoolCache||a-this._eloPoolCache.fetchedAt>this._eloPoolTtlMs){let e=await this.getCardsByELO(n,r);e.length>0&&(this._eloPoolCache={rows:e,fetchedAt:a}),o=`miss`}let rankAgainstCurrentElo=()=>{let e=this._eloPoolCache?.rows??[];return(t?e.filter(e=>t(e)):e).map(e=>({...e})).sort((e,t)=>Math.abs((e.elo??n)-n)-Math.abs((t.elo??n)-n))},s=rankAgainstCurrentElo();if(s.length<e.limit&&(o===`hit`||!this._eloPoolCache)){let e=await this.getCardsByELO(n,r);e.length>0&&(this._eloPoolCache={rows:e,fetchedAt:a}),s=rankAgainstCurrentElo(),o=`refresh`}let c=[];for(;c.length<e.limit&&s.length>0;){let e=randIntWeightedTowardZero(s.length),t=s.splice(e,1)[0];c.push(t)}return c.map(e=>({courseID:this.id,cardID:e.cardID,contentSourceType:`course`,contentSourceID:this.id,elo:e.elo,status:`new`}))}async searchCards(e){logger.log(`[CourseDB ${this.id}] Searching for: "${e}"`);let t;try{t=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`,"data.0.data":{$regex:`.*${e}.*`}}}),logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`)}catch(n){logger.log(`[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,n);let r=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`}});logger.log(`[CourseDB ${this.id}] Retrieved ${r.docs.length} documents for manual filtering`),t={docs:r.docs.filter(t=>{let n=JSON.stringify(t).toLowerCase().includes(e.toLowerCase());return n&&logger.log(`[CourseDB ${this.id}] Manual match found in document: ${t._id}`),n})}}if(logger.log(`[CourseDB ${this.id}] Found ${t.docs.length} displayable data documents`),t.docs.length===0){let e=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`},limit:5});logger.log(`[CourseDB ${this.id}] Sample displayable data:`,e.docs.map(e=>({id:e._id,docType:e.docType,dataStructure:e.data?Object.keys(e.data):`no data field`,dataContent:e.data,fullDoc:e})))}let n=[];for(let e of t.docs){let t=await this.db.find({selector:{docType:`CARD`,id_displayable_data:{$in:[e._id]}}});logger.log(`[CourseDB ${this.id}] Displayable data ${e._id} linked to ${t.docs.length} cards`),n.push(...t.docs)}return logger.log(`[CourseDB ${this.id}] Total cards found: ${n.length}`),n}async find(e){return this.db.find(e)}}}}),classroomLookupDBTitle,CLASSROOM_CONFIG,ClassroomDBBase,StudentClassroomDB,TeacherClassroomDB,ClassroomLookupDB,init_classroomDB2=__esm({"src/impl/couch/classroomDB.ts"(){init_factory(),init_logger(),init_pouchdb_setup(),init_couch(),init_courseDB(),classroomLookupDBTitle=`classdb-lookup`,CLASSROOM_CONFIG=`ClassroomConfig`,ClassroomDBBase=class{constructor(){_defineProperty(this,`_id`,void 0),_defineProperty(this,`_db`,void 0),_defineProperty(this,`_cfg`,void 0),_defineProperty(this,`_initComplete`,!1),_defineProperty(this,`_content_prefix`,`content`)}get _content_searchkeys(){return getStartAndEndKeys2(this._content_prefix)}async getAssignedContent(){return logger.info(`Getting assigned content...`),(await this._db.allDocs({startkey:this._content_prefix,endkey:this._content_prefix+`￰`,include_docs:!0})).rows.map(e=>e.doc)}getContentId(e){return e.type===`tag`?`${this._content_prefix}-${e.courseID}-${e.tagID}`:`${this._content_prefix}-${e.courseID}`}get ready(){return this._initComplete}getConfig(){return this._cfg}},StudentClassroomDB=class _StudentClassroomDB extends ClassroomDBBase{constructor(e,t){super(),_defineProperty(this,`userMessages`,void 0),_defineProperty(this,`_user`,void 0),this._id=e,this._user=t}async init(){let e=`classdb-student-${this._id}`;this._db=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+e,createPouchDBConfig());try{this._cfg=await this._db.get(CLASSROOM_CONFIG),this.userMessages=this._db.changes({since:`now`,live:!0,include_docs:!0}),this._initComplete=!0;return}catch(e){throw Error(`Error in StudentClassroomDB constructor: ${JSON.stringify(e)}`)}}static async factory(e,t){let n=new _StudentClassroomDB(e,t);return await n.init(),n}setChangeFcn(e){this.userMessages.on(`change`,e)}async getWeightedCards(e){let t=[],n=(await this._user.getPendingReviews()).filter(e=>e.scheduledFor===`classroom`&&e.schedulingAgentId===this._id);for(let e of n)t.push({cardId:e.cardId,courseId:e.courseId,score:1,reviewID:e._id,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom scheduled review`}]});let r=await this._user.getActiveCards(),a=new Set(r.map(e=>e.cardID)),o=hooks.utc(),s=(await this.getAssignedContent()).filter(e=>o.isAfter(hooks.utc(e.activeOn,REVIEW_TIME_FORMAT2)));logger.info(`[StudentClassroomDB] Due content: ${JSON.stringify(s)}`);for(let n of s)if(n.type===`course`){let{cards:r}=await new CourseDB(n.courseID,async()=>this._user).getWeightedCards(e);for(let e of r)a.has(e.cardId)||t.push({...e,provenance:[...e.provenance,{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`passed`,score:e.score,reason:`Assigned via classroom from course ${n.courseID}`}]})}else if(n.type===`tag`){let e=await getTag(n.courseID,n.tagID);for(let r of e.taggedCards)a.has(r)||t.push({cardId:r,courseId:n.courseID,score:1,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom assigned tag: ${n.tagID}, new card`}]})}else n.type===`card`&&(a.has(n.cardID)||t.push({cardId:n.cardID,courseId:n.courseID,score:1,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom assigned card, new card`}]}));return logger.info(`[StudentClassroomDB] New cards from classroom ${this._cfg.name}: ${t.length} total (reviews + new)`),{cards:t.sort((e,t)=>t.score-e.score).slice(0,e)}}},TeacherClassroomDB=class _TeacherClassroomDB extends ClassroomDBBase{constructor(e){super(),_defineProperty(this,`_stuDb`,void 0),this._id=e}async init(){let e=`classdb-teacher-${this._id}`,t=`classdb-student-${this._id}`;this._db=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+e,createPouchDBConfig()),this._stuDb=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+t,createPouchDBConfig());try{return this._db.get(CLASSROOM_CONFIG).then(e=>{this._cfg=e,this._initComplete=!0}).then(()=>{})}catch(e){throw Error(`Error in TeacherClassroomDB constructor: ${JSON.stringify(e)}`)}}static async factory(e){let t=new _TeacherClassroomDB(e);return await t.init(),t}async removeContent(e){let t=this.getContentId(e);try{let e=await this._db.get(t);await this._db.remove(e),this._db.replicate.to(this._stuDb,{doc_ids:[t]})}catch(e){logger.error(`Failed to remove content:`,t,e)}}async assignContent(e){let t,n=this.getContentId(e);return t=e.type===`tag`?await this._db.put({courseID:e.courseID,tagID:e.tagID,type:`tag`,_id:n,assignedBy:e.assignedBy,assignedOn:hooks.utc(),activeOn:e.activeOn||hooks.utc()}):await this._db.put({courseID:e.courseID,type:`course`,_id:n,assignedBy:e.assignedBy,assignedOn:hooks.utc(),activeOn:e.activeOn||hooks.utc()}),t.ok?(this._db.replicate.to(this._stuDb,{doc_ids:[n]}),!0):!1}},ClassroomLookupDB=()=>new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+classroomLookupDBTitle,{skip_setup:!0})}}),AdminDB,init_adminDB2=__esm({"src/impl/couch/adminDB.ts"(){init_pouchdb_setup(),init_factory(),init_couch(),init_classroomDB2(),init_courseLookupDB(),init_logger(),AdminDB=class{constructor(){_defineProperty(this,`usersDB`,void 0),this.usersDB=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+`_users`,createPouchDBConfig())}async getUsers(){return(await this.usersDB.allDocs({include_docs:!0,...getStartAndEndKeys2(`org.couchdb.user:`)})).rows.map(e=>e.doc)}async getCourses(){let e=await CourseLookup.allCourseWare();return await Promise.all(e.map(e=>getCredentialledCourseConfig(e._id)))}async removeCourse(e){let t=await CourseLookup.delete(e),n=await getCredentialledCourseConfig(e);n.deleted=!0;let r=await updateCredentialledCourseConfig(e,n);return{ok:t.ok&&r.ok,id:t.id,rev:t.rev}}async getClassrooms(){let e=(await ClassroomLookupDB().allDocs({include_docs:!0})).rows.map(e=>e.doc.uuid);logger.debug(e.join(`, `));let t=[];for(let n=0;n<e.length;n++)try{let r=await TeacherClassroomDB.factory(e[n]);t.push(r)}catch(t){let r=t;r.error&&r.error===`not_found`&&logger.warn(`db ${e[n]} not found`)}return t.map(e=>({...e.getConfig(),_id:e._id}))}}}}),CourseSyncService_exports={};__export(CourseSyncService_exports,{CourseSyncService:()=>CourseSyncService});var DEFAULT_REPLICATION,CourseSyncService,init_CourseSyncService=__esm({"src/impl/couch/CourseSyncService.ts"(){var e;init_pouchdb_setup(),init_couch(),init_logger(),DEFAULT_REPLICATION={batchSize:1e3,batchesLimit:5},CourseSyncService=(e=class _CourseSyncService{constructor(){_defineProperty(this,`entries`,new Map),_defineProperty(this,`replicationOptions`,DEFAULT_REPLICATION)}static getInstance(){return _CourseSyncService.instance||(_CourseSyncService.instance=new _CourseSyncService),_CourseSyncService.instance}configure(e){e.replication&&(this.replicationOptions={...DEFAULT_REPLICATION,...e.replication},logger.info(`[CourseSyncService] Replication configured: batch_size=${this.replicationOptions.batchSize}, batches_limit=${this.replicationOptions.batchesLimit}`))}static resetInstance(){if(_CourseSyncService.instance){for(let[,e]of _CourseSyncService.instance.entries)e.localDB&&e.localDB.close().catch(()=>{});_CourseSyncService.instance.entries.clear()}_CourseSyncService.instance=null}async ensureSynced(e,t){let n=this.entries.get(e);if(n?.status.state===`ready`&&n.localDB){if(!await this.isLocalEpochStale(e,n.localDB))return;logger.info(`[CourseSyncService] Remote DB epoch changed for course ${e} \u2014 destroying stale local replica`);try{await n.localDB.destroy()}catch{}n.localDB=null,n.readyPromise=null}if(n?.status.state===`disabled`)return;if(n?.readyPromise)return n.readyPromise;let r={localDB:null,status:{state:`not-started`},readyPromise:null};return this.entries.set(e,r),r.readyPromise=this.performSync(e,r,t),r.readyPromise}getLocalDB(e){let t=this.entries.get(e);return t?.status.state===`ready`&&t.localDB?t.localDB:null}isReady(e){return this.entries.get(e)?.status.state===`ready`}getStatus(e){return this.entries.get(e)?.status??{state:`not-started`}}async performSync(e,t,n){try{if(!n&&(t.status={state:`checking-config`},!await this.checkLocalSyncEnabled(e))){t.status={state:`disabled`},t.readyPromise=null,logger.debug(`[CourseSyncService] Local sync disabled for course ${e}`);return}t.status={state:`syncing`};let r=this.localDBName(e),a=new pouchdb_setup_default(r);await this.isLocalEpochStale(e,a)&&(logger.info(`[CourseSyncService] Stale local DB detected for course ${e} \u2014 destroying before sync`),await a.destroy(),a=new pouchdb_setup_default(r)),t.localDB=a;let o=this.getRemoteDB(e),s=Date.now();logger.info(`[CourseSyncService] Starting one-shot replication for course ${e} (batch_size=${this.replicationOptions.batchSize}, batches_limit=${this.replicationOptions.batchesLimit})`);let c=await this.replicate(o,a),l=Date.now()-s;logger.info(`[CourseSyncService] Replication complete for course ${e}: ${c.docs_written} docs in ${l}ms`),t.status={state:`warming-views`};let u=Date.now();await this.warmViewIndices(a);let d=Date.now()-u;logger.info(`[CourseSyncService] View indices warmed for course ${e} in ${d}ms`),t.status={state:`ready`,docsReplicated:c.docs_written,syncTimeMs:l,viewWarmTimeMs:d}}catch(n){let r=n instanceof Error?n.message:String(n);if(logger.error(`[CourseSyncService] Sync failed for course ${e}: ${r}`),t.status={state:`error`,error:r},t.readyPromise=null,t.localDB){try{await t.localDB.destroy()}catch{}t.localDB=null}}}async checkLocalSyncEnabled(e){try{return(await this.getRemoteDB(e).get(`CourseConfig`)).localSync?.enabled===!0}catch(t){return logger.warn(`[CourseSyncService] Could not read CourseConfig for ${e}, assuming local sync disabled: ${t}`),!1}}replicate(e,t){return new Promise((n,r)=>{pouchdb_setup_default.replicate(e,t,{batch_size:this.replicationOptions.batchSize,batches_limit:this.replicationOptions.batchesLimit}).on(`complete`,e=>{n(e)}).on(`error`,e=>{r(e)})})}async warmViewIndices(e){for(let t of[`elo`,`getTags`])try{await e.query(t,{limit:1}),logger.debug(`[CourseSyncService] Warmed view index: ${t}`)}catch(e){logger.debug(`[CourseSyncService] Could not warm view ${t}: ${e}`)}}async isLocalEpochStale(e,t){try{let n=await this.getRemoteDB(e).get(`db-epoch`),r=null;try{r=await t.get(`db-epoch`)}catch{return!0}return n.epoch!==r.epoch}catch{return!1}}getRemoteDB(e){return getCourseDB2(e)}localDBName(e){return`coursedb-local-${e}`}},_defineProperty(e,`instance`,null),e)}});async function getCurrentSession(){try{if(ENV.COUCHDB_SERVER_URL===NOT_SET||ENV.COUCHDB_SERVER_PROTOCOL===NOT_SET)throw Error(`CouchDB server configuration not properly initialized. Protocol: "${ENV.COUCHDB_SERVER_PROTOCOL}", URL: "${ENV.COUCHDB_SERVER_URL}"`);let e=ENV.COUCHDB_SERVER_URL.endsWith(`/`)?ENV.COUCHDB_SERVER_URL.slice(0,-1):ENV.COUCHDB_SERVER_URL,t=`${ENV.COUCHDB_SERVER_PROTOCOL}://${e}/_session`;logger.debug(`Attempting session check at: ${t}`);let n=await(0,import_browser_ponyfill.default)(t,{method:`GET`,credentials:`include`});if(!n.ok)throw Error(`Session check failed: ${n.status}`);return await n.json()}catch(e){let t=ENV.COUCHDB_SERVER_URL.endsWith(`/`)?ENV.COUCHDB_SERVER_URL.slice(0,-1):ENV.COUCHDB_SERVER_URL,n=`${ENV.COUCHDB_SERVER_PROTOCOL}://${t}/_session`;throw logger.error(`Session check error attempting to connect to: ${n} - ${e}`),Error(`Session check failed connecting to ${n}: ${e}`)}}async function getLoggedInUsername(){let e=await getCurrentSession();if(e.userCtx.name&&e.userCtx.name!==``)return e.userCtx.name;throw Error(`No logged in user`)}var init_auth=__esm({"src/impl/couch/auth.ts"(){init_factory(),init_logger()}}),CouchDBSyncStrategy_exports={};__export(CouchDBSyncStrategy_exports,{CouchDBSyncStrategy:()=>CouchDBSyncStrategy});var log3,CouchDBSyncStrategy,init_CouchDBSyncStrategy=__esm({"src/impl/couch/CouchDBSyncStrategy.ts"(){init_factory(),init_types_legacy(),init_logger(),init_common(),init_pouchdb_setup(),init_couch(),init_auth(),log3=e=>{logger.info(e)},CouchDBSyncStrategy=class{constructor(){_defineProperty(this,`syncHandle`,void 0)}setupRemoteDB(e){return e===GuestUsername||e.startsWith(GuestUsername)?getLocalUserDB(e):this.getUserDB(e)}getWriteDB(e){return e===GuestUsername||e.startsWith(GuestUsername)?getLocalUserDB(e):this.getUserDB(e)}startSync(e,t){e!==t&&(this.syncHandle=pouchdb_setup_default.sync(e,t,{live:!0,retry:!0}))}stopSync(){this.syncHandle&&(this.syncHandle.cancel(),this.syncHandle=void 0)}canCreateAccount(){return!0}canAuthenticate(){return!0}async createAccount(e,t){let n=await this.getCurrentUsername(),r=n.startsWith(GuestUsername);r&&logger.info(`Creating account for funnel user ${n} -> ${e}`);try{let a=await this.getRemoteCouchRootDB().signUp(e,t);if(a.ok){log3(`CREATEACCOUNT: Successfully created account for ${e}`);try{let e=await this.getRemoteCouchRootDB().logOut();log3(`CREATEACCOUNT: logged out: ${e.ok}`)}catch{}let a=await this.getRemoteCouchRootDB().logIn(e,t);if(log3(`CREATEACCOUNT: logged in as new user: ${a.ok}`),a.ok){if(r){logger.info(`Migrating data from funnel account ${n} to ${e}`);let t=await this.migrateFunnelData(n,e);t.success||logger.warn(`Migration failed: ${t.error}`)}return{status:Status.ok,error:void 0}}else return{status:Status.error,error:`Failed to log in after account creation`}}else return logger.warn(`Signup not OK: ${JSON.stringify(a)}`),{status:Status.error,error:`Account creation failed`}}catch(e){return e.reason===`Document update conflict.`?{status:Status.error,error:`This username is taken!`}:(logger.error(`Error on signup: ${JSON.stringify(e)}`),{status:Status.error,error:e.message||`Unknown error during account creation`})}}async authenticate(e,t){try{return(await this.getRemoteCouchRootDB().logIn(e,t)).ok?(log3(`Successfully logged in as ${e}`),{ok:!0}):(log3(`Login failed for ${e}`),{ok:!1,error:`Invalid username or password`})}catch(t){return logger.error(`Authentication error for ${e}:`,t),{ok:!1,error:t.message||`Authentication failed`}}}async logout(){try{let e=await this.getRemoteCouchRootDB().logOut();return{ok:e.ok,error:e.ok?void 0:`Logout failed`}}catch(e){return logger.error(`Logout error:`,e),{ok:!1,error:e.message||`Logout failed`}}}async getCurrentUsername(){logger.log(`[funnel] CouchDBSyncStrategy.getCurrentUsername() called`);try{let e=await getLoggedInUsername();return logger.log(`[funnel] getLoggedInUsername() returned:`,e),e}catch(e){logger.log(`[funnel] getLoggedInUsername() failed, calling accomodateGuest()`),logger.log(`[funnel] Error was:`,e);let t=accomodateGuest();return logger.log(`[funnel] accomodateGuest() returned:`,t),t.username}}async migrateFunnelData(e,t){try{logger.info(`Starting data migration from ${e} to ${t}`);let n=getLocalUserDB(e),r=getLocalUserDB(t),a=await n.allDocs({include_docs:!0});logger.info(`Found ${a.rows.length} documents in funnel account`);let o=a.rows.filter(e=>!e.id.startsWith(`_design/`)).map(e=>({...e.doc,_rev:void 0}));o.length>0?(await r.bulkDocs(o),logger.info(`Successfully migrated ${o.length} documents from ${e} to ${t}`)):logger.info(`No documents to migrate from funnel account`);try{await n.destroy(),logger.info(`Destroyed consumed guest DB for ${e}`)}catch(t){logger.warn(`Failed to destroy consumed guest DB for ${e} (non-fatal): ${t instanceof Error?t.message:String(t)}`)}return{success:!0}}catch(e){return logger.error(`Migration failed:`,e),{success:!1,error:e instanceof Error?e.message:`Unknown error`}}}getRemoteCouchRootDB(){let e=ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+`skuilder`;try{return new pouchdb_setup_default(e,{skip_setup:!0})}catch(e){throw logger.error(`Failed to initialize remote CouchDB connection:`,e),Error(`Failed to initialize CouchDB: ${JSON.stringify(e)}`)}}getUserDB(e){let t=!1,n=`userdb-${hexEncode(e)}`;return log3(`Fetching user database: ${n} (${e})`),new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+n,createPouchDBConfig())}}}});function createPouchDBConfig(){return ENV.COUCHDB_USERNAME&&ENV.COUCHDB_PASSWORD&&typeof window>`u`?{fetch(e,t={}){let n=btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`),r=new Headers(t.headers||{});r.set(`Authorization`,`Basic ${n}`);let a={...t,headers:r};return pouchdb_setup_default.fetch(e,a)}}:pouchDBincludeCredentialsConfig}function getCourseDB2(e){return new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+`coursedb-`+e,createPouchDBConfig())}function filterAllDocsByPrefix2(e,t,n){let r={startkey:t,endkey:t+`￰`,include_docs:!0};return n&&Object.assign(r,n),e.allDocs(r)}function getStartAndEndKeys2(e){return{startkey:e,endkey:e+`￰`}}var isBrowser,GUEST_LOCAL_DB,localUserDB,pouchDBincludeCredentialsConfig,REVIEW_TIME_FORMAT2,init_couch=__esm({"src/impl/couch/index.ts"(){init_factory(),init_types_legacy(),init_logger(),init_pouchdb_setup(),init_contentSource(),init_adminDB2(),init_classroomDB2(),init_courseAPI(),init_courseDB(),init_CourseSyncService(),init_CouchDBSyncStrategy(),isBrowser=typeof window<`u`,isBrowser&&(window.process=import_browser.default),GUEST_LOCAL_DB=`userdb-${GuestUsername}`,new pouchdb_setup_default(GUEST_LOCAL_DB),pouchDBincludeCredentialsConfig={fetch(e,t){return t.credentials=`include`,pouchdb_setup_default.fetch(e,t)}},REVIEW_TIME_FORMAT2=`YYYY-MM-DD--kk:mm:ss-SSS`}});function accomodateGuest(){if(logger.log(`[funnel] accomodateGuest() called`),typeof localStorage>`u`)return logger.log(`[funnel] localStorage not available (Node.js environment), returning default guest`),{username:GuestUsername+`nodejs-test`,firstVisit:!0};let e=`sk-guest-uuid`,t,n=localStorage.getItem(e);if(logger.log(`[funnel] Checking localStorage for key:`,e),logger.log(`[funnel] Existing UUID value:`,n),logger.log(`[funnel] existingUUID !== null:`,n!==null),n!==null)t=!1,logger.log(`[funnel] Returning guest ${n} "logging in".`);else{t=!0,logger.log(`[funnel] No existing UUID, generating new one...`);let n=generateUUID();logger.log(`[funnel] Generated UUID:`,n),logger.log(`[funnel] UUID length:`,n.length);try{localStorage.setItem(e,n),logger.log(`[funnel] Successfully stored UUID in localStorage`);let t=localStorage.getItem(e);logger.log(`[funnel] Verification read from localStorage:`,t)}catch(e){logger.error(`[funnel] ERROR storing UUID:`,e)}logger.log(`[funnel] Accommodating a new guest with account: ${n}`)}let r=localStorage.getItem(e),a=GuestUsername+r;return logger.log(`[funnel] Final UUID from localStorage:`,r),logger.log(`[funnel] GuestUsername constant:`,GuestUsername),logger.log(`[funnel] Final username to return:`,a),{username:a,firstVisit:t};function generateUUID(){if(logger.log(`[funnel] Inside generateUUID()`),typeof crypto<`u`&&typeof crypto.randomUUID==`function`){let e=crypto.randomUUID();return logger.log(`[funnel] Generated UUID using crypto.randomUUID():`,e),e}if(typeof crypto<`u`&&typeof crypto.getRandomValues==`function`){let e=new Uint8Array(16);crypto.getRandomValues(e),e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t=[Array.from(e.slice(0,4)).map(e=>e.toString(16).padStart(2,`0`)).join(``),Array.from(e.slice(4,6)).map(e=>e.toString(16).padStart(2,`0`)).join(``),Array.from(e.slice(6,8)).map(e=>e.toString(16).padStart(2,`0`)).join(``),Array.from(e.slice(8,10)).map(e=>e.toString(16).padStart(2,`0`)).join(``),Array.from(e.slice(10,16)).map(e=>e.toString(16).padStart(2,`0`)).join(``)].join(`-`);return logger.log(`[funnel] Generated UUID using crypto.getRandomValues():`,t),t}logger.warn(`[funnel] crypto API not available, using timestamp-based UUID (NOT SECURE)`);let e=new Date().getTime();typeof performance<`u`&&typeof performance.now==`function`&&(e+=performance.now());let t=`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,t=>{let n=(e+Math.random()*16)%16|0;return e=Math.floor(e/16),(t===`x`?n:n&3|8).toString(16)});return logger.log(`[funnel] Generated UUID (fallback):`,t),t}}async function getOrCreateClassroomRegistrationsDoc(e){let t;try{t=await getLocalUserDB(e).get(userClassroomsDoc)}catch(n){let r=n;if(r.status===404)await getLocalUserDB(e).put({_id:userClassroomsDoc,registrations:[]}),t=await getOrCreateClassroomRegistrationsDoc(e);else{let e={name:r.name,status:r.status,message:r.message,reason:r.reason,error:r.error};throw logger.error(`Database error in getOrCreateClassroomRegistrationsDoc (standalone function):`,e),Error(`Database error accessing classroom registrations: ${r.message||r.name||`Unknown error`} (status: ${r.status})`)}}return t}async function getOrCreateCourseRegistrationsDoc(e){let t;try{t=await getLocalUserDB(e).get(userCoursesDoc)}catch(n){if(n.status===404)await getLocalUserDB(e).put({_id:userCoursesDoc,courses:[],studyWeight:{}}),t=await getOrCreateCourseRegistrationsDoc(e);else throw Error(`Unexpected error ${JSON.stringify(n)} in getOrCreateCourseRegistrationDoc...`)}return t}async function updateUserElo(e,t,n){let r=await getOrCreateCourseRegistrationsDoc(e),a=r.courses.find(e=>e.courseID===t);return a.elo=n,getLocalUserDB(e).put(r)}async function registerUserForClassroom(e,t,n){return log4(`Registering user: ${e} in course: ${t}`),getOrCreateClassroomRegistrationsDoc(e).then(r=>{let a={classID:t,registeredAs:n};return r.registrations.filter(e=>e.classID===a.classID&&e.registeredAs===a.registeredAs).length===0?r.registrations.push(a):log4(`User ${e} is already registered for class ${t}`),getLocalUserDB(e).put(r)})}async function dropUserFromClassroom(e,t){return getOrCreateClassroomRegistrationsDoc(e).then(n=>{let r=-1;for(let e=0;e<n.registrations.length;e++)n.registrations[e].classID===t&&(r=e);return r!==-1&&n.registrations.splice(r,1),getLocalUserDB(e).put(n)})}async function getUserClassrooms(e){return getOrCreateClassroomRegistrationsDoc(e)}var log4,BaseUser,userCoursesDoc,userClassroomsDoc,init_BaseUserDB=__esm({"src/impl/common/BaseUserDB.ts"(){var e;init_core(),init_util(),init_types_legacy(),init_logger(),init_userDBHelpers(),init_updateQueue(),init_user_course_relDB(),init_couch(),log4=e=>{logger.info(e)},BaseUser=(e=class _BaseUser{static Dummy(e){return new _BaseUser(`Me`,e)}getUsername(){return this._username}isLoggedIn(){return!this._username.startsWith(GuestUsername)}remote(){return this.remoteDB}async createAccount(e,t){if(!this.syncStrategy.canCreateAccount())throw Error(`Account creation not supported by current sync strategy`);if(!this._username.startsWith(GuestUsername))throw Error(`Cannot create a new account while logged in:
271
+ ${e.stack}`:JSON.stringify(e);throw logger.error(`[courseDB] Error creating navigator: ${t}`),e}}setEphemeralHints(e){this._pendingHints=e}async getWeightedCards(e){let t=await this._getCurrentUser();try{let{navigator:n}=await this._getCachedNavigator(t);return this._pendingHints&&(n.setEphemeralHints(this._pendingHints),this._pendingHints=null),await n.getWeightedCards(e)}catch(e){throw logger.error(`[courseDB] Error getting weighted cards: ${e}`),e}}async _getCachedNavigator(e){let t=e.getUsername(),n=Date.now();if(this._cachedNavigator&&this._cachedNavigator.userId===t&&n-this._cachedNavigator.builtAt<this._navigatorTtlMs)return{navigator:this._cachedNavigator.navigator,cacheStatus:`hit`};let r=await this.createNavigator(e);return this._cachedNavigator={navigator:r,userId:t,builtAt:n},{navigator:r,cacheStatus:`miss`}}invalidateNavigatorCache(){this._cachedNavigator=null}async getCardsCenteredAtELO(e={limit:99,elo:`user`},t){let n;if(e.elo===`user`){let e=await this._getCurrentUser();n=-1;try{n=EloToNumber((await e.getCourseRegistrationsDoc()).courses.find(e=>e.courseID===this.id).elo)}catch{n=1e3}}else if(e.elo===`random`){let e=await GET_CACHED(`elo-bounds-${this.id}`,()=>this.getELOBounds());n=Math.round(e.low+Math.random()*(e.high-e.low))}else n=e.elo;let r=Math.max(2e3,e.limit*4),a=Date.now(),o=`hit`;if(!this._eloPoolCache||a-this._eloPoolCache.fetchedAt>this._eloPoolTtlMs){let e=await this.getCardsByELO(n,r);e.length>0&&(this._eloPoolCache={rows:e,fetchedAt:a}),o=`miss`}let rankAgainstCurrentElo=()=>{let e=this._eloPoolCache?.rows??[];return(t?e.filter(e=>t(e)):e).map(e=>({...e})).sort((e,t)=>Math.abs((e.elo??n)-n)-Math.abs((t.elo??n)-n))},s=rankAgainstCurrentElo();if(s.length<e.limit&&(o===`hit`||!this._eloPoolCache)){let e=await this.getCardsByELO(n,r);e.length>0&&(this._eloPoolCache={rows:e,fetchedAt:a}),s=rankAgainstCurrentElo(),o=`refresh`}let c=[];for(;c.length<e.limit&&s.length>0;){let e=randIntWeightedTowardZero(s.length),t=s.splice(e,1)[0];c.push(t)}return c.map(e=>({courseID:this.id,cardID:e.cardID,contentSourceType:`course`,contentSourceID:this.id,elo:e.elo,status:`new`}))}async searchCards(e){logger.log(`[CourseDB ${this.id}] Searching for: "${e}"`);let t;try{t=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`,"data.0.data":{$regex:`.*${e}.*`}}}),logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`)}catch(n){logger.log(`[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,n);let r=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`}});logger.log(`[CourseDB ${this.id}] Retrieved ${r.docs.length} documents for manual filtering`),t={docs:r.docs.filter(t=>{let n=JSON.stringify(t).toLowerCase().includes(e.toLowerCase());return n&&logger.log(`[CourseDB ${this.id}] Manual match found in document: ${t._id}`),n})}}if(logger.log(`[CourseDB ${this.id}] Found ${t.docs.length} displayable data documents`),t.docs.length===0){let e=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`},limit:5});logger.log(`[CourseDB ${this.id}] Sample displayable data:`,e.docs.map(e=>({id:e._id,docType:e.docType,dataStructure:e.data?Object.keys(e.data):`no data field`,dataContent:e.data,fullDoc:e})))}let n=[];for(let e of t.docs){let t=await this.db.find({selector:{docType:`CARD`,id_displayable_data:{$in:[e._id]}}});logger.log(`[CourseDB ${this.id}] Displayable data ${e._id} linked to ${t.docs.length} cards`),n.push(...t.docs)}return logger.log(`[CourseDB ${this.id}] Total cards found: ${n.length}`),n}async find(e){return this.db.find(e)}}}}),classroomLookupDBTitle,CLASSROOM_CONFIG,ClassroomDBBase,StudentClassroomDB,TeacherClassroomDB,ClassroomLookupDB,init_classroomDB2=__esm({"src/impl/couch/classroomDB.ts"(){init_factory(),init_logger(),init_pouchdb_setup(),init_couch(),init_courseDB(),classroomLookupDBTitle=`classdb-lookup`,CLASSROOM_CONFIG=`ClassroomConfig`,ClassroomDBBase=class{constructor(){_defineProperty(this,`_id`,void 0),_defineProperty(this,`_db`,void 0),_defineProperty(this,`_cfg`,void 0),_defineProperty(this,`_initComplete`,!1),_defineProperty(this,`_content_prefix`,`content`)}get _content_searchkeys(){return getStartAndEndKeys2(this._content_prefix)}async getAssignedContent(){return logger.info(`Getting assigned content...`),(await this._db.allDocs({startkey:this._content_prefix,endkey:this._content_prefix+`￰`,include_docs:!0})).rows.map(e=>e.doc)}getContentId(e){return e.type===`tag`?`${this._content_prefix}-${e.courseID}-${e.tagID}`:`${this._content_prefix}-${e.courseID}`}get ready(){return this._initComplete}getConfig(){return this._cfg}},StudentClassroomDB=class _StudentClassroomDB extends ClassroomDBBase{constructor(e,t){super(),_defineProperty(this,`userMessages`,void 0),_defineProperty(this,`_user`,void 0),this._id=e,this._user=t}async init(){let e=`classdb-student-${this._id}`;this._db=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+e,createPouchDBConfig());try{this._cfg=await this._db.get(CLASSROOM_CONFIG),this.userMessages=this._db.changes({since:`now`,live:!0,include_docs:!0}),this._initComplete=!0;return}catch(e){throw Error(`Error in StudentClassroomDB constructor: ${JSON.stringify(e)}`)}}static async factory(e,t){let n=new _StudentClassroomDB(e,t);return await n.init(),n}setChangeFcn(e){this.userMessages.on(`change`,e)}async getWeightedCards(e){let t=[],n=(await this._user.getPendingReviews()).filter(e=>e.scheduledFor===`classroom`&&e.schedulingAgentId===this._id);for(let e of n)t.push({cardId:e.cardId,courseId:e.courseId,score:1,reviewID:e._id,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom scheduled review`}]});let r=await this._user.getActiveCards(),a=new Set(r.map(e=>e.cardID)),o=hooks.utc(),s=(await this.getAssignedContent()).filter(e=>o.isAfter(hooks.utc(e.activeOn,REVIEW_TIME_FORMAT2)));logger.info(`[StudentClassroomDB] Due content: ${JSON.stringify(s)}`);for(let n of s)if(n.type===`course`){let{cards:r}=await new CourseDB(n.courseID,async()=>this._user).getWeightedCards(e);for(let e of r)a.has(e.cardId)||t.push({...e,provenance:[...e.provenance,{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`passed`,score:e.score,reason:`Assigned via classroom from course ${n.courseID}`}]})}else if(n.type===`tag`){let e=await getTag(n.courseID,n.tagID);for(let r of e.taggedCards)a.has(r)||t.push({cardId:r,courseId:n.courseID,score:1,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom assigned tag: ${n.tagID}, new card`}]})}else n.type===`card`&&(a.has(n.cardID)||t.push({cardId:n.cardID,courseId:n.courseID,score:1,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom assigned card, new card`}]}));return logger.info(`[StudentClassroomDB] New cards from classroom ${this._cfg.name}: ${t.length} total (reviews + new)`),{cards:t.sort((e,t)=>t.score-e.score).slice(0,e)}}},TeacherClassroomDB=class _TeacherClassroomDB extends ClassroomDBBase{constructor(e){super(),_defineProperty(this,`_stuDb`,void 0),this._id=e}async init(){let e=`classdb-teacher-${this._id}`,t=`classdb-student-${this._id}`;this._db=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+e,createPouchDBConfig()),this._stuDb=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+t,createPouchDBConfig());try{return this._db.get(CLASSROOM_CONFIG).then(e=>{this._cfg=e,this._initComplete=!0}).then(()=>{})}catch(e){throw Error(`Error in TeacherClassroomDB constructor: ${JSON.stringify(e)}`)}}static async factory(e){let t=new _TeacherClassroomDB(e);return await t.init(),t}async removeContent(e){let t=this.getContentId(e);try{let e=await this._db.get(t);await this._db.remove(e),this._db.replicate.to(this._stuDb,{doc_ids:[t]})}catch(e){logger.error(`Failed to remove content:`,t,e)}}async assignContent(e){let t,n=this.getContentId(e);return t=e.type===`tag`?await this._db.put({courseID:e.courseID,tagID:e.tagID,type:`tag`,_id:n,assignedBy:e.assignedBy,assignedOn:hooks.utc(),activeOn:e.activeOn||hooks.utc()}):await this._db.put({courseID:e.courseID,type:`course`,_id:n,assignedBy:e.assignedBy,assignedOn:hooks.utc(),activeOn:e.activeOn||hooks.utc()}),t.ok?(this._db.replicate.to(this._stuDb,{doc_ids:[n]}),!0):!1}},ClassroomLookupDB=()=>new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+classroomLookupDBTitle,{skip_setup:!0})}}),AdminDB,init_adminDB2=__esm({"src/impl/couch/adminDB.ts"(){init_pouchdb_setup(),init_factory(),init_couch(),init_classroomDB2(),init_courseLookupDB(),init_logger(),AdminDB=class{constructor(){_defineProperty(this,`usersDB`,void 0),this.usersDB=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+`_users`,createPouchDBConfig())}async getUsers(){return(await this.usersDB.allDocs({include_docs:!0,...getStartAndEndKeys2(`org.couchdb.user:`)})).rows.map(e=>e.doc)}async getCourses(){let e=await CourseLookup.allCourseWare();return await Promise.all(e.map(e=>getCredentialledCourseConfig(e._id)))}async removeCourse(e){let t=await CourseLookup.delete(e),n=await getCredentialledCourseConfig(e);n.deleted=!0;let r=await updateCredentialledCourseConfig(e,n);return{ok:t.ok&&r.ok,id:t.id,rev:t.rev}}async getClassrooms(){let e=(await ClassroomLookupDB().allDocs({include_docs:!0})).rows.map(e=>e.doc.uuid);logger.debug(e.join(`, `));let t=[];for(let n=0;n<e.length;n++)try{let r=await TeacherClassroomDB.factory(e[n]);t.push(r)}catch(t){let r=t;r.error&&r.error===`not_found`&&logger.warn(`db ${e[n]} not found`)}return t.map(e=>({...e.getConfig(),_id:e._id}))}}}}),CourseSyncService_exports={};__export(CourseSyncService_exports,{CourseSyncService:()=>CourseSyncService});var DEFAULT_REPLICATION,CourseSyncService,init_CourseSyncService=__esm({"src/impl/couch/CourseSyncService.ts"(){var e;init_pouchdb_setup(),init_couch(),init_logger(),DEFAULT_REPLICATION={batchSize:1e3,batchesLimit:5},CourseSyncService=(e=class _CourseSyncService{constructor(){_defineProperty(this,`entries`,new Map),_defineProperty(this,`replicationOptions`,DEFAULT_REPLICATION)}static getInstance(){return _CourseSyncService.instance||(_CourseSyncService.instance=new _CourseSyncService),_CourseSyncService.instance}configure(e){e.replication&&(this.replicationOptions={...DEFAULT_REPLICATION,...e.replication},logger.info(`[CourseSyncService] Replication configured: batch_size=${this.replicationOptions.batchSize}, batches_limit=${this.replicationOptions.batchesLimit}`))}static resetInstance(){if(_CourseSyncService.instance){for(let[,e]of _CourseSyncService.instance.entries)e.localDB&&e.localDB.close().catch(()=>{});_CourseSyncService.instance.entries.clear()}_CourseSyncService.instance=null}async ensureSynced(e,t){let n=this.entries.get(e);if(n?.status.state===`ready`&&n.localDB){if(!await this.isLocalEpochStale(e,n.localDB))return;logger.info(`[CourseSyncService] Remote DB epoch changed for course ${e} \u2014 destroying stale local replica`);try{await n.localDB.destroy()}catch{}n.localDB=null,n.readyPromise=null}if(n?.status.state===`disabled`)return;if(n?.readyPromise)return n.readyPromise;let r={localDB:null,status:{state:`not-started`},readyPromise:null};return this.entries.set(e,r),r.readyPromise=this.performSync(e,r,t),r.readyPromise}getLocalDB(e){let t=this.entries.get(e);return t?.status.state===`ready`&&t.localDB?t.localDB:null}isReady(e){return this.entries.get(e)?.status.state===`ready`}getStatus(e){return this.entries.get(e)?.status??{state:`not-started`}}async performSync(e,t,n){try{if(!n&&(t.status={state:`checking-config`},!await this.checkLocalSyncEnabled(e))){t.status={state:`disabled`},t.readyPromise=null,logger.debug(`[CourseSyncService] Local sync disabled for course ${e}`);return}t.status={state:`syncing`};let r=this.localDBName(e),a=new pouchdb_setup_default(r);await this.isLocalEpochStale(e,a)&&(logger.info(`[CourseSyncService] Stale local DB detected for course ${e} \u2014 destroying before sync`),await a.destroy(),a=new pouchdb_setup_default(r)),t.localDB=a;let o=this.getRemoteDB(e),s=Date.now();logger.info(`[CourseSyncService] Starting one-shot replication for course ${e} (batch_size=${this.replicationOptions.batchSize}, batches_limit=${this.replicationOptions.batchesLimit})`);let c=await this.replicate(o,a),l=Date.now()-s;logger.info(`[CourseSyncService] Replication complete for course ${e}: ${c.docs_written} docs in ${l}ms`),t.status={state:`warming-views`};let u=Date.now();await this.warmViewIndices(a);let d=Date.now()-u;logger.info(`[CourseSyncService] View indices warmed for course ${e} in ${d}ms`),t.status={state:`ready`,docsReplicated:c.docs_written,syncTimeMs:l,viewWarmTimeMs:d}}catch(n){let r=n instanceof Error?n.message:String(n);if(logger.error(`[CourseSyncService] Sync failed for course ${e}: ${r}`),t.status={state:`error`,error:r},t.readyPromise=null,t.localDB){try{await t.localDB.destroy()}catch{}t.localDB=null}}}async checkLocalSyncEnabled(e){try{return(await this.getRemoteDB(e).get(`CourseConfig`)).localSync?.enabled===!0}catch(t){return logger.warn(`[CourseSyncService] Could not read CourseConfig for ${e}, assuming local sync disabled: ${t}`),!1}}replicate(e,t){return new Promise((n,r)=>{pouchdb_setup_default.replicate(e,t,{batch_size:this.replicationOptions.batchSize,batches_limit:this.replicationOptions.batchesLimit}).on(`complete`,e=>{n(e)}).on(`error`,e=>{r(e)})})}async warmViewIndices(e){for(let t of[`elo`,`getTags`])try{await e.query(t,{limit:1}),logger.debug(`[CourseSyncService] Warmed view index: ${t}`)}catch(e){logger.debug(`[CourseSyncService] Could not warm view ${t}: ${e}`)}}async isLocalEpochStale(e,t){try{let n=await this.getRemoteDB(e).get(`db-epoch`),r=null;try{r=await t.get(`db-epoch`)}catch{return!0}return n.epoch!==r.epoch}catch{return!1}}getRemoteDB(e){return getCourseDB2(e)}localDBName(e){return`coursedb-local-${e}`}},_defineProperty(e,`instance`,null),e)}});async function getCurrentSession(){try{if(ENV.COUCHDB_SERVER_URL===NOT_SET||ENV.COUCHDB_SERVER_PROTOCOL===NOT_SET)throw Error(`CouchDB server configuration not properly initialized. Protocol: "${ENV.COUCHDB_SERVER_PROTOCOL}", URL: "${ENV.COUCHDB_SERVER_URL}"`);let e=ENV.COUCHDB_SERVER_URL.endsWith(`/`)?ENV.COUCHDB_SERVER_URL.slice(0,-1):ENV.COUCHDB_SERVER_URL,t=`${ENV.COUCHDB_SERVER_PROTOCOL}://${e}/_session`;logger.debug(`Attempting session check at: ${t}`);let n=await(0,import_browser_ponyfill.default)(t,{method:`GET`,credentials:`include`});if(!n.ok)throw Error(`Session check failed: ${n.status}`);return await n.json()}catch(e){let t=ENV.COUCHDB_SERVER_URL.endsWith(`/`)?ENV.COUCHDB_SERVER_URL.slice(0,-1):ENV.COUCHDB_SERVER_URL,n=`${ENV.COUCHDB_SERVER_PROTOCOL}://${t}/_session`;throw logger.error(`Session check error attempting to connect to: ${n} - ${e}`),Error(`Session check failed connecting to ${n}: ${e}`)}}async function getLoggedInUsername(){let e=await getCurrentSession();if(e.userCtx.name&&e.userCtx.name!==``)return e.userCtx.name;throw Error(`No logged in user`)}var init_auth=__esm({"src/impl/couch/auth.ts"(){init_factory(),init_logger()}}),CouchDBSyncStrategy_exports={};__export(CouchDBSyncStrategy_exports,{CouchDBSyncStrategy:()=>CouchDBSyncStrategy});var log3,CouchDBSyncStrategy,init_CouchDBSyncStrategy=__esm({"src/impl/couch/CouchDBSyncStrategy.ts"(){init_factory(),init_types_legacy(),init_logger(),init_common(),init_pouchdb_setup(),init_couch(),init_auth(),log3=e=>{logger.info(e)},CouchDBSyncStrategy=class{constructor(){_defineProperty(this,`syncHandle`,void 0),_defineProperty(this,`hydrationHandle`,void 0)}setupRemoteDB(e){return e===GuestUsername||e.startsWith(GuestUsername)?getLocalUserDB(e):this.getUserDB(e)}getWriteDB(e){return e===GuestUsername||e.startsWith(GuestUsername)?getLocalUserDB(e):this.getUserDB(e)}async hydrate(e,t){let n=pouchdb_setup_default.replicate(t,e,{});this.hydrationHandle=n;try{return{docsWritten:(await n).docs_written}}finally{this.hydrationHandle=void 0}}startSync(e,t){e.name!==t.name&&(this.syncHandle=pouchdb_setup_default.sync(e,t,{live:!0,retry:!0}))}stopSync(){if(this.hydrationHandle){try{this.hydrationHandle.cancel()}catch(e){logger.warn(`Failed to cancel hydration pull (continuing anyway): ${e}`)}this.hydrationHandle=void 0}if(this.syncHandle){try{this.syncHandle.cancel()}catch(e){logger.warn(`Failed to cancel user sync (continuing anyway): ${e}`)}this.syncHandle=void 0}}canCreateAccount(){return!0}canAuthenticate(){return!0}async createAccount(e,t){let n=await this.getCurrentUsername(),r=n.startsWith(GuestUsername);r&&logger.info(`Creating account for funnel user ${n} -> ${e}`);try{let a=await this.getRemoteCouchRootDB().signUp(e,t);if(a.ok){log3(`CREATEACCOUNT: Successfully created account for ${e}`);try{let e=await this.getRemoteCouchRootDB().logOut();log3(`CREATEACCOUNT: logged out: ${e.ok}`)}catch{}let a=await this.getRemoteCouchRootDB().logIn(e,t);if(log3(`CREATEACCOUNT: logged in as new user: ${a.ok}`),a.ok){if(r){logger.info(`Migrating data from funnel account ${n} to ${e}`);let t=await this.migrateFunnelData(n,e);t.success||logger.warn(`Migration failed: ${t.error}`)}return{status:Status.ok,error:void 0}}else return{status:Status.error,error:`Failed to log in after account creation`}}else return logger.warn(`Signup not OK: ${JSON.stringify(a)}`),{status:Status.error,error:`Account creation failed`}}catch(e){return e.reason===`Document update conflict.`?{status:Status.error,error:`This username is taken!`}:(logger.error(`Error on signup: ${JSON.stringify(e)}`),{status:Status.error,error:e.message||`Unknown error during account creation`})}}async authenticate(e,t){try{return(await this.getRemoteCouchRootDB().logIn(e,t)).ok?(log3(`Successfully logged in as ${e}`),{ok:!0}):(log3(`Login failed for ${e}`),{ok:!1,error:`Invalid username or password`})}catch(t){return logger.error(`Authentication error for ${e}:`,t),{ok:!1,error:t.message||`Authentication failed`}}}async logout(){try{let e=await this.getRemoteCouchRootDB().logOut();return{ok:e.ok,error:e.ok?void 0:`Logout failed`}}catch(e){return logger.error(`Logout error:`,e),{ok:!1,error:e.message||`Logout failed`}}}async getCurrentUsername(){logger.log(`[funnel] CouchDBSyncStrategy.getCurrentUsername() called`);try{let e=await getLoggedInUsername();return logger.log(`[funnel] getLoggedInUsername() returned:`,e),e}catch(e){logger.log(`[funnel] getLoggedInUsername() failed, calling accomodateGuest()`),logger.log(`[funnel] Error was:`,e);let t=accomodateGuest();return logger.log(`[funnel] accomodateGuest() returned:`,t),t.username}}async migrateFunnelData(e,t){try{logger.info(`Starting data migration from ${e} to ${t}`);let n=getLocalUserDB(e),r=getLocalUserDB(t),a=await n.allDocs({include_docs:!0});logger.info(`Found ${a.rows.length} documents in funnel account`);let o=a.rows.filter(e=>!e.id.startsWith(`_design/`)).map(e=>({...e.doc,_rev:void 0}));o.length>0?(await r.bulkDocs(o),logger.info(`Successfully migrated ${o.length} documents from ${e} to ${t}`)):logger.info(`No documents to migrate from funnel account`);try{await n.destroy(),logger.info(`Destroyed consumed guest DB for ${e}`)}catch(t){logger.warn(`Failed to destroy consumed guest DB for ${e} (non-fatal): ${t instanceof Error?t.message:String(t)}`)}return{success:!0}}catch(e){return logger.error(`Migration failed:`,e),{success:!1,error:e instanceof Error?e.message:`Unknown error`}}}getRemoteCouchRootDB(){let e=ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+`skuilder`;try{return new pouchdb_setup_default(e,{skip_setup:!0})}catch(e){throw logger.error(`Failed to initialize remote CouchDB connection:`,e),Error(`Failed to initialize CouchDB: ${JSON.stringify(e)}`)}}getUserDB(e){let t=!1,n=`userdb-${hexEncode(e)}`;return log3(`Fetching user database: ${n} (${e})`),new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+n,createPouchDBConfig())}}}});function createPouchDBConfig(){return ENV.COUCHDB_USERNAME&&ENV.COUCHDB_PASSWORD&&typeof window>`u`?{fetch(e,t={}){let n=btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`),r=new Headers(t.headers||{});r.set(`Authorization`,`Basic ${n}`);let a={...t,headers:r};return pouchdb_setup_default.fetch(e,a)}}:pouchDBincludeCredentialsConfig}function getCourseDB2(e){return new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+`coursedb-`+e,createPouchDBConfig())}function filterAllDocsByPrefix2(e,t,n){let r={startkey:t,endkey:t+`￰`,include_docs:!0};return n&&Object.assign(r,n),e.allDocs(r)}function getStartAndEndKeys2(e){return{startkey:e,endkey:e+`￰`}}var isBrowser,GUEST_LOCAL_DB,localUserDB,pouchDBincludeCredentialsConfig,REVIEW_TIME_FORMAT2,init_couch=__esm({"src/impl/couch/index.ts"(){init_factory(),init_types_legacy(),init_logger(),init_pouchdb_setup(),init_contentSource(),init_adminDB2(),init_classroomDB2(),init_courseAPI(),init_courseDB(),init_CourseSyncService(),init_CouchDBSyncStrategy(),isBrowser=typeof window<`u`,isBrowser&&(window.process=import_browser.default),GUEST_LOCAL_DB=`userdb-${GuestUsername}`,new pouchdb_setup_default(GUEST_LOCAL_DB),pouchDBincludeCredentialsConfig={fetch(e,t){return t.credentials=`include`,pouchdb_setup_default.fetch(e,t)}},REVIEW_TIME_FORMAT2=`YYYY-MM-DD--kk:mm:ss-SSS`}});function withTimeout(e,t,n){let r;return Promise.race([e,new Promise((e,a)=>{r=setTimeout(()=>a(Error(`${n} timed out after ${t}ms`)),t)})]).finally(()=>{r!==void 0&&clearTimeout(r)})}function accomodateGuest(){if(logger.log(`[funnel] accomodateGuest() called`),typeof localStorage>`u`)return logger.log(`[funnel] localStorage not available (Node.js environment), returning default guest`),{username:GuestUsername+`nodejs-test`,firstVisit:!0};let e=`sk-guest-uuid`,t,n=localStorage.getItem(e);if(logger.log(`[funnel] Checking localStorage for key:`,e),logger.log(`[funnel] Existing UUID value:`,n),logger.log(`[funnel] existingUUID !== null:`,n!==null),n!==null)t=!1,logger.log(`[funnel] Returning guest ${n} "logging in".`);else{t=!0,logger.log(`[funnel] No existing UUID, generating new one...`);let n=generateUUID();logger.log(`[funnel] Generated UUID:`,n),logger.log(`[funnel] UUID length:`,n.length);try{localStorage.setItem(e,n),logger.log(`[funnel] Successfully stored UUID in localStorage`);let t=localStorage.getItem(e);logger.log(`[funnel] Verification read from localStorage:`,t)}catch(e){logger.error(`[funnel] ERROR storing UUID:`,e)}logger.log(`[funnel] Accommodating a new guest with account: ${n}`)}let r=localStorage.getItem(e),a=GuestUsername+r;return logger.log(`[funnel] Final UUID from localStorage:`,r),logger.log(`[funnel] GuestUsername constant:`,GuestUsername),logger.log(`[funnel] Final username to return:`,a),{username:a,firstVisit:t};function generateUUID(){if(logger.log(`[funnel] Inside generateUUID()`),typeof crypto<`u`&&typeof crypto.randomUUID==`function`){let e=crypto.randomUUID();return logger.log(`[funnel] Generated UUID using crypto.randomUUID():`,e),e}if(typeof crypto<`u`&&typeof crypto.getRandomValues==`function`){let e=new Uint8Array(16);crypto.getRandomValues(e),e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t=[Array.from(e.slice(0,4)).map(e=>e.toString(16).padStart(2,`0`)).join(``),Array.from(e.slice(4,6)).map(e=>e.toString(16).padStart(2,`0`)).join(``),Array.from(e.slice(6,8)).map(e=>e.toString(16).padStart(2,`0`)).join(``),Array.from(e.slice(8,10)).map(e=>e.toString(16).padStart(2,`0`)).join(``),Array.from(e.slice(10,16)).map(e=>e.toString(16).padStart(2,`0`)).join(``)].join(`-`);return logger.log(`[funnel] Generated UUID using crypto.getRandomValues():`,t),t}logger.warn(`[funnel] crypto API not available, using timestamp-based UUID (NOT SECURE)`);let e=new Date().getTime();typeof performance<`u`&&typeof performance.now==`function`&&(e+=performance.now());let t=`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,t=>{let n=(e+Math.random()*16)%16|0;return e=Math.floor(e/16),(t===`x`?n:n&3|8).toString(16)});return logger.log(`[funnel] Generated UUID (fallback):`,t),t}}async function getOrCreateClassroomRegistrationsDoc(e){let t;try{t=await getLocalUserDB(e).get(userClassroomsDoc)}catch(n){let r=n;if(r.status===404)await getLocalUserDB(e).put({_id:userClassroomsDoc,registrations:[]}),t=await getOrCreateClassroomRegistrationsDoc(e);else{let e={name:r.name,status:r.status,message:r.message,reason:r.reason,error:r.error};throw logger.error(`Database error in getOrCreateClassroomRegistrationsDoc (standalone function):`,e),Error(`Database error accessing classroom registrations: ${r.message||r.name||`Unknown error`} (status: ${r.status})`)}}return t}async function getOrCreateCourseRegistrationsDoc(e){let t;try{t=await getLocalUserDB(e).get(userCoursesDoc)}catch(n){if(n.status===404)await getLocalUserDB(e).put({_id:userCoursesDoc,courses:[],studyWeight:{}}),t=await getOrCreateCourseRegistrationsDoc(e);else throw Error(`Unexpected error ${JSON.stringify(n)} in getOrCreateCourseRegistrationDoc...`)}return t}async function updateUserElo(e,t,n){let r=await getOrCreateCourseRegistrationsDoc(e),a=r.courses.find(e=>e.courseID===t);return a.elo=n,getLocalUserDB(e).put(r)}async function registerUserForClassroom(e,t,n){return log4(`Registering user: ${e} in course: ${t}`),getOrCreateClassroomRegistrationsDoc(e).then(r=>{let a={classID:t,registeredAs:n};return r.registrations.filter(e=>e.classID===a.classID&&e.registeredAs===a.registeredAs).length===0?r.registrations.push(a):log4(`User ${e} is already registered for class ${t}`),getLocalUserDB(e).put(r)})}async function dropUserFromClassroom(e,t){return getOrCreateClassroomRegistrationsDoc(e).then(n=>{let r=-1;for(let e=0;e<n.registrations.length;e++)n.registrations[e].classID===t&&(r=e);return r!==-1&&n.registrations.splice(r,1),getLocalUserDB(e).put(n)})}async function getUserClassrooms(e){return getOrCreateClassroomRegistrationsDoc(e)}var log4,HYDRATION_TIMEOUT_MS,BaseUser,userCoursesDoc,userClassroomsDoc,init_BaseUserDB=__esm({"src/impl/common/BaseUserDB.ts"(){var e;init_core(),init_util(),init_types_legacy(),init_logger(),init_userDBHelpers(),init_updateQueue(),init_user_course_relDB(),init_couch(),log4=e=>{logger.info(e)},HYDRATION_TIMEOUT_MS=15e3,BaseUser=(e=class _BaseUser{static Dummy(e){return new _BaseUser(`Me`,e)}getUsername(){return this._username}isLoggedIn(){return!this._username.startsWith(GuestUsername)}remote(){return this.remoteDB}hydrationStatus(){return{...this._hydration}}canMaterializeDefaults(){return this._hydration.state!==`failed`&&this._hydration.state!==`hydrating`}async createAccount(e,t){if(!this.syncStrategy.canCreateAccount())throw Error(`Account creation not supported by current sync strategy`);if(!this._username.startsWith(GuestUsername))throw Error(`Cannot create a new account while logged in:
272
272
  Currently logged-in as ${this._username}.`);let n=await this.syncStrategy.createAccount(e,t);if(n.status===Status.ok){log4(`Account created successfully, updating username to ${e}`),this._username=e;try{localStorage.removeItem(`sk-guest-uuid`)}catch(e){logger.warn(`localStorage not available (Node.js environment):`,e)}await this.init()}return{status:n.status,error:n.error||``}}async login(e,t){if(!this.syncStrategy.canAuthenticate())throw Error(`Authentication not supported by current sync strategy`);if(!this._username.startsWith(GuestUsername)&&this._username!=e){if(this._username!=e)throw Error(`Cannot change accounts while logged in.
273
- Log out of account ${this.getUsername()} before logging in as ${e}.`);logger.warn(`User ${this._username} is already logged in, but executing login again.`)}let n=await this.syncStrategy.authenticate(e,t);if(n.ok){log4(`Logged in as ${e}`),this._username=e;try{localStorage.removeItem(`sk-guest-uuid`)}catch(e){logger.warn(`localStorage not available (Node.js environment):`,e)}await this.init()}return n}async resetUserData(){if(this.syncStrategy.canAuthenticate())return{status:Status.error,error:`Reset user data is only available for local-only mode. Use logout instead for remote sync.`};try{let e=getLocalUserDB(this._username),t=(await e.allDocs({include_docs:!1})).rows.filter(e=>{let t=e.id;return t.startsWith(DocTypePrefixes.CARDRECORD)||t.startsWith(DocTypePrefixes.SCHEDULED_CARD)||t.startsWith(DocTypePrefixes.STRATEGY_STATE)||t.startsWith(DocTypePrefixes.USER_OUTCOME)||t.startsWith(DocTypePrefixes.STRATEGY_LEARNING_STATE)||t===_BaseUser.DOC_IDS.COURSE_REGISTRATIONS||t===_BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS||t===_BaseUser.DOC_IDS.CONFIG}).map(e=>({_id:e.id,_rev:e.value.rev,_deleted:!0}));return t.length>0&&await e.bulkDocs(t),await this.init(),{status:Status.ok}}catch(e){return logger.error(`Failed to reset user data:`,e),{status:Status.error,error:e instanceof Error?e.message:`Unknown error during reset`}}}async logout(){if(!this.syncStrategy.canAuthenticate())return this._username=await this.syncStrategy.getCurrentUsername(),await this.init(),{ok:!0};let e=await this.syncStrategy.logout();try{localStorage.removeItem(`sk-guest-uuid`)}catch(e){logger.warn(`localStorage not available (Node.js environment):`,e)}return this._username=await this.syncStrategy.getCurrentUsername(),await this.init(),e}async get(e){return this.localDB.get(e)}update(e,t){return this.updateQueue.update(e,t)}async getCourseRegistrationsDoc(){logger.debug(`Fetching courseRegistrations for ${this.getUsername()}`);let e;try{return await this.localDB.get(_BaseUser.DOC_IDS.COURSE_REGISTRATIONS)}catch(t){if(t.status===404)await this.localDB.put({_id:_BaseUser.DOC_IDS.COURSE_REGISTRATIONS,courses:[],studyWeight:{}}),e=await this.getCourseRegistrationsDoc();else throw Error(`Unexpected error ${JSON.stringify(t)} in getOrCreateCourseRegistrationDoc...`)}return e}async getActiveCourses(){return(await this.getCourseRegistrationsDoc()).courses.filter(e=>e.status===void 0||e.status===`active`)}async getActiveCards(){let e=getStartAndEndKeys(DocTypePrefixes.SCHEDULED_CARD);return(await this.remoteDB.allDocs({startkey:e.startkey,endkey:e.endkey,include_docs:!0})).rows.map(e=>({courseID:e.doc.courseId,cardID:e.doc.cardId}))}async getActivityRecords(){try{let e=await this.getHistory(),t=[];if(!Array.isArray(e))return logger.error(`getHistory did not return an array:`,e),t;let n=0;for(let r=0;r<e.length;r++)try{e[r]&&Array.isArray(e[r].records)&&e[r].records.forEach(e=>{try{if(!e.timeStamp)return;let r;if(typeof e.timeStamp==`object`)if(typeof e.timeStamp.toDate==`function`)r=e.timeStamp.toISOString();else if(e.timeStamp instanceof Date)r=e.timeStamp.toISOString();else{n<3&&(logger.warn(`Unknown timestamp object type:`,e.timeStamp),n++);return}else if(typeof e.timeStamp==`string`){let t=new Date(e.timeStamp);if(isNaN(t.getTime()))return;r=e.timeStamp}else if(typeof e.timeStamp==`number`)r=new Date(e.timeStamp).toISOString();else return;t.push({timeStamp:r,courseID:e.courseID||`unknown`,cardID:e.cardID||`unknown`,timeSpent:e.timeSpent||0,type:`card_view`})}catch{}})}catch(e){logger.error(`Error processing history item:`,e)}return logger.debug(`Found ${t.length} activity records`),t}catch(e){return logger.error(`Error in getActivityRecords:`,e),[]}}async getReviewstoDate(e,t){let n=getStartAndEndKeys(DocTypePrefixes.SCHEDULED_CARD),r=await this.remoteDB.allDocs({startkey:n.startkey,endkey:n.endkey,include_docs:!0});return log4(`Fetching ${this._username}'s scheduled reviews${t?` for course ${t}`:``}.`),r.rows.filter(n=>{if(n.id.startsWith(DocTypePrefixes.SCHEDULED_CARD)){let r=hooks.utc(n.id.substr(DocTypePrefixes.SCHEDULED_CARD.length),REVIEW_TIME_FORMAT);if(e.isAfter(r)&&(t===void 0||n.doc.courseId===t))return!0}}).map(e=>e.doc)}async getReviewsForcast(e,t){let n=hooks.utc().add(e,`days`);return this.getReviewstoDate(n,t)}async getPendingReviews(e){let t=hooks.utc();return this.getReviewstoDate(t,e)}async getScheduledReviewCount(e){return(await this.getPendingReviews(e)).length}async getRegisteredCourses(){return(await this.getCourseRegistrationsDoc()).courses.filter(e=>!e.status||e.status===`active`||e.status===`maintenance-mode`)}async getCourseRegDoc(e){let t=(await this.getCourseRegistrationsDoc()).courses.find(t=>t.courseID===e);if(t)return t;throw Error(`Course registration not found for course ID: ${e}`)}async registerForCourse(e,t=!1){return this.getCourseRegistrationsDoc().then(n=>{let r=t?`preview`:`active`;logger.debug(`Registering for ${e} with status: ${r}`);let a={status:r,courseID:e,user:!0,admin:!1,moderator:!1,elo:{global:{score:1e3,count:0},tags:{},misc:{}}};return n.courses.filter(e=>e.courseID===a.courseID).length===0?(log4(`It's a new course registration!`),n.courses.push(a),n.studyWeight[e]=1):n.courses.forEach(t=>{log4(`Found the previously registered course!`),t.courseID===e&&(t.status=r)}),this.localDB.put(n)}).catch(e=>{throw log4(`Registration failed because of: ${JSON.stringify(e)}`),e})}async dropCourse(e,t=`dropped`){return this.getCourseRegistrationsDoc().then(n=>{let r=-1;for(let t=0;t<n.courses.length;t++)n.courses[t].courseID===e&&(r=t);if(r!==-1)delete n.studyWeight[e],n.courses[r].status=t;else throw Error(`User ${this.getUsername()} is not currently registered for course ${e}`);return this.localDB.put(n)})}async getCourseInterface(e){return new UsrCrsData(this,e)}async getUserEditableCourses(){let e=[],t=await this.getCourseRegistrationsDoc();return e=e.concat(t.courses.map(e=>e.courseID)),await Promise.all(e.map(async e=>await getCredentialledCourseConfig(e)))}async getConfig(){let e={_id:_BaseUser.DOC_IDS.CONFIG,darkMode:!1,likesConfetti:!1,sessionTimeLimit:5};try{let e=await this.localDB.get(_BaseUser.DOC_IDS.CONFIG);return logger.debug(`Raw config from DB:`,e),e}catch(t){let n=t;if(n.name&&n.name===`not_found`)return await this.localDB.put(e),this.getConfig();throw logger.error(`Error setting user default config:`,t),Error(`Error returning the user's configuration: ${JSON.stringify(t)}`)}}async setConfig(e){logger.debug(`Setting Config items ${JSON.stringify(e)}`);let t=await this.getConfig(),n=await this.localDB.put({...t,...e});n.ok?logger.debug(`Config items set: ${JSON.stringify(e)}`):logger.error(`Error setting config items: ${JSON.stringify(n)}`)}static async instance(e,t){return t?(_BaseUser._instance=new _BaseUser(t,e),await _BaseUser._instance.init(),_BaseUser._instance):_BaseUser._instance&&_BaseUser._initialized?_BaseUser._instance:_BaseUser._instance?new Promise(e=>{(function waitForUser(){if(_BaseUser._initialized)return e(_BaseUser._instance);setTimeout(waitForUser,50)})()}):(_BaseUser._instance=new _BaseUser(await e.getCurrentUsername(),e),await _BaseUser._instance.init(),_BaseUser._instance)}constructor(e,t){_defineProperty(this,`_username`,void 0),_defineProperty(this,`syncStrategy`,void 0),_defineProperty(this,`localDB`,void 0),_defineProperty(this,`remoteDB`,void 0),_defineProperty(this,`writeDB`,void 0),_defineProperty(this,`updateQueue`,void 0),_BaseUser._initialized=!1,this._username=e,this.syncStrategy=t,this.setDBandQ()}setDBandQ(){this.localDB=getLocalUserDB(this._username),this.remoteDB=this.syncStrategy.setupRemoteDB(this._username),this.writeDB=this.syncStrategy.getWriteDB?this.syncStrategy.getWriteDB(this._username):this.localDB,this.updateQueue=new UpdateQueue(this.localDB,this.writeDB)}async init(){if(_BaseUser._initialized=!1,this._username===`admin`){_BaseUser._initialized=!0;return}this.setDBandQ(),this.syncStrategy.startSync(this.localDB,this.remoteDB),this.applyDesignDocs().catch(e=>{log4(`Error in applyDesignDocs background task: ${e}`),e&&typeof e==`object`&&log4(`Full error details in applyDesignDocs: ${JSON.stringify(e)}`)}),this.deduplicateReviews().catch(e=>{log4(`Error in deduplicateReviews background task: ${e}`),e&&typeof e==`object`&&log4(`Full error details in background task: ${JSON.stringify(e)}`)}),_BaseUser._initialized=!0}async applyDesignDocs(){if(log4(`Starting applyDesignDocs for user: ${this._username}`),log4(`Remote DB name: ${this.remoteDB.name||`unknown`}`),this._username===`admin`){log4(`Skipping design docs for admin user`);return}log4(`Applying ${_BaseUser.designDocs.length} design docs`);for(let e of _BaseUser.designDocs){log4(`Applying design doc: ${e._id}`);try{try{let t=await this.remoteDB.get(e._id);await this.remoteDB.put({...e,_rev:t._rev})}catch(t){if(t?.name===`not_found`)await this.remoteDB.put(e);else throw t}}catch(t){if(t.name&&t.name===`conflict`)logger.warn(`Design doc ${e._id} update conflict - will retry`),await new Promise(e=>setTimeout(e,1e3)),await this.applyDesignDoc(e);else throw logger.error(`Failed to apply design doc ${e._id}:`,t),t}}}async applyDesignDoc(e,t=3){try{let t=await this.remoteDB.get(e._id);await this.remoteDB.put({...e,_rev:t._rev})}catch(n){if(n?.name===`conflict`&&t>0)return await new Promise(e=>setTimeout(e,1e3)),this.applyDesignDoc(e,t-1);throw n}}async putCardRecord(e){let t=getCardHistoryID(e.courseID,e.cardID);e.timeStamp=hooks.utc(e.timeStamp).toString();try{let n=await this.update(t,function(t){return t.records.push(e),t.bestInterval=t.bestInterval||0,t.lapses=t.lapses||0,t.streak=t.streak||0,t});return n.records=n.records.map(e=>{let t={...e};return t.timeStamp=hooks.utc(e.timeStamp),t}),n}catch(n){let r=n;if(r.status===404)try{let n={_id:t,cardID:e.cardID,courseID:e.courseID,records:[e],lapses:0,streak:0,bestInterval:0},r=await this.writeDB.put(n);return{...n,_rev:r.rev}}catch(e){throw Error(`Failed to create CardHistory for ${t}. Reason: ${e}`)}else throw Error(`putCardRecord failed because of:
273
+ Log out of account ${this.getUsername()} before logging in as ${e}.`);logger.warn(`User ${this._username} is already logged in, but executing login again.`)}let n=await this.syncStrategy.authenticate(e,t);if(n.ok){log4(`Logged in as ${e}`),this._username=e;try{localStorage.removeItem(`sk-guest-uuid`)}catch(e){logger.warn(`localStorage not available (Node.js environment):`,e)}await this.init()}return n}async resetUserData(){if(this.syncStrategy.canAuthenticate())return{status:Status.error,error:`Reset user data is only available for local-only mode. Use logout instead for remote sync.`};try{let e=getLocalUserDB(this._username),t=(await e.allDocs({include_docs:!1})).rows.filter(e=>{let t=e.id;return t.startsWith(DocTypePrefixes.CARDRECORD)||t.startsWith(DocTypePrefixes.SCHEDULED_CARD)||t.startsWith(DocTypePrefixes.STRATEGY_STATE)||t.startsWith(DocTypePrefixes.USER_OUTCOME)||t.startsWith(DocTypePrefixes.STRATEGY_LEARNING_STATE)||t===_BaseUser.DOC_IDS.COURSE_REGISTRATIONS||t===_BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS||t===_BaseUser.DOC_IDS.CONFIG}).map(e=>({_id:e.id,_rev:e.value.rev,_deleted:!0}));return t.length>0&&await e.bulkDocs(t),await this.init(),{status:Status.ok}}catch(e){return logger.error(`Failed to reset user data:`,e),{status:Status.error,error:e instanceof Error?e.message:`Unknown error during reset`}}}async logout(){if(!this.syncStrategy.canAuthenticate())return this._username=await this.syncStrategy.getCurrentUsername(),await this.init(),{ok:!0};let e=await this.syncStrategy.logout();try{localStorage.removeItem(`sk-guest-uuid`)}catch(e){logger.warn(`localStorage not available (Node.js environment):`,e)}return this._username=await this.syncStrategy.getCurrentUsername(),await this.init(),e}async get(e){return this.localDB.get(e)}update(e,t){return this.updateQueue.update(e,t)}async getCourseRegistrationsDoc(){logger.debug(`Fetching courseRegistrations for ${this.getUsername()}`);let e;try{return await this.localDB.get(_BaseUser.DOC_IDS.COURSE_REGISTRATIONS)}catch(t){if(t.status===404){if(!this.canMaterializeDefaults())throw Error(`Cannot read course registrations for ${this._username}: the local mirror is unavailable (hydration state '${this._hydration.state}'). Refusing to create an empty registration doc \u2014 that would report an existing user as unregistered, and then lose to their real document once sync resumes.`);await this.localDB.put({_id:_BaseUser.DOC_IDS.COURSE_REGISTRATIONS,courses:[],studyWeight:{}}),e=await this.getCourseRegistrationsDoc()}else throw Error(`Unexpected error ${JSON.stringify(t)} in getOrCreateCourseRegistrationDoc...`)}return e}async getActiveCourses(){return(await this.getCourseRegistrationsDoc()).courses.filter(e=>e.status===void 0||e.status===`active`)}async getActiveCards(){let e=getStartAndEndKeys(DocTypePrefixes.SCHEDULED_CARD);return(await this.remoteDB.allDocs({startkey:e.startkey,endkey:e.endkey,include_docs:!0})).rows.map(e=>({courseID:e.doc.courseId,cardID:e.doc.cardId}))}async getActivityRecords(){try{let e=await this.getHistory(),t=[];if(!Array.isArray(e))return logger.error(`getHistory did not return an array:`,e),t;let n=0;for(let r=0;r<e.length;r++)try{e[r]&&Array.isArray(e[r].records)&&e[r].records.forEach(e=>{try{if(!e.timeStamp)return;let r;if(typeof e.timeStamp==`object`)if(typeof e.timeStamp.toDate==`function`)r=e.timeStamp.toISOString();else if(e.timeStamp instanceof Date)r=e.timeStamp.toISOString();else{n<3&&(logger.warn(`Unknown timestamp object type:`,e.timeStamp),n++);return}else if(typeof e.timeStamp==`string`){let t=new Date(e.timeStamp);if(isNaN(t.getTime()))return;r=e.timeStamp}else if(typeof e.timeStamp==`number`)r=new Date(e.timeStamp).toISOString();else return;t.push({timeStamp:r,courseID:e.courseID||`unknown`,cardID:e.cardID||`unknown`,timeSpent:e.timeSpent||0,type:`card_view`})}catch{}})}catch(e){logger.error(`Error processing history item:`,e)}return logger.debug(`Found ${t.length} activity records`),t}catch(e){return logger.error(`Error in getActivityRecords:`,e),[]}}async getReviewstoDate(e,t){let n=getStartAndEndKeys(DocTypePrefixes.SCHEDULED_CARD),r=await this.remoteDB.allDocs({startkey:n.startkey,endkey:n.endkey,include_docs:!0});return log4(`Fetching ${this._username}'s scheduled reviews${t?` for course ${t}`:``}.`),r.rows.filter(n=>{if(n.id.startsWith(DocTypePrefixes.SCHEDULED_CARD)){let r=hooks.utc(n.id.substr(DocTypePrefixes.SCHEDULED_CARD.length),REVIEW_TIME_FORMAT);if(e.isAfter(r)&&(t===void 0||n.doc.courseId===t))return!0}}).map(e=>e.doc)}async getReviewsForcast(e,t){let n=hooks.utc().add(e,`days`);return this.getReviewstoDate(n,t)}async getPendingReviews(e){let t=hooks.utc();return this.getReviewstoDate(t,e)}async getScheduledReviewCount(e){return(await this.getPendingReviews(e)).length}async getRegisteredCourses(){return(await this.getCourseRegistrationsDoc()).courses.filter(e=>!e.status||e.status===`active`||e.status===`maintenance-mode`)}async getCourseRegDoc(e){let t=(await this.getCourseRegistrationsDoc()).courses.find(t=>t.courseID===e);if(t)return t;throw Error(`Course registration not found for course ID: ${e}`)}async registerForCourse(e,t=!1){return this.getCourseRegistrationsDoc().then(n=>{let r=t?`preview`:`active`;logger.debug(`Registering for ${e} with status: ${r}`);let a={status:r,courseID:e,user:!0,admin:!1,moderator:!1,elo:{global:{score:1e3,count:0},tags:{},misc:{}}};return n.courses.filter(e=>e.courseID===a.courseID).length===0?(log4(`It's a new course registration!`),n.courses.push(a),n.studyWeight[e]=1):n.courses.forEach(t=>{log4(`Found the previously registered course!`),t.courseID===e&&(t.status=r)}),this.localDB.put(n)}).catch(e=>{throw log4(`Registration failed because of: ${JSON.stringify(e)}`),e})}async dropCourse(e,t=`dropped`){return this.getCourseRegistrationsDoc().then(n=>{let r=-1;for(let t=0;t<n.courses.length;t++)n.courses[t].courseID===e&&(r=t);if(r!==-1)delete n.studyWeight[e],n.courses[r].status=t;else throw Error(`User ${this.getUsername()} is not currently registered for course ${e}`);return this.localDB.put(n)})}async getCourseInterface(e){return new UsrCrsData(this,e)}async getUserEditableCourses(){let e=[],t=await this.getCourseRegistrationsDoc();return e=e.concat(t.courses.map(e=>e.courseID)),await Promise.all(e.map(async e=>await getCredentialledCourseConfig(e)))}async getConfig(){let e={_id:_BaseUser.DOC_IDS.CONFIG,darkMode:!1,likesConfetti:!1,sessionTimeLimit:5};try{let e=await this.localDB.get(_BaseUser.DOC_IDS.CONFIG);return logger.debug(`Raw config from DB:`,e),e}catch(t){let n=t;if(n.name&&n.name===`not_found`){if(!this.canMaterializeDefaults())throw Error(`Cannot read config for ${this._username}: the local mirror is unavailable (hydration state '${this._hydration.state}'). Refusing to write a default config over what may be an existing one.`);return await this.localDB.put(e),this.getConfig()}else throw logger.error(`Error setting user default config:`,t),Error(`Error returning the user's configuration: ${JSON.stringify(t)}`)}}async setConfig(e){logger.debug(`Setting Config items ${JSON.stringify(e)}`);let t=await this.getConfig(),n=await this.localDB.put({...t,...e});n.ok?logger.debug(`Config items set: ${JSON.stringify(e)}`):logger.error(`Error setting config items: ${JSON.stringify(n)}`)}static async instance(e,t){return t?(_BaseUser._instance=new _BaseUser(t,e),await _BaseUser._instance.init(),_BaseUser._instance):_BaseUser._instance&&_BaseUser._initialized?_BaseUser._instance:_BaseUser._instance?new Promise(e=>{(function waitForUser(){if(_BaseUser._initialized)return e(_BaseUser._instance);setTimeout(waitForUser,50)})()}):(_BaseUser._instance=new _BaseUser(await e.getCurrentUsername(),e),await _BaseUser._instance.init(),_BaseUser._instance)}constructor(e,t){_defineProperty(this,`_username`,void 0),_defineProperty(this,`syncStrategy`,void 0),_defineProperty(this,`localDB`,void 0),_defineProperty(this,`remoteDB`,void 0),_defineProperty(this,`writeDB`,void 0),_defineProperty(this,`updateQueue`,void 0),_defineProperty(this,`_hydration`,{state:`not-required`}),_BaseUser._initialized=!1,this._username=e,this.syncStrategy=t,this.setDBandQ()}setDBandQ(){this.localDB=getLocalUserDB(this._username),this.remoteDB=this.syncStrategy.setupRemoteDB(this._username),this.writeDB=this.syncStrategy.getWriteDB?this.syncStrategy.getWriteDB(this._username):this.localDB,this.updateQueue=new UpdateQueue(this.localDB,this.writeDB)}async init(){if(_BaseUser._initialized=!1,this._username===`admin`){_BaseUser._initialized=!0;return}this.syncStrategy.stopSync?.(),this.setDBandQ(),await this.hydrateLocalMirror(),this.syncStrategy.startSync(this.localDB,this.remoteDB),this.applyDesignDocs().catch(e=>{log4(`Error in applyDesignDocs background task: ${e}`),e&&typeof e==`object`&&log4(`Full error details in applyDesignDocs: ${JSON.stringify(e)}`)}),this.deduplicateReviews().catch(e=>{log4(`Error in deduplicateReviews background task: ${e}`),e&&typeof e==`object`&&log4(`Full error details in background task: ${JSON.stringify(e)}`)}),_BaseUser._initialized=!0}async hydrateLocalMirror(){if(this.localDB.name===this.remoteDB.name||!this.syncStrategy.hydrate){this._hydration={state:`not-required`};return}if(await this.hasHydrationMarker()){this._hydration={state:`stale`};return}this._hydration={state:`hydrating`};let e=Date.now();try{let{docsWritten:t}=await withTimeout(this.syncStrategy.hydrate(this.localDB,this.remoteDB),HYDRATION_TIMEOUT_MS,`Hydration of local mirror for ${this._username}`);await this.writeHydrationMarker(),this._hydration={state:`hydrated`,docsWritten:t,durationMs:Date.now()-e},log4(`Hydrated local mirror for ${this._username}: ${t} docs in ${this._hydration.durationMs}ms`)}catch(t){this.syncStrategy.stopSync?.(),this._hydration={state:`failed`,durationMs:Date.now()-e,error:t instanceof Error?t.message:String(t)},logger.error(`Failed to hydrate local mirror for ${this._username}:`,t)}}async hasHydrationMarker(){try{return(await this.localDB.get(HYDRATION_MARKER_ID)).username===this._username}catch{return!1}}async writeHydrationMarker(){try{let e;try{e=(await this.localDB.get(HYDRATION_MARKER_ID))._rev}catch{}await this.localDB.put({_id:HYDRATION_MARKER_ID,_rev:e,username:this._username,hydratedAt:new Date().toISOString()})}catch(e){logger.warn(`Could not write hydration marker for ${this._username}: ${e}`)}}async applyDesignDocs(){if(log4(`Starting applyDesignDocs for user: ${this._username}`),log4(`Remote DB name: ${this.remoteDB.name||`unknown`}`),this._username===`admin`){log4(`Skipping design docs for admin user`);return}log4(`Applying ${_BaseUser.designDocs.length} design docs`);for(let e of _BaseUser.designDocs){log4(`Applying design doc: ${e._id}`);try{try{let t=await this.remoteDB.get(e._id);await this.remoteDB.put({...e,_rev:t._rev})}catch(t){if(t?.name===`not_found`)await this.remoteDB.put(e);else throw t}}catch(t){if(t.name&&t.name===`conflict`)logger.warn(`Design doc ${e._id} update conflict - will retry`),await new Promise(e=>setTimeout(e,1e3)),await this.applyDesignDoc(e);else throw logger.error(`Failed to apply design doc ${e._id}:`,t),t}}}async applyDesignDoc(e,t=3){try{let t=await this.remoteDB.get(e._id);await this.remoteDB.put({...e,_rev:t._rev})}catch(n){if(n?.name===`conflict`&&t>0)return await new Promise(e=>setTimeout(e,1e3)),this.applyDesignDoc(e,t-1);throw n}}async putCardRecord(e){let t=getCardHistoryID(e.courseID,e.cardID);e.timeStamp=hooks.utc(e.timeStamp).toString();try{let n=await this.update(t,function(t){return t.records.push(e),t.bestInterval=t.bestInterval||0,t.lapses=t.lapses||0,t.streak=t.streak||0,t});return n.records=n.records.map(e=>{let t={...e};return t.timeStamp=hooks.utc(e.timeStamp),t}),n}catch(n){let r=n;if(r.status===404)try{let n={_id:t,cardID:e.cardID,courseID:e.courseID,records:[e],lapses:0,streak:0,bestInterval:0},r=await this.writeDB.put(n);return{...n,_rev:r.rev}}catch(e){throw Error(`Failed to create CardHistory for ${t}. Reason: ${e}`)}else throw Error(`putCardRecord failed because of:
274
274
  name:${r.name}
275
275
  error: ${r.error}
276
276
  message: ${r.message}`)}}async deduplicateReviews(){try{log4(`Starting deduplication of scheduled reviews...`),log4(`Remote DB name: ${this.remoteDB.name||`unknown`}`),log4(`Write DB name: ${this.writeDB.name||`unknown`}`);let e={},t=[];log4(`Attempting to query remoteDB for reviewCards/reviewCards. Database: ${this.remoteDB.name||`unknown`}`);let n=await this.remoteDB.query(`reviewCards/reviewCards`);if(log4(`Found ${n.rows.length} scheduled reviews to process`),n.rows.forEach(n=>{let r=n.value,a=n.key;e[r]?(log4(`Found duplicate scheduled review for card: ${r}`),log4(`Marking earlier review ${e[r]} for deletion, keeping ${a}`),t.push(e[r]),e[r]=a):e[r]=a}),t.length>0){log4(`Removing ${t.length} duplicate reviews...`);let e=t.map(async e=>{try{let t=await this.remoteDB.get(e);await this.writeDB.remove(t),log4(`Successfully removed duplicate review: ${e}`)}catch(t){log4(`Failed to remove duplicate review ${e}: ${t}`)}});await Promise.all(e),log4(`Deduplication complete. Processed ${t.length} duplicates`)}else log4(`No duplicate reviews found`)}catch(e){log4(`Error during review deduplication: ${e}`),e&&typeof e==`object`&&`status`in e&&e.status===404&&(log4(`Database not found (404) during review deduplication. Database: ${this.remoteDB.name||`unknown`}`),log4(`This might indicate the user database doesn't exist or the reviewCards view isn't available`)),e&&typeof e==`object`&&log4(`Full error details: ${JSON.stringify(e)}`)}}async getSeenCards(e){let t=DocTypePrefixes.CARDRECORD;e&&(t+=`-${e}-`);let n=await filterAllDocsByPrefix(this.localDB,t,{include_docs:!1}),r=[];return n.rows.forEach(e=>{e.id.startsWith(t)&&r.push(e.id.substr(t.length))}),r}async getHistory(){return(await filterAllDocsByPrefix(this.remoteDB,DocTypePrefixes.CARDRECORD,{include_docs:!0,attachments:!1})).rows.map(e=>e.doc)}async updateCourseSettings(e,t){this.getCourseRegistrationsDoc().then(n=>{let r=n.courses.find(t=>t.courseID===e);return r&&((r.settings===null||r.settings===void 0)&&(r.settings={}),t.forEach(e=>{r.settings[e.key]=e.value})),this.localDB.put(n)})}async getCourseSettings(e){let t=(await this.getCourseRegistrationsDoc()).courses.find(t=>t.courseID===e);if(t)return t.settings;throw Error(`getCourseSettings Failed:
277
- User is not registered for course ${e}`)}async getOrCreateClassroomRegistrationsDoc(){let e;try{e=await this.remoteDB.get(_BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS)}catch(t){let n=t;if(n.status===404)await this.writeDB.put({_id:_BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS,registrations:[]}),e=await this.getOrCreateClassroomRegistrationsDoc();else{let e={name:n.name,status:n.status,message:n.message,reason:n.reason,error:n.error};throw logger.error(`Database error in getOrCreateClassroomRegistrationsDoc (private method):`,e),Error(`Database error accessing classroom registrations: ${n.message||n.name||`Unknown error`} (status: ${n.status})`)}}return logger.debug(`Returning classroom registrations doc: ${JSON.stringify(e)}`),e}async getActiveClasses(){try{return(await this.getOrCreateClassroomRegistrationsDoc()).registrations.filter(e=>e.registeredAs===`student`).map(e=>e.classID)}catch(e){return logger.warn(`Failed to load classroom registrations, continuing without classroom data:`,e),[]}}async scheduleCardReview(e){return scheduleCardReviewLocal(this.writeDB,e)}async removeScheduledCardReview(e){return removeScheduledCardReviewLocal(this.writeDB,e)}async registerForClassroom(e,t){return registerUserForClassroom(this._username,e,t)}async dropFromClassroom(e){return dropUserFromClassroom(this._username,e)}async getUserClassrooms(){return getUserClassrooms(this._username)}async updateUserElo(e,t){return updateUserElo(this._username,e,t)}async getStrategyState(e,t){let n=buildStrategyStateId(e,t);try{return(await this.localDB.get(n)).data}catch(e){if(e.status===404)return null;throw e}}async putStrategyState(e,t,n){let r=buildStrategyStateId(e,t),a;try{a=(await this.localDB.get(r))._rev}catch(e){if(e.status!==404)throw e}let o={_id:r,_rev:a,docType:`STRATEGY_STATE`,courseId:e,strategyKey:t,data:n,updatedAt:new Date().toISOString()};await this.localDB.put(o)}async putUserOutcome(e){try{await this.localDB.put(e)}catch(t){if(t.status===409)e._rev=(await this.localDB.get(e._id))._rev,await this.localDB.put(e);else throw t}}async deleteStrategyState(e,t){let n=buildStrategyStateId(e,t);try{let e=await this.localDB.get(n);await this.localDB.remove(e)}catch(e){if(e.status===404)return;throw e}}},_defineProperty(e,`_instance`,void 0),_defineProperty(e,`_initialized`,!1),_defineProperty(e,`DOC_IDS`,{CONFIG:`CONFIG`,COURSE_REGISTRATIONS:`CourseRegistrations`,CLASSROOM_REGISTRATIONS:`ClassroomRegistrations`}),_defineProperty(e,`designDocs`,[{_id:`_design/reviewCards`,views:{reviewCards:{map:`function (doc) {
277
+ User is not registered for course ${e}`)}async getOrCreateClassroomRegistrationsDoc(){let e;try{e=await this.remoteDB.get(_BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS)}catch(t){let n=t;if(n.status===404)await this.writeDB.put({_id:_BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS,registrations:[]}),e=await this.getOrCreateClassroomRegistrationsDoc();else{let e={name:n.name,status:n.status,message:n.message,reason:n.reason,error:n.error};throw logger.error(`Database error in getOrCreateClassroomRegistrationsDoc (private method):`,e),Error(`Database error accessing classroom registrations: ${n.message||n.name||`Unknown error`} (status: ${n.status})`)}}return logger.debug(`Returning classroom registrations doc: ${JSON.stringify(e)}`),e}async getActiveClasses(){try{return(await this.getOrCreateClassroomRegistrationsDoc()).registrations.filter(e=>e.registeredAs===`student`).map(e=>e.classID)}catch(e){return logger.warn(`Failed to load classroom registrations, continuing without classroom data:`,e),[]}}async scheduleCardReview(e){return scheduleCardReviewLocal(this.writeDB,e)}async removeScheduledCardReview(e){return removeScheduledCardReviewLocal(this.writeDB,e)}async registerForClassroom(e,t){return registerUserForClassroom(this._username,e,t)}async dropFromClassroom(e){return dropUserFromClassroom(this._username,e)}async getUserClassrooms(){return getUserClassrooms(this._username)}async updateUserElo(e,t){return updateUserElo(this._username,e,t)}async getStrategyState(e,t){let n=buildStrategyStateId(e,t);try{return(await this.localDB.get(n)).data}catch(e){if(e.status===404){if(!this.canMaterializeDefaults())throw Error(`Cannot read strategy state '${t}' for ${this._username}: the local mirror is unavailable (hydration state '${this._hydration.state}').`);return null}throw e}}async putStrategyState(e,t,n){let r=buildStrategyStateId(e,t),a;try{a=(await this.localDB.get(r))._rev}catch(e){if(e.status!==404)throw e}let o={_id:r,_rev:a,docType:`STRATEGY_STATE`,courseId:e,strategyKey:t,data:n,updatedAt:new Date().toISOString()};await this.localDB.put(o)}async putUserOutcome(e){try{await this.localDB.put(e)}catch(t){if(t.status===409)e._rev=(await this.localDB.get(e._id))._rev,await this.localDB.put(e);else throw t}}async deleteStrategyState(e,t){let n=buildStrategyStateId(e,t);try{let e=await this.localDB.get(n);await this.localDB.remove(e)}catch(e){if(e.status===404)return;throw e}}},_defineProperty(e,`_instance`,void 0),_defineProperty(e,`_initialized`,!1),_defineProperty(e,`DOC_IDS`,{CONFIG:`CONFIG`,COURSE_REGISTRATIONS:`CourseRegistrations`,CLASSROOM_REGISTRATIONS:`ClassroomRegistrations`}),_defineProperty(e,`designDocs`,[{_id:`_design/reviewCards`,views:{reviewCards:{map:`function (doc) {
278
278
  if (doc._id && doc._id.indexOf('card_review') === 0 && doc.courseId && doc.cardId) {
279
279
  emit(doc._id, doc.courseId + '-' + doc.cardId);
280
280
  }
281
- }`}}}]),e),userCoursesDoc=`CourseRegistrations`,userClassroomsDoc=`ClassroomRegistrations`}}),init_common=__esm({"src/impl/common/index.ts"(){init_SyncStrategy(),init_BaseUserDB(),init_userDBHelpers()}}),PouchDataLayerProvider_exports={};__export(PouchDataLayerProvider_exports,{CouchDataLayerProvider:()=>CouchDataLayerProvider});var CouchDataLayerProvider,init_PouchDataLayerProvider=__esm({"src/impl/couch/PouchDataLayerProvider.ts"(){init_logger(),init_dataDirectory(),init_auth(),init_adminDB2(),init_classroomDB2(),init_courseDB(),init_CourseSyncService(),init_common(),init_CouchDBSyncStrategy(),CouchDataLayerProvider=class{constructor(e){_defineProperty(this,`initialized`,!1),_defineProperty(this,`userDB`,void 0),_defineProperty(this,`currentUsername`,``),_defineProperty(this,`_courseIDs`,[]),e&&(this._courseIDs=e)}async initialize(){if(!this.initialized){if(typeof process<`u`&&process.versions!=null&&process.versions.node!=null){logger.info(`CouchDataLayerProvider: Running in Node.js environment, creating guest UserDB for testing.`),await initializeDataDirectory();let e=new CouchDBSyncStrategy;this.userDB=await BaseUser.instance(e)}else{let e=new CouchDBSyncStrategy;this.userDB=await BaseUser.instance(e),this.currentUsername=this.userDB.getUsername(),logger.debug(`Current username: ${this.currentUsername}`)}this.initialized=!0}}async teardown(){this.initialized=!1}getUserDB(){return this.userDB}getCourseDB(e){let t=CourseSyncService.getInstance().getLocalDB(e);return new CourseDB(e,async()=>this.getUserDB(),t??void 0)}async ensureCourseSynced(e,t){return CourseSyncService.getInstance().ensureSynced(e,t)}getCoursesDB(){return new CoursesDB(this._courseIDs)}async getClassroomDB(e,t){return t===`student`?await StudentClassroomDB.factory(e,this.getUserDB()):await TeacherClassroomDB.factory(e)}getAdminDB(){return new AdminDB}async createUserReaderForUser(e){let t=await getLoggedInUsername();if(t!==`admin`)throw Error(`Unauthorized: Only admin users can access other users' data`);logger.info(`Admin user '${t}' requesting UserDBReader for '${e}'`);let n=new CouchDBSyncStrategy;return await BaseUser.instance(n,e)}isReadOnly(){return!1}}}}),pathUtils,nodeFS,StaticDataUnpacker,init_StaticDataUnpacker=__esm({"src/impl/static/StaticDataUnpacker.ts"(){init_logger(),init_core(),pathUtils={isAbsolute:e=>!!(/^[a-zA-Z]:[\\/]/.test(e)||/^\\\\/.test(e)||e.startsWith(`/`))},nodeFS=null;try{typeof window>`u`&&typeof process<`u`&&process.versions?.node&&(nodeFS=eval(`require`)(`fs`))}catch{}StaticDataUnpacker=class{constructor(e,t){_defineProperty(this,`manifest`,void 0),_defineProperty(this,`basePath`,void 0),_defineProperty(this,`documentCache`,new Map),_defineProperty(this,`chunkCache`,new Map),_defineProperty(this,`indexCache`,new Map),this.manifest=e,this.basePath=t}async getDocument(e){if(this.documentCache.has(e)){let t=this.documentCache.get(e);return await this.hydrateAttachments(t)}let t=await this.findChunkForDocument(e);if(!t)throw logger.error(`Document ${e} not found in any chunk. Available chunks:`,this.manifest.chunks.map(e=>`${e.id} (${e.docType}): ${e.startKey} - ${e.endKey}`)),Error(`Document ${e} not found in any chunk`);if(await this.loadChunk(t.id),this.documentCache.has(e)){let t=this.documentCache.get(e);return await this.hydrateAttachments(t)}throw logger.error(`Document ${e} not found in chunk ${t.id}`),Error(`Document ${e} not found in chunk ${t.id}`)}async getAllDocumentsByPrefix(e){let t=this.manifest.chunks.filter(t=>{let n=e+`￰`;return t.startKey<=n&&t.endKey>=e});if(t.length===0)return logger.debug(`[StaticDataUnpacker] No chunks found for prefix: ${e}`),[];await Promise.all(t.map(e=>this.loadChunk(e.id)));let n=[];for(let[t,r]of this.documentCache.entries())t.startsWith(e)&&n.push(await this.hydrateAttachments(r));return logger.debug(`[StaticDataUnpacker] Found ${n.length} documents with prefix: ${e}`),n}async queryByElo(e,t=25){let n=await this.loadIndex(`elo`);if(!n||!n.sorted)return logger.warn(`ELO index not found or malformed, returning empty results`),[];let r=n.sorted,a=0,o=0,s=r.length-1;for(;o<=s;){let t=Math.floor((o+s)/2);r[t].elo<e?o=t+1:s=t-1}a=o;let c=[],l=Math.floor(t/2);for(let e=Math.max(0,a-l);e<a&&c.length<t;e++)c.push(r[e].cardId);for(let e=a;e<r.length&&c.length<t;e++)c.push(r[e].cardId);return c}async getTagsIndex(){try{return await this.loadIndex(`tags`)}catch{return{byCard:{},byTag:{}}}}getDocTypeFromId(e){for(let t in DocTypePrefixes){let n=DocTypePrefixes[t];if(e.startsWith(`${n}-`))return t}}async findChunkForDocument(e){let t=this.getDocTypeFromId(e);if(t){let n=this.manifest.chunks.filter(e=>e.docType===t);for(let t of n)if(e>=t.startKey&&e<=t.endKey&&await this.verifyDocumentInChunk(e,t))return t}else{for(let t of this.manifest.chunks)if(e>=t.startKey&&e<=t.endKey&&await this.verifyDocumentInChunk(e,t))return t;let t=this.manifest.chunks.filter(e=>e.docType!==`CARD`&&e.docType!==`DISPLAYABLE_DATA`&&e.docType!==`TAG`);for(let n of t)if(e>=n.startKey&&e<=n.endKey&&await this.verifyDocumentInChunk(e,n))return n;return}}async verifyDocumentInChunk(e,t){try{return await this.loadChunk(t.id),this.documentCache.has(e)}catch{return!1}}async loadChunk(e){if(this.chunkCache.has(e))return;let t=this.manifest.chunks.find(t=>t.id===e);if(!t)throw Error(`Chunk ${e} not found in manifest`);try{let n=`${this.basePath}/${t.path}`;logger.debug(`Loading chunk from ${n}`);let r;if(this.isLocalPath(n)&&nodeFS){let e=await nodeFS.promises.readFile(n,`utf8`);r=JSON.parse(e)}else{let t=await fetch(n);if(!t.ok)throw Error(`Failed to fetch chunk ${e}: ${t.status} ${t.statusText}`);r=await t.json()}this.chunkCache.set(e,r);for(let e of r)e._id&&this.documentCache.set(e._id,e);logger.debug(`Loaded ${r.length} documents from chunk ${e}`)}catch(t){throw logger.error(`Failed to load chunk ${e}:`,t),t}}async loadIndex(e){if(this.indexCache.has(e))return this.indexCache.get(e);let t=this.manifest.indices.find(t=>t.name===e);if(!t)throw Error(`Index ${e} not found in manifest`);try{let n=`${this.basePath}/${t.path}`;logger.debug(`Loading index from ${n}`);let r;if(this.isLocalPath(n)&&nodeFS){let e=await nodeFS.promises.readFile(n,`utf8`);r=JSON.parse(e)}else{let t=await fetch(n);if(!t.ok)throw Error(`Failed to fetch index ${e}: ${t.status} ${t.statusText}`);r=await t.json()}return this.indexCache.set(e,r),logger.debug(`Loaded index ${e}`),r}catch(t){throw logger.error(`Failed to load index ${e}:`,t),t}}async getRawDocument(e){if(this.documentCache.has(e))return this.documentCache.get(e);let t=await this.findChunkForDocument(e);if(!t)throw logger.error(`Document ${e} not found in any chunk. Available chunks:`,this.manifest.chunks.map(e=>`${e.id} (${e.docType}): ${e.startKey} - ${e.endKey}`)),Error(`Document ${e} not found in any chunk`);if(await this.loadChunk(t.id),this.documentCache.has(e))return this.documentCache.get(e);throw logger.error(`Document ${e} not found in chunk ${t.id}`),Error(`Document ${e} not found in chunk ${t.id}`)}getAttachmentUrl(e,t){return`${this.basePath}/attachments/${e}/${t}`}async getAttachmentPath(e,t){try{let n=await this.getRawDocument(e);if(n._attachments&&n._attachments[t]){let e=n._attachments[t];if(e.path)return`${this.basePath}/${e.path}`}return null}catch{return null}}async getAttachmentBlob(e,t){let n=await this.getAttachmentPath(e,t);if(!n)return null;try{if(this.isLocalPath(n)&&nodeFS)return await nodeFS.promises.readFile(n);{let r=await fetch(n);if(!r.ok)throw Error(`Failed to fetch attachment ${e}/${t}: ${r.status} ${r.statusText}`);return await r.blob()}}catch(n){return logger.error(`Failed to load attachment ${e}/${t}:`,n),null}}async hydrateAttachments(e){let t=e;if(!t._attachments)return e;let n=JSON.parse(JSON.stringify(e));for(let[e,r]of Object.entries(t._attachments)){let a=r;if(a.path)try{let r=await this.getAttachmentBlob(t._id,e);r?typeof window<`u`&&window.URL?n._attachments[e]={...a,data:r,stub:!1}:n._attachments[e]={...a,buffer:r}:logger.warn(`[hydrateAttachments] getAttachmentBlob returned null for ${t._id}/${e}. Skipping hydration for this attachment.`)}catch(n){logger.warn(`[hydrateAttachments] Failed to hydrate attachment ${t._id}/${e}:`,n)}else logger.debug(`[hydrateAttachments] Attachment ${e} for doc ${t._id} has no path. Skipping blob conversion.`)}return n}clearCaches(){this.documentCache.clear(),this.chunkCache.clear(),this.indexCache.clear()}getCacheStats(){return{documents:this.documentCache.size,chunks:this.chunkCache.size,indices:this.indexCache.size}}isLocalPath(e){return!e.startsWith(`http://`)&&!e.startsWith(`https://`)&&(pathUtils.isAbsolute(e)||e.startsWith(`./`)||e.startsWith(`../`))}}}}),StaticCourseDB,init_courseDB2=__esm({"src/impl/static/courseDB.ts"(){init_types_legacy(),init_logger(),init_defaults(),init_PipelineAssembler(),StaticCourseDB=class{constructor(e,t,n,r){_defineProperty(this,`_pendingHints`,null),this.courseId=e,this.unpacker=t,this.userDB=n,this.manifest=r}getCourseID(){return this.courseId}async getCourseConfig(){if(this.manifest.courseConfig!=null)return this.manifest.courseConfig;throw Error(`Course config not found for course ${this.courseId}`)}async updateCourseConfig(e){throw Error(`Cannot update course config in static mode`)}async getCourseInfo(){return{cardCount:this.manifest.chunks.filter(e=>e.docType===`CARD`).reduce((e,t)=>e+t.documentCount,0),registeredUsers:0}}async getCourseDoc(e,t){return this.unpacker.getDocument(e)}async getCourseDocs(e,t){let n=await Promise.all(e.map(async e=>{try{return{id:e,key:e,value:{rev:`1-static`},doc:await this.unpacker.getDocument(e)}}catch{return{key:e,error:`not_found`}}}));return{total_rows:e.length,offset:0,rows:n}}async getCardsByELO(e,t){return(await this.unpacker.queryByElo(e,t||25)).map(e=>{let[t,n,r]=e.split(`-`);return{courseID:t,cardID:n,elo:r?parseInt(r):void 0}})}async getCardEloData(e){return await Promise.all(e.map(async e=>{try{return(await this.unpacker.getDocument(e)).elo||{global:{score:1e3,count:0},tags:{},misc:{}}}catch{return{global:{score:1e3,count:0},tags:{},misc:{}}}}))}async updateCardElo(e,t){return{ok:!0,id:e,rev:`1-static`}}async getCardsCenteredAtELO(e,t){let n=typeof e.elo==`number`?e.elo:1e3;if(e.elo===`user`)try{let e=(await this.userDB.getCourseRegistrationsDoc()).courses.find(e=>e.courseID===this.courseId);e&&typeof e.elo==`object`&&(n=e.elo.global.score)}catch{n=1e3}else e.elo===`random`&&(n=800+Math.random()*400);let r=(await this.unpacker.queryByElo(n,e.limit*2)).map(e=>({cardID:e,courseID:this.courseId}));return t&&(r=r.filter(t)),r.slice(0,e.limit).map(e=>({status:`new`,cardID:e.cardID,contentSourceType:`course`,contentSourceID:this.courseId,courseID:this.courseId}))}async getAppliedTags(e){try{let t=await this.unpacker.getTagsIndex(),n=t.byCard[e]||[],r=await Promise.all(n.map(async n=>{let r=`TAG-${n}`;try{let t=await this.unpacker.getDocument(r);return{id:r,key:e,value:{name:t.name,snippet:t.snippet,count:t.taggedCards?.length||0}}}catch(a){if(a&&a.status===404)logger.warn(`Tag document not found for ${n}, creating stub`);else throw logger.error(`Error getting tag document for ${n}:`,a),a;return{id:r,key:e,value:{name:n,snippet:`Tag: ${n}`,count:t.byTag[n]?.length||0}}}}));return{total_rows:r.length,offset:0,rows:r}}catch(t){return logger.error(`Error getting applied tags for card ${e}:`,t),{total_rows:0,offset:0,rows:[]}}}async getAppliedTagsBatch(e){let t=await this.unpacker.getTagsIndex(),n=new Map;for(let r of e)n.set(r,t.byCard[r]||[]);return n}async getAllCardIds(){let e=await this.unpacker.getTagsIndex();return Object.keys(e.byCard)}async addTagToCard(e,t){throw Error(`Cannot modify tags in static mode`)}async removeTagFromCard(e,t){throw Error(`Cannot modify tags in static mode`)}async createTag(e){throw Error(`Cannot create tags in static mode`)}async getTag(e){return this.unpacker.getDocument(`TAG-${e}`)}async updateTag(e){throw Error(`Cannot update tags in static mode`)}async getCourseTagStubs(){try{let e=await this.unpacker.getTagsIndex();if(!e||!e.byTag)return logger.warn(`Tags index not found or empty`),{total_rows:0,offset:0,rows:[]};let t=Object.keys(e.byTag),n=await Promise.all(t.map(async t=>{let n=e.byTag[t]||[],r=`TAG-${t}`;try{return{id:r,key:r,value:{rev:`1-static`},doc:await this.unpacker.getDocument(r)}}catch(e){if(e&&e.status===404)return logger.warn(`Tag document not found for ${t}, creating stub`),{id:r,key:r,value:{rev:`1-static`},doc:{_id:r,_rev:`1-static`,course:this.courseId,docType:`TAG`,name:t,snippet:`Tag: ${t}`,wiki:``,taggedCards:n,author:`system`}};throw logger.error(`Error getting tag document for ${t}:`,e),e}}));return{total_rows:n.length,offset:0,rows:n}}catch(e){return logger.error(`Failed to get course tag stubs:`,e),{total_rows:0,offset:0,rows:[]}}}async addNote(e,t,n,r,a,o,s){return{status:Status.error,message:`Cannot add notes in static mode`}}async removeCard(e){throw Error(`Cannot remove cards in static mode`)}async getInexperiencedCards(){return[]}async getNavigationStrategy(e){try{return await this.unpacker.getDocument(e)}catch(t){throw logger.error(`[static/courseDB] Strategy ${e} not found: ${t}`),t}}async getAllNavigationStrategies(){let e=DocTypePrefixes.NAVIGATION_STRATEGY;try{return await this.unpacker.getAllDocumentsByPrefix(e)}catch(e){return logger.warn(`[static/courseDB] Error loading navigation strategies: ${e}`),[]}}async addNavigationStrategy(e){throw Error(`Cannot add navigation strategies in static mode`)}async updateNavigationStrategy(e,t){throw Error(`Cannot update navigation strategies in static mode`)}async createNavigator(e){try{let t=await this.getAllNavigationStrategies();if(t.length===0)return logger.debug(`[static/courseDB] No strategy documents found, using default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])`),createDefaultPipeline(e,this);let{pipeline:n,generatorStrategies:r,filterStrategies:a,warnings:o}=await new PipelineAssembler().assemble({strategies:t,user:e,course:this});for(let e of o)logger.warn(`[PipelineAssembler] ${e}`);return n?(logger.debug(`[static/courseDB] Using assembled pipeline with ${r.length} generator(s) and ${a.length} filter(s)`),n):(logger.debug(`[static/courseDB] Pipeline assembly failed, using default pipeline`),createDefaultPipeline(e,this))}catch(e){throw logger.error(`[static/courseDB] Error creating navigator: ${e}`),e}}setEphemeralHints(e){this._pendingHints=e}async getWeightedCards(e){try{let t=await this.createNavigator(this.userDB);return this._pendingHints&&(t.setEphemeralHints(this._pendingHints),this._pendingHints=null),t.getWeightedCards(e)}catch(e){throw logger.error(`[static/courseDB] Error getting weighted cards: ${e}`),e}}getAttachmentUrl(e,t){return this.unpacker.getAttachmentUrl(e,t)}async getAttachmentBlob(e,t){return this.unpacker.getAttachmentBlob(e,t)}async searchCards(e){return[]}async find(e){return{docs:[],warning:`Find operations not supported in static mode`}}}}}),StaticCoursesDB,init_coursesDB=__esm({"src/impl/static/coursesDB.ts"(){init_logger(),StaticCoursesDB=class{constructor(e,t){this.manifests=e,this.dependencyNameToCourseId=t}async getCourseConfig(e){let t=this.manifests[e];if(!t&&this.dependencyNameToCourseId){let n=this.dependencyNameToCourseId.get(e);n&&(t=this.manifests[n])}if(!t)throw logger.warn(`Course manifest for ${e} not found`),Error(`Course ${e} not found`);if(t.courseConfig)return t.courseConfig;throw logger.warn(`Course config not found in manifest for course ${e}`),Error(`Course config not found for course ${e}`)}async getCourseList(){return Object.keys(this.manifests).map(e=>({courseID:e,name:this.manifests[e].courseName}))}async disambiguateCourse(e,t){logger.warn(`Cannot disambiguate courses in static mode`)}}}}),NoOpSyncStrategy,init_NoOpSyncStrategy=__esm({"src/impl/static/NoOpSyncStrategy.ts"(){init_common(),NoOpSyncStrategy=class{constructor(){_defineProperty(this,`currentUsername`,accomodateGuest().username)}setupRemoteDB(e){return getLocalUserDB(e)}getWriteDB(e){return getLocalUserDB(e)}startSync(e,t){}stopSync(){}canCreateAccount(){return!1}canAuthenticate(){return!1}async createAccount(e,t){throw Error(`Account creation not supported in static mode. Use local account switching instead.`)}async authenticate(e,t){throw Error(`Remote authentication not supported in static mode. Use local account switching instead.`)}async logout(){return this.currentUsername=accomodateGuest().username,{ok:!0}}async getCurrentUsername(){return this.currentUsername}setCurrentUsername(e){this.currentUsername=e}}}}),StaticDataLayerProvider_exports={};__export(StaticDataLayerProvider_exports,{StaticDataLayerProvider:()=>StaticDataLayerProvider});var StaticDataLayerProvider,init_StaticDataLayerProvider=__esm({"src/impl/static/StaticDataLayerProvider.ts"(){init_logger(),init_StaticDataUnpacker(),init_courseDB2(),init_coursesDB(),init_common(),init_NoOpSyncStrategy(),StaticDataLayerProvider=class{constructor(e){_defineProperty(this,`config`,void 0),_defineProperty(this,`initialized`,!1),_defineProperty(this,`courseUnpackers`,new Map),_defineProperty(this,`manifests`,{}),_defineProperty(this,`dependencyNameToCourseId`,new Map),this.config={localStoragePrefix:e.localStoragePrefix||`skuilder-static`,rootManifest:e.rootManifest||{dependencies:{}},rootManifestUrl:e.rootManifestUrl||`/`}}async resolveCourseDependencies(){logger.info(`[StaticDataLayerProvider] Starting course dependency resolution...`);let e=this.config.rootManifest;for(let[t,n]of Object.entries(e.dependencies||{}))try{logger.debug(`[StaticDataLayerProvider] Resolving dependency: ${t} from ${n}`);let e=new URL(n,this.config.rootManifestUrl).href,r=await fetch(e);if(!r.ok)throw Error(`Failed to fetch course manifest for ${t}`);let a=await r.json();if(a.content&&a.content.manifest){let n=new URL(`.`,e).href,r=new URL(a.content.manifest,e).href,o=await fetch(r);if(!o.ok)throw Error(`Failed to fetch final content manifest for ${t} at ${r}`);let s=await o.json(),c=s.courseId||s.courseConfig?.courseID;if(!c)throw Error(`Course manifest for ${t} missing courseId`);this.manifests[c]=s;let l=new StaticDataUnpacker(s,n);this.courseUnpackers.set(c,l),this.dependencyNameToCourseId.set(t,c),logger.info(`[StaticDataLayerProvider] Successfully resolved and prepared course: ${t} (courseId: ${c})`)}}catch(e){logger.error(`[StaticDataLayerProvider] Failed to resolve dependency ${t}:`,e)}logger.info(`[StaticDataLayerProvider] Course dependency resolution complete.`)}async initialize(){this.initialized||(logger.info(`Initializing static data layer provider`),await this.resolveCourseDependencies(),this.initialized=!0)}async teardown(){this.courseUnpackers.clear(),this.initialized=!1}getUserDB(){let e=new NoOpSyncStrategy;return BaseUser.Dummy(e)}getCourseDB(e){let t=this.courseUnpackers.get(e),n=e;if(!t){let r=this.dependencyNameToCourseId.get(e);r&&(t=this.courseUnpackers.get(r),n=r)}if(!t)throw Error(`Course ${e} not found or failed to initialize in static data layer.`);let r=this.manifests[n];return new StaticCourseDB(n,t,this.getUserDB(),r)}getCoursesDB(){return new StaticCoursesDB(this.manifests,this.dependencyNameToCourseId)}async getClassroomDB(e,t){throw Error(`Classrooms not supported in static mode`)}getAdminDB(){throw Error(`Admin functions not supported in static mode`)}async createUserReaderForUser(e){return logger.warn(`StaticDataLayerProvider: Multi-user access not supported in static mode`),logger.warn(`Request: trying to access data for ${e}`),logger.warn(`Returning current user's data instead`),this.getUserDB()}isReadOnly(){return!0}}}});async function initializeDataLayer(e){if(dataLayerInstance)return logger.warn(`Data layer already initialized. Returning existing instance.`),dataLayerInstance;if(await initializeNavigatorRegistry(),e.options.localStoragePrefix&&(ENV.LOCAL_STORAGE_PREFIX=e.options.localStoragePrefix),e.type===`couch`){if(!e.options.COUCHDB_SERVER_URL||!e.options.COUCHDB_SERVER_PROTOCOL)throw Error(`Missing CouchDB server URL or protocol`);if(ENV.COUCHDB_SERVER_PROTOCOL=e.options.COUCHDB_SERVER_PROTOCOL,ENV.COUCHDB_SERVER_URL=e.options.COUCHDB_SERVER_URL,ENV.COUCHDB_USERNAME=e.options.COUCHDB_USERNAME,ENV.COUCHDB_PASSWORD=e.options.COUCHDB_PASSWORD,e.options.courseSync){let{CourseSyncService:t}=await Promise.resolve().then(()=>(init_CourseSyncService(),CourseSyncService_exports));t.getInstance().configure(e.options.courseSync)}if(e.options.COUCHDB_PASSWORD&&e.options.COUCHDB_USERNAME&&typeof window<`u`){let{CouchDBSyncStrategy:t}=await Promise.resolve().then(()=>(init_CouchDBSyncStrategy(),CouchDBSyncStrategy_exports)),n=new t,r=await(await BaseUser.instance(n,e.options.COUCHDB_USERNAME)).login(e.options.COUCHDB_USERNAME,e.options.COUCHDB_PASSWORD);r.ok?logger.info(`Successfully authenticated as ${e.options.COUCHDB_USERNAME}`):logger.warn(`Authentication failed: ${r.error}`)}let{CouchDataLayerProvider:t}=await Promise.resolve().then(()=>(init_PouchDataLayerProvider(),PouchDataLayerProvider_exports));dataLayerInstance=new t(e.options.COURSE_IDS)}else if(e.type===`static`){let{StaticDataLayerProvider:t}=await Promise.resolve().then(()=>(init_StaticDataLayerProvider(),StaticDataLayerProvider_exports));dataLayerInstance=new t(e.options)}else throw Error(`Unknown data layer type: ${e.type}`);return await dataLayerInstance.initialize(),dataLayerInstance}function getDataLayer(){if(!dataLayerInstance)throw Error(`Data layer not initialized. Call initializeDataLayer first.`);return dataLayerInstance}async function _resetDataLayer(){dataLayerInstance&&await dataLayerInstance.teardown(),dataLayerInstance=null}var NOT_SET,ENV,dataLayerInstance,init_factory=__esm({"src/factory.ts"(){init_common(),init_logger(),init_navigators(),NOT_SET=`NOT_SET`,ENV={COUCHDB_SERVER_PROTOCOL:NOT_SET,COUCHDB_SERVER_URL:NOT_SET,LOCAL_STORAGE_PREFIX:``},dataLayerInstance=null}}),TagFilteredContentSource,init_TagFilteredContentSource=__esm({"src/study/TagFilteredContentSource.ts"(){init_courseDB(),init_logger(),TagFilteredContentSource=class{constructor(e,t,n){_defineProperty(this,`courseId`,void 0),_defineProperty(this,`filter`,void 0),_defineProperty(this,`user`,void 0),_defineProperty(this,`resolvedCardIds`,null),this.courseId=e,this.filter=t,this.user=n,logger.info(`[TagFilteredContentSource] Created for course "${e}" with filter:`,JSON.stringify(t))}async resolveFilteredCardIds(){if(this.resolvedCardIds!==null)return this.resolvedCardIds;let e=new Set;if(this.filter.include.length>0)for(let t of this.filter.include)try{(await getTag(this.courseId,t)).taggedCards.forEach(t=>e.add(t))}catch(e){logger.warn(`[TagFilteredContentSource] Could not resolve tag "${t}" for inclusion:`,e)}if(e.size===0&&this.filter.include.length>0)return logger.warn(`[TagFilteredContentSource] No cards found for include tags: ${this.filter.include.join(`, `)}`),this.resolvedCardIds=new Set,this.resolvedCardIds;let t=new Set;if(this.filter.exclude.length>0)for(let e of this.filter.exclude)try{(await getTag(this.courseId,e)).taggedCards.forEach(e=>t.add(e))}catch(t){logger.warn(`[TagFilteredContentSource] Could not resolve tag "${e}" for exclusion:`,t)}let n=new Set;for(let r of e)t.has(r)||n.add(r);return logger.info(`[TagFilteredContentSource] Resolved ${n.size} cards (included: ${e.size}, excluded: ${t.size})`),this.resolvedCardIds=n,n}async getWeightedCards(e){if(!hasActiveFilter(this.filter))return logger.warn(`[TagFilteredContentSource] getWeightedCards called with no active filter`),{cards:[]};let t=await this.resolveFilteredCardIds(),n=await this.user.getActiveCards(),r=new Set(n.map(e=>e.cardID)),a=[];for(let n of t)if(r.has(n)||a.push({cardId:n,courseId:this.courseId,score:1,provenance:[{strategy:`tagFilter`,strategyName:`Tag Filter`,strategyId:`TAG_FILTER`,action:`generated`,score:1,reason:`Tag-filtered new card (tags: ${this.filter.include.join(`, `)})`}]}),a.length>=e)break;logger.info(`[TagFilteredContentSource] Found ${a.length} new cards matching filter`);let o=await this.user.getPendingReviews(this.courseId),s=o.filter(e=>t.has(e.cardId));return logger.info(`[TagFilteredContentSource] Found ${s.length} pending reviews matching filter (of ${o.length} total)`),{cards:[...s.map(e=>({cardId:e.cardId,courseId:e.courseId,score:1,reviewID:e._id,provenance:[{strategy:`tagFilter`,strategyName:`Tag Filter`,strategyId:`TAG_FILTER`,action:`generated`,score:1,reason:`Tag-filtered review (tags: ${this.filter.include.join(`, `)})`}]})),...a].slice(0,e)}}clearCache(){this.resolvedCardIds=null}getCourseId(){return this.courseId}getFilter(){return this.filter}}}});function isReview(e){return e.status===`review`||e.status===`failed-review`||`reviewID`in e}async function getStudySource(e,t){return e.type===`classroom`?await StudentClassroomDB.factory(e.id,t):hasActiveFilter(e.tagFilter)?new TagFilteredContentSource(e.id,e.tagFilter,t):getDataLayer().getCourseDB(e.id)}var init_contentSource=__esm({"src/core/interfaces/contentSource.ts"(){init_factory(),init_classroomDB2(),init_TagFilteredContentSource()}}),init_courseDB3=__esm({"src/core/interfaces/courseDB.ts"(){}}),init_dataLayerProvider=__esm({"src/core/interfaces/dataLayerProvider.ts"(){}}),init_userDB=__esm({"src/core/interfaces/userDB.ts"(){}}),init_interfaces=__esm({"src/core/interfaces/index.ts"(){init_adminDB(),init_classroomDB(),init_contentSource(),init_courseDB3(),init_dataLayerProvider(),init_userDB()}}),init_user=__esm({"src/core/types/user.ts"(){}});function buildStrategyStateId(e,t){return`STRATEGY_STATE::${e}::${t}`}var init_strategyState=__esm({"src/core/types/strategyState.ts"(){}}),init_userOutcome=__esm({"src/core/types/userOutcome.ts"(){}});async function importParsedCards(e,t,n){let r=[];for(let a of e)try{let e=await processCard(a,t,n);r.push(e)}catch(e){logger.error(`Error processing card:`,e);let t=a.markdown;a.tags&&a.tags.length>0&&(t+=`
281
+ }`}}}]),e),userCoursesDoc=`CourseRegistrations`,userClassroomsDoc=`ClassroomRegistrations`}}),init_common=__esm({"src/impl/common/index.ts"(){init_SyncStrategy(),init_BaseUserDB(),init_userDBHelpers()}}),PouchDataLayerProvider_exports={};__export(PouchDataLayerProvider_exports,{CouchDataLayerProvider:()=>CouchDataLayerProvider});var CouchDataLayerProvider,init_PouchDataLayerProvider=__esm({"src/impl/couch/PouchDataLayerProvider.ts"(){init_logger(),init_dataDirectory(),init_auth(),init_adminDB2(),init_classroomDB2(),init_courseDB(),init_CourseSyncService(),init_common(),init_CouchDBSyncStrategy(),CouchDataLayerProvider=class{constructor(e){_defineProperty(this,`initialized`,!1),_defineProperty(this,`userDB`,void 0),_defineProperty(this,`currentUsername`,``),_defineProperty(this,`_courseIDs`,[]),e&&(this._courseIDs=e)}async initialize(){if(!this.initialized){if(typeof process<`u`&&process.versions!=null&&process.versions.node!=null){logger.info(`CouchDataLayerProvider: Running in Node.js environment, creating guest UserDB for testing.`),await initializeDataDirectory();let e=new CouchDBSyncStrategy;this.userDB=await BaseUser.instance(e)}else{let e=new CouchDBSyncStrategy;this.userDB=await BaseUser.instance(e),this.currentUsername=this.userDB.getUsername(),logger.debug(`Current username: ${this.currentUsername}`)}this.initialized=!0}}async teardown(){this.initialized=!1}getUserDB(){return this.userDB}getCourseDB(e){let t=CourseSyncService.getInstance().getLocalDB(e);return new CourseDB(e,async()=>this.getUserDB(),t??void 0)}async ensureCourseSynced(e,t){return CourseSyncService.getInstance().ensureSynced(e,t)}getCoursesDB(){return new CoursesDB(this._courseIDs)}async getClassroomDB(e,t){return t===`student`?await StudentClassroomDB.factory(e,this.getUserDB()):await TeacherClassroomDB.factory(e)}getAdminDB(){return new AdminDB}async createUserReaderForUser(e){let t=await getLoggedInUsername();if(t!==`admin`)throw Error(`Unauthorized: Only admin users can access other users' data`);logger.info(`Admin user '${t}' requesting UserDBReader for '${e}'`);let n=new CouchDBSyncStrategy;return await BaseUser.instance(n,e)}isReadOnly(){return!1}}}}),pathUtils,nodeFS,StaticDataUnpacker,init_StaticDataUnpacker=__esm({"src/impl/static/StaticDataUnpacker.ts"(){init_logger(),init_core(),pathUtils={isAbsolute:e=>!!(/^[a-zA-Z]:[\\/]/.test(e)||/^\\\\/.test(e)||e.startsWith(`/`))},nodeFS=null;try{typeof window>`u`&&typeof process<`u`&&process.versions?.node&&(nodeFS=eval(`require`)(`fs`))}catch{}StaticDataUnpacker=class{constructor(e,t){_defineProperty(this,`manifest`,void 0),_defineProperty(this,`basePath`,void 0),_defineProperty(this,`documentCache`,new Map),_defineProperty(this,`chunkCache`,new Map),_defineProperty(this,`indexCache`,new Map),this.manifest=e,this.basePath=t}async getDocument(e){if(this.documentCache.has(e)){let t=this.documentCache.get(e);return await this.hydrateAttachments(t)}let t=await this.findChunkForDocument(e);if(!t)throw logger.error(`Document ${e} not found in any chunk. Available chunks:`,this.manifest.chunks.map(e=>`${e.id} (${e.docType}): ${e.startKey} - ${e.endKey}`)),Error(`Document ${e} not found in any chunk`);if(await this.loadChunk(t.id),this.documentCache.has(e)){let t=this.documentCache.get(e);return await this.hydrateAttachments(t)}throw logger.error(`Document ${e} not found in chunk ${t.id}`),Error(`Document ${e} not found in chunk ${t.id}`)}async getAllDocumentsByPrefix(e){let t=this.manifest.chunks.filter(t=>{let n=e+`￰`;return t.startKey<=n&&t.endKey>=e});if(t.length===0)return logger.debug(`[StaticDataUnpacker] No chunks found for prefix: ${e}`),[];await Promise.all(t.map(e=>this.loadChunk(e.id)));let n=[];for(let[t,r]of this.documentCache.entries())t.startsWith(e)&&n.push(await this.hydrateAttachments(r));return logger.debug(`[StaticDataUnpacker] Found ${n.length} documents with prefix: ${e}`),n}async queryByElo(e,t=25){let n=await this.loadIndex(`elo`);if(!n||!n.sorted)return logger.warn(`ELO index not found or malformed, returning empty results`),[];let r=n.sorted,a=0,o=0,s=r.length-1;for(;o<=s;){let t=Math.floor((o+s)/2);r[t].elo<e?o=t+1:s=t-1}a=o;let c=[],l=Math.floor(t/2);for(let e=Math.max(0,a-l);e<a&&c.length<t;e++)c.push(r[e].cardId);for(let e=a;e<r.length&&c.length<t;e++)c.push(r[e].cardId);return c}async getTagsIndex(){try{return await this.loadIndex(`tags`)}catch{return{byCard:{},byTag:{}}}}getDocTypeFromId(e){for(let t in DocTypePrefixes){let n=DocTypePrefixes[t];if(e.startsWith(`${n}-`))return t}}async findChunkForDocument(e){let t=this.getDocTypeFromId(e);if(t){let n=this.manifest.chunks.filter(e=>e.docType===t);for(let t of n)if(e>=t.startKey&&e<=t.endKey&&await this.verifyDocumentInChunk(e,t))return t}else{for(let t of this.manifest.chunks)if(e>=t.startKey&&e<=t.endKey&&await this.verifyDocumentInChunk(e,t))return t;let t=this.manifest.chunks.filter(e=>e.docType!==`CARD`&&e.docType!==`DISPLAYABLE_DATA`&&e.docType!==`TAG`);for(let n of t)if(e>=n.startKey&&e<=n.endKey&&await this.verifyDocumentInChunk(e,n))return n;return}}async verifyDocumentInChunk(e,t){try{return await this.loadChunk(t.id),this.documentCache.has(e)}catch{return!1}}async loadChunk(e){if(this.chunkCache.has(e))return;let t=this.manifest.chunks.find(t=>t.id===e);if(!t)throw Error(`Chunk ${e} not found in manifest`);try{let n=`${this.basePath}/${t.path}`;logger.debug(`Loading chunk from ${n}`);let r;if(this.isLocalPath(n)&&nodeFS){let e=await nodeFS.promises.readFile(n,`utf8`);r=JSON.parse(e)}else{let t=await fetch(n);if(!t.ok)throw Error(`Failed to fetch chunk ${e}: ${t.status} ${t.statusText}`);r=await t.json()}this.chunkCache.set(e,r);for(let e of r)e._id&&this.documentCache.set(e._id,e);logger.debug(`Loaded ${r.length} documents from chunk ${e}`)}catch(t){throw logger.error(`Failed to load chunk ${e}:`,t),t}}async loadIndex(e){if(this.indexCache.has(e))return this.indexCache.get(e);let t=this.manifest.indices.find(t=>t.name===e);if(!t)throw Error(`Index ${e} not found in manifest`);try{let n=`${this.basePath}/${t.path}`;logger.debug(`Loading index from ${n}`);let r;if(this.isLocalPath(n)&&nodeFS){let e=await nodeFS.promises.readFile(n,`utf8`);r=JSON.parse(e)}else{let t=await fetch(n);if(!t.ok)throw Error(`Failed to fetch index ${e}: ${t.status} ${t.statusText}`);r=await t.json()}return this.indexCache.set(e,r),logger.debug(`Loaded index ${e}`),r}catch(t){throw logger.error(`Failed to load index ${e}:`,t),t}}async getRawDocument(e){if(this.documentCache.has(e))return this.documentCache.get(e);let t=await this.findChunkForDocument(e);if(!t)throw logger.error(`Document ${e} not found in any chunk. Available chunks:`,this.manifest.chunks.map(e=>`${e.id} (${e.docType}): ${e.startKey} - ${e.endKey}`)),Error(`Document ${e} not found in any chunk`);if(await this.loadChunk(t.id),this.documentCache.has(e))return this.documentCache.get(e);throw logger.error(`Document ${e} not found in chunk ${t.id}`),Error(`Document ${e} not found in chunk ${t.id}`)}getAttachmentUrl(e,t){return`${this.basePath}/attachments/${e}/${t}`}async getAttachmentPath(e,t){try{let n=await this.getRawDocument(e);if(n._attachments&&n._attachments[t]){let e=n._attachments[t];if(e.path)return`${this.basePath}/${e.path}`}return null}catch{return null}}async getAttachmentBlob(e,t){let n=await this.getAttachmentPath(e,t);if(!n)return null;try{if(this.isLocalPath(n)&&nodeFS)return await nodeFS.promises.readFile(n);{let r=await fetch(n);if(!r.ok)throw Error(`Failed to fetch attachment ${e}/${t}: ${r.status} ${r.statusText}`);return await r.blob()}}catch(n){return logger.error(`Failed to load attachment ${e}/${t}:`,n),null}}async hydrateAttachments(e){let t=e;if(!t._attachments)return e;let n=JSON.parse(JSON.stringify(e));for(let[e,r]of Object.entries(t._attachments)){let a=r;if(a.path)try{let r=await this.getAttachmentBlob(t._id,e);r?typeof window<`u`&&window.URL?n._attachments[e]={...a,data:r,stub:!1}:n._attachments[e]={...a,buffer:r}:logger.warn(`[hydrateAttachments] getAttachmentBlob returned null for ${t._id}/${e}. Skipping hydration for this attachment.`)}catch(n){logger.warn(`[hydrateAttachments] Failed to hydrate attachment ${t._id}/${e}:`,n)}else logger.debug(`[hydrateAttachments] Attachment ${e} for doc ${t._id} has no path. Skipping blob conversion.`)}return n}clearCaches(){this.documentCache.clear(),this.chunkCache.clear(),this.indexCache.clear()}getCacheStats(){return{documents:this.documentCache.size,chunks:this.chunkCache.size,indices:this.indexCache.size}}isLocalPath(e){return!e.startsWith(`http://`)&&!e.startsWith(`https://`)&&(pathUtils.isAbsolute(e)||e.startsWith(`./`)||e.startsWith(`../`))}}}}),StaticCourseDB,init_courseDB2=__esm({"src/impl/static/courseDB.ts"(){init_types_legacy(),init_logger(),init_defaults(),init_PipelineAssembler(),StaticCourseDB=class{constructor(e,t,n,r){_defineProperty(this,`_pendingHints`,null),this.courseId=e,this.unpacker=t,this.userDB=n,this.manifest=r}getCourseID(){return this.courseId}async getCourseConfig(){if(this.manifest.courseConfig!=null)return this.manifest.courseConfig;throw Error(`Course config not found for course ${this.courseId}`)}async updateCourseConfig(e){throw Error(`Cannot update course config in static mode`)}async getCourseInfo(){return{cardCount:this.manifest.chunks.filter(e=>e.docType===`CARD`).reduce((e,t)=>e+t.documentCount,0),registeredUsers:0}}async getCourseDoc(e,t){return this.unpacker.getDocument(e)}async getCourseDocs(e,t){let n=await Promise.all(e.map(async e=>{try{return{id:e,key:e,value:{rev:`1-static`},doc:await this.unpacker.getDocument(e)}}catch{return{key:e,error:`not_found`}}}));return{total_rows:e.length,offset:0,rows:n}}async getCardsByELO(e,t){return(await this.unpacker.queryByElo(e,t||25)).map(e=>{let[t,n,r]=e.split(`-`);return{courseID:t,cardID:n,elo:r?parseInt(r):void 0}})}async getCardEloData(e){return await Promise.all(e.map(async e=>{try{return(await this.unpacker.getDocument(e)).elo||{global:{score:1e3,count:0},tags:{},misc:{}}}catch{return{global:{score:1e3,count:0},tags:{},misc:{}}}}))}async updateCardElo(e,t){return{ok:!0,id:e,rev:`1-static`}}async getCardsCenteredAtELO(e,t){let n=typeof e.elo==`number`?e.elo:1e3;if(e.elo===`user`)try{let e=(await this.userDB.getCourseRegistrationsDoc()).courses.find(e=>e.courseID===this.courseId);e&&typeof e.elo==`object`&&(n=e.elo.global.score)}catch{n=1e3}else e.elo===`random`&&(n=800+Math.random()*400);let r=(await this.unpacker.queryByElo(n,e.limit*2)).map(e=>({cardID:e,courseID:this.courseId}));return t&&(r=r.filter(t)),r.slice(0,e.limit).map(e=>({status:`new`,cardID:e.cardID,contentSourceType:`course`,contentSourceID:this.courseId,courseID:this.courseId}))}async getAppliedTags(e){try{let t=await this.unpacker.getTagsIndex(),n=t.byCard[e]||[],r=await Promise.all(n.map(async n=>{let r=`TAG-${n}`;try{let t=await this.unpacker.getDocument(r);return{id:r,key:e,value:{name:t.name,snippet:t.snippet,count:t.taggedCards?.length||0}}}catch(a){if(a&&a.status===404)logger.warn(`Tag document not found for ${n}, creating stub`);else throw logger.error(`Error getting tag document for ${n}:`,a),a;return{id:r,key:e,value:{name:n,snippet:`Tag: ${n}`,count:t.byTag[n]?.length||0}}}}));return{total_rows:r.length,offset:0,rows:r}}catch(t){return logger.error(`Error getting applied tags for card ${e}:`,t),{total_rows:0,offset:0,rows:[]}}}async getAppliedTagsBatch(e){let t=await this.unpacker.getTagsIndex(),n=new Map;for(let r of e)n.set(r,t.byCard[r]||[]);return n}async getAllCardIds(){let e=await this.unpacker.getTagsIndex();return Object.keys(e.byCard)}async addTagToCard(e,t){throw Error(`Cannot modify tags in static mode`)}async removeTagFromCard(e,t){throw Error(`Cannot modify tags in static mode`)}async createTag(e){throw Error(`Cannot create tags in static mode`)}async getTag(e){return this.unpacker.getDocument(`TAG-${e}`)}async updateTag(e){throw Error(`Cannot update tags in static mode`)}async getCourseTagStubs(){try{let e=await this.unpacker.getTagsIndex();if(!e||!e.byTag)return logger.warn(`Tags index not found or empty`),{total_rows:0,offset:0,rows:[]};let t=Object.keys(e.byTag),n=await Promise.all(t.map(async t=>{let n=e.byTag[t]||[],r=`TAG-${t}`;try{return{id:r,key:r,value:{rev:`1-static`},doc:await this.unpacker.getDocument(r)}}catch(e){if(e&&e.status===404)return logger.warn(`Tag document not found for ${t}, creating stub`),{id:r,key:r,value:{rev:`1-static`},doc:{_id:r,_rev:`1-static`,course:this.courseId,docType:`TAG`,name:t,snippet:`Tag: ${t}`,wiki:``,taggedCards:n,author:`system`}};throw logger.error(`Error getting tag document for ${t}:`,e),e}}));return{total_rows:n.length,offset:0,rows:n}}catch(e){return logger.error(`Failed to get course tag stubs:`,e),{total_rows:0,offset:0,rows:[]}}}async addNote(e,t,n,r,a,o,s){return{status:Status.error,message:`Cannot add notes in static mode`}}async removeCard(e){throw Error(`Cannot remove cards in static mode`)}async getInexperiencedCards(){return[]}async getNavigationStrategy(e){try{return await this.unpacker.getDocument(e)}catch(t){throw logger.error(`[static/courseDB] Strategy ${e} not found: ${t}`),t}}async getAllNavigationStrategies(){let e=DocTypePrefixes.NAVIGATION_STRATEGY;try{return await this.unpacker.getAllDocumentsByPrefix(e)}catch(e){return logger.warn(`[static/courseDB] Error loading navigation strategies: ${e}`),[]}}async addNavigationStrategy(e){throw Error(`Cannot add navigation strategies in static mode`)}async updateNavigationStrategy(e,t){throw Error(`Cannot update navigation strategies in static mode`)}async createNavigator(e){try{let t=await this.getAllNavigationStrategies();if(t.length===0)return logger.debug(`[static/courseDB] No strategy documents found, using default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])`),createDefaultPipeline(e,this);let{pipeline:n,generatorStrategies:r,filterStrategies:a,warnings:o}=await new PipelineAssembler().assemble({strategies:t,user:e,course:this});for(let e of o)logger.warn(`[PipelineAssembler] ${e}`);return n?(logger.debug(`[static/courseDB] Using assembled pipeline with ${r.length} generator(s) and ${a.length} filter(s)`),n):(logger.debug(`[static/courseDB] Pipeline assembly failed, using default pipeline`),createDefaultPipeline(e,this))}catch(e){throw logger.error(`[static/courseDB] Error creating navigator: ${e}`),e}}setEphemeralHints(e){this._pendingHints=e}async getWeightedCards(e){try{let t=await this.createNavigator(this.userDB);return this._pendingHints&&(t.setEphemeralHints(this._pendingHints),this._pendingHints=null),t.getWeightedCards(e)}catch(e){throw logger.error(`[static/courseDB] Error getting weighted cards: ${e}`),e}}getAttachmentUrl(e,t){return this.unpacker.getAttachmentUrl(e,t)}async getAttachmentBlob(e,t){return this.unpacker.getAttachmentBlob(e,t)}async searchCards(e){return[]}async find(e){return{docs:[],warning:`Find operations not supported in static mode`}}}}}),StaticCoursesDB,init_coursesDB=__esm({"src/impl/static/coursesDB.ts"(){init_logger(),StaticCoursesDB=class{constructor(e,t){this.manifests=e,this.dependencyNameToCourseId=t}async getCourseConfig(e){let t=this.manifests[e];if(!t&&this.dependencyNameToCourseId){let n=this.dependencyNameToCourseId.get(e);n&&(t=this.manifests[n])}if(!t)throw logger.warn(`Course manifest for ${e} not found`),Error(`Course ${e} not found`);if(t.courseConfig)return t.courseConfig;throw logger.warn(`Course config not found in manifest for course ${e}`),Error(`Course config not found for course ${e}`)}async getCourseList(){return Object.keys(this.manifests).map(e=>({courseID:e,name:this.manifests[e].courseName}))}async disambiguateCourse(e,t){logger.warn(`Cannot disambiguate courses in static mode`)}}}}),NoOpSyncStrategy,init_NoOpSyncStrategy=__esm({"src/impl/static/NoOpSyncStrategy.ts"(){init_common(),NoOpSyncStrategy=class{constructor(){_defineProperty(this,`currentUsername`,accomodateGuest().username)}setupRemoteDB(e){return getLocalUserDB(e)}getWriteDB(e){return getLocalUserDB(e)}startSync(e,t){}stopSync(){}canCreateAccount(){return!1}canAuthenticate(){return!1}async createAccount(e,t){throw Error(`Account creation not supported in static mode. Use local account switching instead.`)}async authenticate(e,t){throw Error(`Remote authentication not supported in static mode. Use local account switching instead.`)}async logout(){return this.currentUsername=accomodateGuest().username,{ok:!0}}async getCurrentUsername(){return this.currentUsername}setCurrentUsername(e){this.currentUsername=e}}}}),StaticDataLayerProvider_exports={};__export(StaticDataLayerProvider_exports,{StaticDataLayerProvider:()=>StaticDataLayerProvider});var StaticDataLayerProvider,init_StaticDataLayerProvider=__esm({"src/impl/static/StaticDataLayerProvider.ts"(){init_logger(),init_StaticDataUnpacker(),init_courseDB2(),init_coursesDB(),init_common(),init_NoOpSyncStrategy(),StaticDataLayerProvider=class{constructor(e){_defineProperty(this,`config`,void 0),_defineProperty(this,`initialized`,!1),_defineProperty(this,`courseUnpackers`,new Map),_defineProperty(this,`manifests`,{}),_defineProperty(this,`dependencyNameToCourseId`,new Map),this.config={localStoragePrefix:e.localStoragePrefix||`skuilder-static`,rootManifest:e.rootManifest||{dependencies:{}},rootManifestUrl:e.rootManifestUrl||`/`}}async resolveCourseDependencies(){logger.info(`[StaticDataLayerProvider] Starting course dependency resolution...`);let e=this.config.rootManifest;for(let[t,n]of Object.entries(e.dependencies||{}))try{logger.debug(`[StaticDataLayerProvider] Resolving dependency: ${t} from ${n}`);let e=new URL(n,this.config.rootManifestUrl).href,r=await fetch(e);if(!r.ok)throw Error(`Failed to fetch course manifest for ${t}`);let a=await r.json();if(a.content&&a.content.manifest){let n=new URL(`.`,e).href,r=new URL(a.content.manifest,e).href,o=await fetch(r);if(!o.ok)throw Error(`Failed to fetch final content manifest for ${t} at ${r}`);let s=await o.json(),c=s.courseId||s.courseConfig?.courseID;if(!c)throw Error(`Course manifest for ${t} missing courseId`);this.manifests[c]=s;let l=new StaticDataUnpacker(s,n);this.courseUnpackers.set(c,l),this.dependencyNameToCourseId.set(t,c),logger.info(`[StaticDataLayerProvider] Successfully resolved and prepared course: ${t} (courseId: ${c})`)}}catch(e){logger.error(`[StaticDataLayerProvider] Failed to resolve dependency ${t}:`,e)}logger.info(`[StaticDataLayerProvider] Course dependency resolution complete.`)}async initialize(){this.initialized||(logger.info(`Initializing static data layer provider`),await this.resolveCourseDependencies(),this.initialized=!0)}async teardown(){this.courseUnpackers.clear(),this.initialized=!1}getUserDB(){let e=new NoOpSyncStrategy;return BaseUser.Dummy(e)}getCourseDB(e){let t=this.courseUnpackers.get(e),n=e;if(!t){let r=this.dependencyNameToCourseId.get(e);r&&(t=this.courseUnpackers.get(r),n=r)}if(!t)throw Error(`Course ${e} not found or failed to initialize in static data layer.`);let r=this.manifests[n];return new StaticCourseDB(n,t,this.getUserDB(),r)}getCoursesDB(){return new StaticCoursesDB(this.manifests,this.dependencyNameToCourseId)}async getClassroomDB(e,t){throw Error(`Classrooms not supported in static mode`)}getAdminDB(){throw Error(`Admin functions not supported in static mode`)}async createUserReaderForUser(e){return logger.warn(`StaticDataLayerProvider: Multi-user access not supported in static mode`),logger.warn(`Request: trying to access data for ${e}`),logger.warn(`Returning current user's data instead`),this.getUserDB()}isReadOnly(){return!0}}}});async function initializeDataLayer(e){if(dataLayerInstance)return logger.warn(`Data layer already initialized. Returning existing instance.`),dataLayerInstance;if(await initializeNavigatorRegistry(),e.options.localStoragePrefix&&(ENV.LOCAL_STORAGE_PREFIX=e.options.localStoragePrefix),e.type===`couch`){if(!e.options.COUCHDB_SERVER_URL||!e.options.COUCHDB_SERVER_PROTOCOL)throw Error(`Missing CouchDB server URL or protocol`);if(ENV.COUCHDB_SERVER_PROTOCOL=e.options.COUCHDB_SERVER_PROTOCOL,ENV.COUCHDB_SERVER_URL=e.options.COUCHDB_SERVER_URL,ENV.COUCHDB_USERNAME=e.options.COUCHDB_USERNAME,ENV.COUCHDB_PASSWORD=e.options.COUCHDB_PASSWORD,e.options.courseSync){let{CourseSyncService:t}=await Promise.resolve().then(()=>(init_CourseSyncService(),CourseSyncService_exports));t.getInstance().configure(e.options.courseSync)}if(e.options.COUCHDB_PASSWORD&&e.options.COUCHDB_USERNAME&&typeof window<`u`){let{CouchDBSyncStrategy:t}=await Promise.resolve().then(()=>(init_CouchDBSyncStrategy(),CouchDBSyncStrategy_exports)),n=new t,r=await(await BaseUser.instance(n,e.options.COUCHDB_USERNAME)).login(e.options.COUCHDB_USERNAME,e.options.COUCHDB_PASSWORD);r.ok?logger.info(`Successfully authenticated as ${e.options.COUCHDB_USERNAME}`):logger.warn(`Authentication failed: ${r.error}`)}let{CouchDataLayerProvider:t}=await Promise.resolve().then(()=>(init_PouchDataLayerProvider(),PouchDataLayerProvider_exports));dataLayerInstance=new t(e.options.COURSE_IDS)}else if(e.type===`static`){let{StaticDataLayerProvider:t}=await Promise.resolve().then(()=>(init_StaticDataLayerProvider(),StaticDataLayerProvider_exports));dataLayerInstance=new t(e.options)}else throw Error(`Unknown data layer type: ${e.type}`);return await dataLayerInstance.initialize(),dataLayerInstance}function getDataLayer(){if(!dataLayerInstance)throw Error(`Data layer not initialized. Call initializeDataLayer first.`);return dataLayerInstance}async function _resetDataLayer(){dataLayerInstance&&await dataLayerInstance.teardown(),dataLayerInstance=null}var NOT_SET,ENV,dataLayerInstance,init_factory=__esm({"src/factory.ts"(){init_common(),init_logger(),init_navigators(),NOT_SET=`NOT_SET`,ENV={COUCHDB_SERVER_PROTOCOL:NOT_SET,COUCHDB_SERVER_URL:NOT_SET,LOCAL_STORAGE_PREFIX:``},dataLayerInstance=null}}),TagFilteredContentSource,init_TagFilteredContentSource=__esm({"src/study/TagFilteredContentSource.ts"(){init_courseDB(),init_logger(),TagFilteredContentSource=class{constructor(e,t,n){_defineProperty(this,`courseId`,void 0),_defineProperty(this,`filter`,void 0),_defineProperty(this,`user`,void 0),_defineProperty(this,`resolvedCardIds`,null),this.courseId=e,this.filter=t,this.user=n,logger.info(`[TagFilteredContentSource] Created for course "${e}" with filter:`,JSON.stringify(t))}async resolveFilteredCardIds(){if(this.resolvedCardIds!==null)return this.resolvedCardIds;let e=new Set;if(this.filter.include.length>0)for(let t of this.filter.include)try{(await getTag(this.courseId,t)).taggedCards.forEach(t=>e.add(t))}catch(e){logger.warn(`[TagFilteredContentSource] Could not resolve tag "${t}" for inclusion:`,e)}if(e.size===0&&this.filter.include.length>0)return logger.warn(`[TagFilteredContentSource] No cards found for include tags: ${this.filter.include.join(`, `)}`),this.resolvedCardIds=new Set,this.resolvedCardIds;let t=new Set;if(this.filter.exclude.length>0)for(let e of this.filter.exclude)try{(await getTag(this.courseId,e)).taggedCards.forEach(e=>t.add(e))}catch(t){logger.warn(`[TagFilteredContentSource] Could not resolve tag "${e}" for exclusion:`,t)}let n=new Set;for(let r of e)t.has(r)||n.add(r);return logger.info(`[TagFilteredContentSource] Resolved ${n.size} cards (included: ${e.size}, excluded: ${t.size})`),this.resolvedCardIds=n,n}async getWeightedCards(e){if(!hasActiveFilter(this.filter))return logger.warn(`[TagFilteredContentSource] getWeightedCards called with no active filter`),{cards:[]};let t=await this.resolveFilteredCardIds(),n=await this.user.getActiveCards(),r=new Set(n.map(e=>e.cardID)),a=[];for(let n of t)if(r.has(n)||a.push({cardId:n,courseId:this.courseId,score:1,provenance:[{strategy:`tagFilter`,strategyName:`Tag Filter`,strategyId:`TAG_FILTER`,action:`generated`,score:1,reason:`Tag-filtered new card (tags: ${this.filter.include.join(`, `)})`}]}),a.length>=e)break;logger.info(`[TagFilteredContentSource] Found ${a.length} new cards matching filter`);let o=await this.user.getPendingReviews(this.courseId),s=o.filter(e=>t.has(e.cardId));return logger.info(`[TagFilteredContentSource] Found ${s.length} pending reviews matching filter (of ${o.length} total)`),{cards:[...s.map(e=>({cardId:e.cardId,courseId:e.courseId,score:1,reviewID:e._id,provenance:[{strategy:`tagFilter`,strategyName:`Tag Filter`,strategyId:`TAG_FILTER`,action:`generated`,score:1,reason:`Tag-filtered review (tags: ${this.filter.include.join(`, `)})`}]})),...a].slice(0,e)}}clearCache(){this.resolvedCardIds=null}getCourseId(){return this.courseId}getFilter(){return this.filter}}}});function isReview(e){return e.status===`review`||e.status===`failed-review`||`reviewID`in e}async function getStudySource(e,t){return e.type===`classroom`?await StudentClassroomDB.factory(e.id,t):hasActiveFilter(e.tagFilter)?new TagFilteredContentSource(e.id,e.tagFilter,t):getDataLayer().getCourseDB(e.id)}var init_contentSource=__esm({"src/core/interfaces/contentSource.ts"(){init_factory(),init_classroomDB2(),init_TagFilteredContentSource()}}),init_courseDB3=__esm({"src/core/interfaces/courseDB.ts"(){}}),init_dataLayerProvider=__esm({"src/core/interfaces/dataLayerProvider.ts"(){}}),init_userDB=__esm({"src/core/interfaces/userDB.ts"(){}}),init_interfaces=__esm({"src/core/interfaces/index.ts"(){init_adminDB(),init_classroomDB(),init_contentSource(),init_courseDB3(),init_dataLayerProvider(),init_userDB()}}),init_user=__esm({"src/core/types/user.ts"(){}});function buildStrategyStateId(e,t){return`STRATEGY_STATE::${e}::${t}`}var init_strategyState=__esm({"src/core/types/strategyState.ts"(){}}),init_userOutcome=__esm({"src/core/types/userOutcome.ts"(){}}),HYDRATION_MARKER_ID,init_hydration=__esm({"src/core/types/hydration.ts"(){HYDRATION_MARKER_ID=`_local/hydration`}});async function importParsedCards(e,t,n){let r=[];for(let a of e)try{let e=await processCard(a,t,n);r.push(e)}catch(e){logger.error(`Error processing card:`,e);let t=a.markdown;a.tags&&a.tags.length>0&&(t+=`
282
282
  tags: ${a.tags.join(`, `)}`),a.elo!==void 0&&(t+=`
283
283
  elo: ${a.elo}`),r.push({originalText:t,status:`error`,message:`Error processing card: ${e instanceof Error?e.message:`Unknown error`}`})}return r}async function processCard(e,t,n){let{markdown:r,tags:a,elo:o}=e,s=r;a.length>0&&(s+=`
284
284
  tags: ${a.join(`, `)}`),o!==void 0&&(s+=`
@@ -305,7 +305,7 @@ Examples:
305
305
  window.skuilder.userdb.showScheduledReviews('course123')
306
306
  window.skuilder.userdb.queryByType('SCHEDULED_CARD', 10)
307
307
  window.skuilder.userdb.raw(db => db.allDocs({ limit: 5 }))
308
- `)}},mountUserDBDebugger()}}),init_core=__esm({"src/core/index.ts"(){init_interfaces(),init_types_legacy(),init_user(),init_strategyState(),init_userOutcome(),init_Loggable(),init_util(),init_navigators(),init_bulkImport(),init_orchestration(),init_UserDBDebugger()}});init_core(),init_courseLookupDB(),init_logger();function isDataShapeRegistered(e,t){let n=NameSpacer.getDataShapeString({dataShape:e.name,course:e.course});return t.dataShapes.find(e=>e.name===n)!==void 0}function isDataShapeSchemaAvailable(e,t){let n=NameSpacer.getDataShapeString({dataShape:e.name,course:e.course}),r=t.dataShapes.find(e=>e.name===n);return r!==void 0&&r.serializedZodSchema!==void 0}function isQuestionTypeRegistered(e,t){let n=NameSpacer.getQuestionString({course:e.course,questionType:e.name});return t.questionTypes.some(e=>e.name===n)}function processCustomQuestionsData(e){let t=[],n=[],r=e.courses.map(e=>e.name);return e.questionClasses.forEach(a=>{let o=r.length>0?r[0]:e.meta.packageName;a.dataShapes&&Array.isArray(a.dataShapes)&&a.dataShapes.forEach(e=>{t.push({name:e.name,course:o,dataShape:e})}),n.push({name:a.name,course:o,questionClass:a,dataShapes:a.dataShapes||[],views:a.views||[]})}),{dataShapes:t,questions:n}}function registerDataShape(e,t){let n=NameSpacer.getDataShapeString({dataShape:e.name,course:e.course}),r;try{r=toZodJSON(e.dataShape)}catch(e){logger.warn(`Failed to generate schema for ${n}:`,e),r=void 0}let a=t.dataShapes.findIndex(e=>e.name===n);if(a!==-1){let o=t.dataShapes[a];return o.serializedZodSchema===r?(logger.info(`DataShape '${e.name}' from '${e.course}' already registered with identical schema`),!1):(o.serializedZodSchema?logger.info(`DataShape '${e.name}' from '${e.course}' schema has changed, updating...`):logger.info(`DataShape '${e.name}' from '${e.course}' already registered, but with no schema. Adding schema to existing entry`),t.dataShapes[a]={name:n,questionTypes:o.questionTypes,serializedZodSchema:r},logger.info(`Updated DataShape: ${n}`),!0)}return t.dataShapes.push({name:n,questionTypes:[],serializedZodSchema:r}),logger.info(`Registered DataShape: ${n}`),!0}function registerQuestionType(e,t){if(isQuestionTypeRegistered(e,t))return logger.info(`QuestionType '${e.name}' from '${e.course}' already registered`),!1;let n=NameSpacer.getQuestionString({course:e.course,questionType:e.name}),r=e.views.map(e=>e.name?e.name:`unnamedComponent`),a=e.dataShapes.map(t=>NameSpacer.getDataShapeString({course:e.course,dataShape:t.name}));return t.questionTypes.push({name:n,viewList:r,dataShapeList:a}),e.dataShapes.forEach(r=>{let a=NameSpacer.getDataShapeString({course:e.course,dataShape:r.name});for(let e of t.dataShapes)e.name===a&&e.questionTypes.push(n)}),logger.info(`Registered QuestionType: ${n}`),!0}function removeDataShape(e,t){let n=t.dataShapes.findIndex(t=>t.name===e);return n===-1?(logger.info(`DataShape '${e}' not found in course config`),!1):(t.dataShapes.splice(n,1),t.questionTypes.forEach(t=>{let n=t.dataShapeList.indexOf(e);n!==-1&&t.dataShapeList.splice(n,1)}),logger.info(`Removed DataShape: ${e}`),!0)}function removeQuestionType(e,t){let n=t.questionTypes.findIndex(t=>t.name===e);return n===-1?(logger.info(`QuestionType '${e}' not found in course config`),!1):(t.questionTypes.splice(n,1),t.dataShapes.forEach(t=>{let n=t.questionTypes.indexOf(e);n!==-1&&t.questionTypes.splice(n,1)}),logger.info(`Removed QuestionType: ${e}`),!0)}async function removeCustomQuestionTypes(e,t,n,r){try{logger.info(`Beginning custom question removal`),logger.info(`Removing ${e.length} data shapes and ${t.length} question types`);let a=0;for(let e of t)removeQuestionType(e,n)&&a++;for(let t of e)removeDataShape(t,n)&&a++;logger.info(`Updating course configuration...`);let o=await r.updateCourseConfig(n);if(!o.ok)throw Error(`Failed to update course config: ${JSON.stringify(o)}`);return logger.info(`Custom question removal complete: ${a} items removed`),{success:!0,removedCount:a}}catch(e){let t=e instanceof Error?e.message:String(e);return logger.error(`Custom question removal failed: ${t}`),{success:!1,removedCount:0,errorMessage:t}}}async function registerSeedData(e,t,n){if(e.questionClass.seedData&&Array.isArray(e.questionClass.seedData)){logger.info(`Registering seed data for question: ${e.name}`);try{let r=e.questionClass.seedData.filter(()=>e.dataShapes.length>0).map(r=>t.addNote(e.course,e.dataShapes[0],r,n,[]));await Promise.all(r),logger.info(`Seed data registered for question: ${e.name}`)}catch(t){logger.warn(`Failed to register seed data for question '${e.name}': ${t instanceof Error?t.message:String(t)}`)}}}async function registerBlanksCard(e,t,n,r,a){try{logger.info(`Registering BlanksCard data shapes and question type...`);let o=0,s=`default`;for(let e of t)registerDataShape({name:e.name,course:s,dataShape:e},n)&&o++;let c={name:e.name,course:s,questionClass:{name:e.name,dataShapes:t,views:e.views||[]},dataShapes:t,views:e.views||[]};registerQuestionType(c,n)&&o++,logger.info(`Updating course configuration with BlanksCard...`);let l=await r.updateCourseConfig(n);if(!l.ok)throw Error(`Failed to update course config: ${JSON.stringify(l)}`);return a&&await registerSeedData(c,r,a),logger.info(`BlanksCard registration complete: ${o} items registered`),{success:!0}}catch(e){let t=e instanceof Error?e.message:String(e);return logger.error(`BlanksCard registration failed: ${t}`),{success:!1,errorMessage:t}}}async function registerCustomQuestionTypes(e,t,n,r){try{logger.info(`Beginning custom question registration`),logger.info(`Processing ${e.questionClasses.length} question classes`);let{dataShapes:a,questions:o}=processCustomQuestionsData(e);logger.info(`Found ${a.length} data shapes and ${o.length} questions`);let s=0;logger.info(`Registering data shapes...`);for(let e of a)registerDataShape(e,t)&&s++;logger.info(`Registering question types...`);for(let e of o)registerQuestionType(e,t)&&s++;logger.info(`Updating course configuration...`);let c=await n.updateCourseConfig(t);if(!c.ok)throw Error(`Failed to update course config: ${JSON.stringify(c)}`);if(r){logger.info(`Registering seed data...`);for(let e of o)await registerSeedData(e,n,r)}return logger.info(`Custom question registration complete: ${s} items registered`),{success:!0,registeredCount:s}}catch(e){let t=e instanceof Error?e.message:String(e);return logger.error(`Custom question registration failed: ${t}`),{success:!1,registeredCount:0,errorMessage:t}}}init_couch(),init_util(),init_logger();var duration=hooks.duration;function newInterval(e,t){return areQuestionRecords(t)?newQuestionInterval(e,t):1e5}function newQuestionInterval(e,t){let n=t.records,r=n[n.length-1],a=lastSuccessfulInterval(n);if(a>t.bestInterval&&(t.bestInterval=a,e.update(t._id,{bestInterval:a}).catch(e=>{logger.warn(`[SpacedRepetition] Failed to update bestInterval for ${t._id}: ${e?.message??e}`)})),r.isCorrect){let e=r.performance,n=isTaggedPerformance(e)?e._global:e,o=Math.min(1,Math.max(0,n));logger.debug(`Demontrated skill: ${o}`);let s=a*(.75+o);if(t.lapses=getLapses(t.records),t.streak=getStreak(t.records),t.lapses&&t.streak&&t.bestInterval&&(t.lapses>=0||t.streak>=0)){let e=(t.lapses*s+t.streak*t.bestInterval)/(t.lapses+t.streak);return logger.debug(`Weighted average interval calculation:
308
+ `)}},mountUserDBDebugger()}}),init_core=__esm({"src/core/index.ts"(){init_interfaces(),init_types_legacy(),init_user(),init_strategyState(),init_userOutcome(),init_hydration(),init_Loggable(),init_util(),init_navigators(),init_bulkImport(),init_orchestration(),init_UserDBDebugger()}});init_core(),init_courseLookupDB(),init_logger();function isDataShapeRegistered(e,t){let n=NameSpacer.getDataShapeString({dataShape:e.name,course:e.course});return t.dataShapes.find(e=>e.name===n)!==void 0}function isDataShapeSchemaAvailable(e,t){let n=NameSpacer.getDataShapeString({dataShape:e.name,course:e.course}),r=t.dataShapes.find(e=>e.name===n);return r!==void 0&&r.serializedZodSchema!==void 0}function isQuestionTypeRegistered(e,t){let n=NameSpacer.getQuestionString({course:e.course,questionType:e.name});return t.questionTypes.some(e=>e.name===n)}function processCustomQuestionsData(e){let t=[],n=[],r=e.courses.map(e=>e.name);return e.questionClasses.forEach(a=>{let o=r.length>0?r[0]:e.meta.packageName;a.dataShapes&&Array.isArray(a.dataShapes)&&a.dataShapes.forEach(e=>{t.push({name:e.name,course:o,dataShape:e})}),n.push({name:a.name,course:o,questionClass:a,dataShapes:a.dataShapes||[],views:a.views||[]})}),{dataShapes:t,questions:n}}function registerDataShape(e,t){let n=NameSpacer.getDataShapeString({dataShape:e.name,course:e.course}),r;try{r=toZodJSON(e.dataShape)}catch(e){logger.warn(`Failed to generate schema for ${n}:`,e),r=void 0}let a=t.dataShapes.findIndex(e=>e.name===n);if(a!==-1){let o=t.dataShapes[a];return o.serializedZodSchema===r?(logger.info(`DataShape '${e.name}' from '${e.course}' already registered with identical schema`),!1):(o.serializedZodSchema?logger.info(`DataShape '${e.name}' from '${e.course}' schema has changed, updating...`):logger.info(`DataShape '${e.name}' from '${e.course}' already registered, but with no schema. Adding schema to existing entry`),t.dataShapes[a]={name:n,questionTypes:o.questionTypes,serializedZodSchema:r},logger.info(`Updated DataShape: ${n}`),!0)}return t.dataShapes.push({name:n,questionTypes:[],serializedZodSchema:r}),logger.info(`Registered DataShape: ${n}`),!0}function registerQuestionType(e,t){if(isQuestionTypeRegistered(e,t))return logger.info(`QuestionType '${e.name}' from '${e.course}' already registered`),!1;let n=NameSpacer.getQuestionString({course:e.course,questionType:e.name}),r=e.views.map(e=>e.name?e.name:`unnamedComponent`),a=e.dataShapes.map(t=>NameSpacer.getDataShapeString({course:e.course,dataShape:t.name}));return t.questionTypes.push({name:n,viewList:r,dataShapeList:a}),e.dataShapes.forEach(r=>{let a=NameSpacer.getDataShapeString({course:e.course,dataShape:r.name});for(let e of t.dataShapes)e.name===a&&e.questionTypes.push(n)}),logger.info(`Registered QuestionType: ${n}`),!0}function removeDataShape(e,t){let n=t.dataShapes.findIndex(t=>t.name===e);return n===-1?(logger.info(`DataShape '${e}' not found in course config`),!1):(t.dataShapes.splice(n,1),t.questionTypes.forEach(t=>{let n=t.dataShapeList.indexOf(e);n!==-1&&t.dataShapeList.splice(n,1)}),logger.info(`Removed DataShape: ${e}`),!0)}function removeQuestionType(e,t){let n=t.questionTypes.findIndex(t=>t.name===e);return n===-1?(logger.info(`QuestionType '${e}' not found in course config`),!1):(t.questionTypes.splice(n,1),t.dataShapes.forEach(t=>{let n=t.questionTypes.indexOf(e);n!==-1&&t.questionTypes.splice(n,1)}),logger.info(`Removed QuestionType: ${e}`),!0)}async function removeCustomQuestionTypes(e,t,n,r){try{logger.info(`Beginning custom question removal`),logger.info(`Removing ${e.length} data shapes and ${t.length} question types`);let a=0;for(let e of t)removeQuestionType(e,n)&&a++;for(let t of e)removeDataShape(t,n)&&a++;logger.info(`Updating course configuration...`);let o=await r.updateCourseConfig(n);if(!o.ok)throw Error(`Failed to update course config: ${JSON.stringify(o)}`);return logger.info(`Custom question removal complete: ${a} items removed`),{success:!0,removedCount:a}}catch(e){let t=e instanceof Error?e.message:String(e);return logger.error(`Custom question removal failed: ${t}`),{success:!1,removedCount:0,errorMessage:t}}}async function registerSeedData(e,t,n){if(e.questionClass.seedData&&Array.isArray(e.questionClass.seedData)){logger.info(`Registering seed data for question: ${e.name}`);try{let r=e.questionClass.seedData.filter(()=>e.dataShapes.length>0).map(r=>t.addNote(e.course,e.dataShapes[0],r,n,[]));await Promise.all(r),logger.info(`Seed data registered for question: ${e.name}`)}catch(t){logger.warn(`Failed to register seed data for question '${e.name}': ${t instanceof Error?t.message:String(t)}`)}}}async function registerBlanksCard(e,t,n,r,a){try{logger.info(`Registering BlanksCard data shapes and question type...`);let o=0,s=`default`;for(let e of t)registerDataShape({name:e.name,course:s,dataShape:e},n)&&o++;let c={name:e.name,course:s,questionClass:{name:e.name,dataShapes:t,views:e.views||[]},dataShapes:t,views:e.views||[]};registerQuestionType(c,n)&&o++,logger.info(`Updating course configuration with BlanksCard...`);let l=await r.updateCourseConfig(n);if(!l.ok)throw Error(`Failed to update course config: ${JSON.stringify(l)}`);return a&&await registerSeedData(c,r,a),logger.info(`BlanksCard registration complete: ${o} items registered`),{success:!0}}catch(e){let t=e instanceof Error?e.message:String(e);return logger.error(`BlanksCard registration failed: ${t}`),{success:!1,errorMessage:t}}}async function registerCustomQuestionTypes(e,t,n,r){try{logger.info(`Beginning custom question registration`),logger.info(`Processing ${e.questionClasses.length} question classes`);let{dataShapes:a,questions:o}=processCustomQuestionsData(e);logger.info(`Found ${a.length} data shapes and ${o.length} questions`);let s=0;logger.info(`Registering data shapes...`);for(let e of a)registerDataShape(e,t)&&s++;logger.info(`Registering question types...`);for(let e of o)registerQuestionType(e,t)&&s++;logger.info(`Updating course configuration...`);let c=await n.updateCourseConfig(t);if(!c.ok)throw Error(`Failed to update course config: ${JSON.stringify(c)}`);if(r){logger.info(`Registering seed data...`);for(let e of o)await registerSeedData(e,n,r)}return logger.info(`Custom question registration complete: ${s} items registered`),{success:!0,registeredCount:s}}catch(e){let t=e instanceof Error?e.message:String(e);return logger.error(`Custom question registration failed: ${t}`),{success:!1,registeredCount:0,errorMessage:t}}}init_couch(),init_util(),init_logger();var duration=hooks.duration;function newInterval(e,t){return areQuestionRecords(t)?newQuestionInterval(e,t):1e5}function newQuestionInterval(e,t){let n=t.records,r=n[n.length-1],a=lastSuccessfulInterval(n);if(a>t.bestInterval&&(t.bestInterval=a,e.update(t._id,{bestInterval:a}).catch(e=>{logger.warn(`[SpacedRepetition] Failed to update bestInterval for ${t._id}: ${e?.message??e}`)})),r.isCorrect){let e=r.performance,n=isTaggedPerformance(e)?e._global:e,o=Math.min(1,Math.max(0,n));logger.debug(`Demontrated skill: ${o}`);let s=a*(.75+o);if(t.lapses=getLapses(t.records),t.streak=getStreak(t.records),t.lapses&&t.streak&&t.bestInterval&&(t.lapses>=0||t.streak>=0)){let e=(t.lapses*s+t.streak*t.bestInterval)/(t.lapses+t.streak);return logger.debug(`Weighted average interval calculation:
309
309
  (${t.lapses} * ${s} + ${t.streak} * ${t.bestInterval}) / (${t.lapses} + ${t.streak}) = ${e}`),e}else return s}else return 0}function lastSuccessfulInterval(e){for(let t=e.length-1;t>=1;t--)if(e[t].priorAttemps===0&&e[t].isCorrect){let n=secondsBetween(e[t-1].timeStamp,e[t].timeStamp),r=Math.max(n,1200*60);return logger.debug(`Last interval w/ this card was: ${n}s, returning ${r}s`),r}return getInitialInterval(e)}function getStreak(e){let t=0,n=e.length-1;for(;n>=0&&e[n].isCorrect;)n--,t++;return t}function getLapses(e){return e.filter(e=>e.isCorrect===!1).length}function getInitialInterval(e){return logger.warn(`history of length: ${e.length} ignored!`),3600*24*3}function secondsBetween(e,t){return e=hooks(e),t=hooks(t),duration(t.diff(e)).asSeconds()}init_logger();var SrsService=class{constructor(e){_defineProperty(this,`user`,void 0),this.user=e}removeReview(e){logger.info(`[SrsService] Removing orphaned scheduled review: ${e}`),this.user.removeScheduledCardReview(e)}async scheduleReview(e,t){let n=newInterval(this.user,e),r=hooks.utc().add(n,`seconds`);isReview(t)&&(logger.info(`[SrsService] Removing previously scheduled review for: ${t.cardID}`),this.user.removeScheduledCardReview(t.reviewID)),this.user.scheduleCardReview({user:this.user.getUsername(),course_id:e.courseID,card_id:e.cardID,time:r,scheduledFor:t.contentSourceType,schedulingAgentId:t.contentSourceID})}};init_logger();var EloService=class{constructor(e,t){_defineProperty(this,`dataLayer`,void 0),_defineProperty(this,`user`,void 0),this.dataLayer=e,this.user=t}async updateUserAndCardElo(e,t,n,r,a,o){o&&logger.warn(`k value interpretation not currently implemented`);let s=this.dataLayer.getCourseDB(a.card.course_id),c=r.courses.find(e=>e.courseID===t);if(!c){logger.error(`[EloService] No registration for course ${t} on user's registration doc \u2014 skipping ELO update for card ${n}. (Is the user registered for this course?)`);return}let l=toCourseElo(c.elo),u=(await s.getCardEloData([a.card.card_id]))[0];if(u&&l){let r=adjustCourseScores(l,u,e);c.elo=r.userElo;let a=await Promise.allSettled([this.user.updateUserElo(t,r.userElo),s.updateCardElo(n,r.cardElo)]),o=a[0].status===`fulfilled`,d=a[1].status===`fulfilled`;if(o&&d){let e=a[0].value,t=a[1].value;e.ok&&t&&t.ok&&logger.info(`[EloService] Updated ELOS:
310
310
  User: ${JSON.stringify(r.userElo)})
311
311
  Card: ${JSON.stringify(r.cardElo)})
@@ -340,10 +340,10 @@ Example:
340
340
  window.skuilder.mixer.showLastMix()
341
341
  window.skuilder.mixer.explainSourceBalance()
342
342
  window.skuilder.mixer.compareScores()
343
- `)}};function mountMixerDebugger(){if(typeof window>`u`)return;let e=window;e.skuilder=e.skuilder||{},e.skuilder.mixer=mixerDebugAPI}mountMixerDebugger(),init_logger(),init_PipelineDebugger(),init_SrsDebugger(),init_logger();var activeController=null;function registerActiveController(e){activeController=e}function getActiveController(){return activeController}var OVERLAY_ID=`skuilder-session-overlay`,POLL_MS=300,INLINE_THRESHOLD=5,SPINNER_FRAMES=[`⠋`,`⠙`,`⠹`,`⠸`,`⠼`,`⠴`,`⠦`,`⠧`,`⠇`,`⠏`],spinnerFrame=0,overlayEl=null,pollHandle=null,lastSnapshot=null,copyFlashUntil=0,minified=!1,expanded={supplyQ:!1,failedQ:!1,drawn:!1};function toggleSessionOverlay(){if(typeof document>`u`){logger.info(`[Session Overlay] No DOM available (non-browser host); overlay unavailable.`);return}overlayEl?(teardown(),logger.info(`[Session Overlay] Hidden.`)):(mount(),logger.info(`[Session Overlay] Shown. Toggle off with window.skuilder.session.dbgOverlay().`))}function mount(){minified=!1,overlayEl=document.createElement(`div`),overlayEl.id=OVERLAY_ID,Object.assign(overlayEl.style,{position:`fixed`,top:`8px`,left:`8px`,zIndex:`2147483647`,maxWidth:`320px`,maxHeight:`90vh`,overflowY:`auto`,padding:`8px 10px`,background:`rgba(17, 24, 39, 0.92)`,color:`#e5e7eb`,font:`11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace`,borderRadius:`6px`,boxShadow:`0 4px 16px rgba(0,0,0,0.4)`,pointerEvents:`auto`,userSelect:`none`}),document.body.appendChild(overlayEl),render(),pollHandle=setInterval(render,POLL_MS)}function teardown(){pollHandle!==null&&(clearInterval(pollHandle),pollHandle=null),overlayEl?.parentNode&&overlayEl.parentNode.removeChild(overlayEl),overlayEl=null}function render(){if(!overlayEl)return;spinnerFrame++;let e=getActiveController();if(!e){lastSnapshot=null,overlayEl.innerHTML=headerHtml()+(minified?``:`<div style="opacity:.65">No active session.</div>`),attachHandlers();return}let t=e.getDebugSnapshot();if(lastSnapshot=t,minified){overlayEl.innerHTML=headerHtml(),attachHandlers();return}overlayEl.innerHTML=headerHtml()+replanHtml(t)+metaHtml(t)+hintsHtml(t.sessionHints)+backlogHtml(t.reviewBacklog)+queueHtml(`supplyQ`,`supplyQ`,t.supplyQ)+queueHtml(`failedQ`,`failedQ`,t.failedQ)+drawnHtml(`drawn`,t.drawnCards),attachHandlers()}function attachHandlers(){if(!overlayEl)return;overlayEl.querySelectorAll(`[data-q]`).forEach(e=>{e.onclick=()=>{let t=e.dataset.q;t&&(expanded[t]=!expanded[t],render())}});let e=overlayEl.querySelector(`[data-copy]`);e&&(e.onclick=e=>{e.stopPropagation(),copySnapshot()});let t=overlayEl.querySelector(`[data-min]`);t&&(t.onclick=e=>{e.stopPropagation(),minified=!minified,render()})}function copySnapshot(){let e=snapshotToText(lastSnapshot),flash=()=>{copyFlashUntil=Date.now()+1200,render()},t=typeof navigator<`u`?navigator.clipboard:void 0;t?.writeText?t.writeText(e).then(flash,e=>{logger.warn(`[Session Overlay] Clipboard write failed: ${String(e)}`)}):logger.info(`[Session Overlay] Clipboard unavailable; snapshot follows:
344
- ${e}`)}function headerHtml(){let e=Date.now()<copyFlashUntil;return`<div style="font-weight:600;color:#93c5fd;margin-bottom:${minified?0:4}px">${`<span data-copy style="cursor:pointer;float:right;font-weight:400;color:${e?`#86efac`:`#93c5fd`};border:1px solid currentColor;border-radius:4px;padding:0 4px;line-height:1.3">${e?`✓ copied`:`⎘ copy`}</span>`}${`<span data-min title="${minified?`Restore`:`Minify`}" style="cursor:pointer;float:right;font-weight:400;color:#93c5fd;border:1px solid currentColor;border-radius:4px;padding:0 5px;margin-right:4px;line-height:1.3">${minified?`▢`:`—`}</span>`}\u2699 SessionController</div>`}function replanHtml(e){return e.replanActive?`<div style="margin-bottom:6px;color:#fde047">${SPINNER_FRAMES[spinnerFrame%SPINNER_FRAMES.length]} replanning <span style="opacity:.85">[${esc(e.replanLabel??`(auto)`)}]</span></div>`:`<div style="margin-bottom:6px;opacity:.45">○ idle</div>`}function metaHtml(e){return`<div style="margin-bottom:6px">${[`time ${formatTime(e.secondsRemaining)}${e.hasCardGuarantee?` \xB7 <span style="color:#fbbf24">guarantee ${e.minCardsGuarantee}</span>`:``}`,`well-indicated left: ${e.wellIndicatedRemaining}`,`current: ${e.currentCard?esc(e.currentCard):`<span style="opacity:.6">—</span>`}`].map(e=>`<div>${e}</div>`).join(``)}</div>`}function hintsHtml(e){let t=[];return e&&(e.boostTags&&Object.keys(e.boostTags).length&&t.push(`boost: `+Object.entries(e.boostTags).map(([e,t])=>`${esc(e)}<span style="opacity:.6">\xD7${t}</span>`).join(`, `)),e.boostCards&&Object.keys(e.boostCards).length&&t.push(`boostCards: `+Object.entries(e.boostCards).map(([e,t])=>`${esc(e)}<span style="opacity:.6">\xD7${t}</span>`).join(`, `)),e.requireCards?.length&&t.push(`require: ${e.requireCards.map(esc).join(`, `)}`),e.requireTags?.length&&t.push(`requireTags: ${e.requireTags.map(esc).join(`, `)}`),e.excludeTags?.length&&t.push(`exclude: ${e.excludeTags.map(esc).join(`, `)}`),e.excludeCards?.length&&t.push(`excludeCards: ${e.excludeCards.map(esc).join(`, `)}`)),`<div style="margin-bottom:6px"><div style="color:#86efac">sessionHints</div>${t.length?t.map(e=>`<div style="margin-left:6px">${e}</div>`).join(``):`<div style="margin-left:6px;opacity:.6">none</div>`}</div>`}function backlogHtml(e){return e.length?`<div style="margin-bottom:6px"><div style="color:#93c5fd">review backpressure</div>${e.map(e=>{let t=e.backlogMultiplier>=3,n=e.backlogMultiplier<=1?`#86efac`:t?`#fca5a5`:`#fcd34d`,r=e.backlogMultiplier>1?`climbing (\xD7${e.backlogGrowthRate.toFixed(2)} per ${e.healthyBacklog}-review excess, unbounded)`:`healthy — no pressure`,a=e.topReviewScore===null?`—`:e.topReviewScore.toFixed(2),o=e.nextDueIn?` <span style="opacity:.6">\xB7 next due ${esc(e.nextDueIn)}</span>`:``;return`<div style="margin-left:6px"><span style="opacity:.7">${esc(e.courseId.slice(0,8))}</span> due ${e.dueNow}/${e.scheduledTotal} <span style="opacity:.6">(healthy ${e.healthyBacklog})</span>${o}<div style="margin-left:6px">pressure <span style="color:${n}">\xD7${e.backlogMultiplier.toFixed(2)}</span> <span style="opacity:.6">${r} \xB7 top review ${a}</span></div></div>`}).join(``)}</div>`:``}function fmtScore(e){return e===void 0?``:Number.isFinite(e)?e.toFixed(2):`REQ`}function queueItemHtml(e){let t=e.origin===`review`?`#93c5fd`:`#fcd34d`,n=fmtScore(e.score),r=`${e.origin===`review`?`r`:`n`}${n?` `+n:``}`;return`${esc(e.cardID)}<span style="color:${t};opacity:.85"> [${r}]</span>`}function queueHtml(e,t,n){let r=n.length>INLINE_THRESHOLD,a=r&&expanded[e],o=r?expanded[e]?`▾ `:`▸ `:``,s=n.dequeueCount?` <span style="opacity:.5">drawn ${n.dequeueCount}</span>`:``,c=r?`cursor:pointer;color:#f9a8d4`:`color:#f9a8d4`,l=`<div${r?` data-q="${e}"`:``} style="${c}">${o}${t}: ${n.length}${s}</div>`;if(!n.cards.length)return l+`<div style="margin:1px 0 6px 6px;opacity:.5">empty</div>`;let u=a?n.cards:n.cards.slice(0,INLINE_THRESHOLD),d=n.length-u.length,p=`<ol style="margin:2px 0 ${r?2:6}px 0;padding-left:20px">`+u.map(e=>`<li style="white-space:nowrap">${queueItemHtml(e)}</li>`).join(``)+`</ol>`;if(r){let t=a?`▾ show less`:`\u2026 +${d} more`;p+=`<div data-q="${e}" style="cursor:pointer;margin:0 0 6px 20px;opacity:.6">${t}</div>`}return l+p}function outcomeGlyph(e){return e===!0?`<span style="color:#86efac">✓</span>`:e===!1?`<span style="color:#fca5a5">✗</span>`:`<span style="opacity:.5">·</span>`}function drawnHtml(e,t){let n=t.length>INLINE_THRESHOLD,r=n&&expanded[e],a=n?expanded[e]?`▾ `:`▸ `:``,o=n?`cursor:pointer;color:#c4b5fd`:`color:#c4b5fd`,s=`<div${n?` data-q="${e}"`:``} style="${o}">${a}drawn: ${t.length}</div>`;if(!t.length)return s+`<div style="margin:1px 0 6px 6px;opacity:.5">none yet</div>`;let c=r?t:t.slice(0,INLINE_THRESHOLD),l=t.length-c.length,u=`<ol style="margin:2px 0 ${n?2:6}px 0;padding-left:20px">${c.map(e=>{let t=e.attempts>1?`<span style="opacity:.5"> \xD7${e.attempts}</span>`:``,n=`<span style="opacity:.45"> ${Math.round(e.timeSpentMs/100)/10}s</span>`;return`<li style="white-space:nowrap">${outcomeGlyph(e.correct)} ${esc(e.cardID)}<span style="opacity:.5"> [${esc(e.status)}]</span>${t}${n}</li>`}).join(``)}</ol>`;if(n){let t=r?`▾ show less`:`\u2026 +${l} more`;u+=`<div data-q="${e}" style="cursor:pointer;margin:0 0 6px 20px;opacity:.6">${t}</div>`}return s+u}function snapshotToText(e){if(!e)return`SessionController — no active session.`;let t=[];t.push(`=== SessionController ===`),t.push(`time ${formatTime(e.secondsRemaining)}`),e.hasCardGuarantee&&t.push(`guarantee: ${e.minCardsGuarantee}`),t.push(`well-indicated left: ${e.wellIndicatedRemaining}`),t.push(`current: ${e.currentCard??`—`}`),t.push(e.replanActive?`replan: ACTIVE [${e.replanLabel??`(auto)`}]`:`replan: idle`),t.push(``),t.push(`sessionHints:`);let n=e.sessionHints,r=[];if(n&&(n.boostTags&&Object.keys(n.boostTags).length&&r.push(` boost: ${Object.entries(n.boostTags).map(([e,t])=>`${e}\xD7${t}`).join(`, `)}`),n.boostCards&&Object.keys(n.boostCards).length&&r.push(` boostCards: ${Object.entries(n.boostCards).map(([e,t])=>`${e}\xD7${t}`).join(`, `)}`),n.requireCards?.length&&r.push(` require: ${n.requireCards.join(`, `)}`),n.requireTags?.length&&r.push(` requireTags: ${n.requireTags.join(`, `)}`),n.excludeTags?.length&&r.push(` exclude: ${n.excludeTags.join(`, `)}`),n.excludeCards?.length&&r.push(` excludeCards: ${n.excludeCards.join(`, `)}`)),t.push(r.length?r.join(`
345
- `):` none`),e.reviewBacklog.length){t.push(``),t.push(`review backpressure:`);for(let n of e.reviewBacklog){let e=n.backlogMultiplier>1?`climbing`:`healthy`,r=n.topReviewScore===null?`—`:n.topReviewScore.toFixed(2),a=n.nextDueIn?`, next due ${n.nextDueIn}`:``;t.push(` ${n.courseId.slice(0,8)}: due ${n.dueNow}/${n.scheduledTotal} (healthy ${n.healthyBacklog})${a}; pressure \xD7${n.backlogMultiplier.toFixed(2)} ${e}; top review ${r}`)}}let queueText=(e,n)=>{t.push(``),t.push(`${e}: ${n.length} (drawn ${n.dequeueCount})`),n.cards.forEach((e,n)=>{let r=fmtScore(e.score),a=`${e.origin===`review`?`r`:`n`}${r?` `+r:``}`;t.push(` ${n+1}. ${e.cardID} [${a}]`)})};return queueText(`supplyQ`,e.supplyQ),queueText(`failedQ`,e.failedQ),t.push(``),t.push(`drawn: ${e.drawnCards.length}`),e.drawnCards.forEach((e,n)=>{let r=e.correct===!0?`✓`:e.correct===!1?`✗`:`·`,a=`${Math.round(e.timeSpentMs/100)/10}s`;t.push(` ${n+1}. ${r} ${e.cardID} [${e.status}] \xD7${e.attempts} ${a}`)}),t.join(`
346
- `)}function formatTime(e){let t=Math.max(0,Math.round(e));return`${Math.floor(t/60)}:${(t%60).toString().padStart(2,`0`)}`}function esc(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`)}var activeSession=null,sessionHistory=[],MAX_HISTORY=5;function clearStaleSessionDebugState(){clearRunHistory(),clearSrsBacklogDebug()}function startSessionTracking(e,t){let n=`session-${Date.now()}-${Math.random().toString(36).slice(2,8)}`;activeSession={sessionId:n,startTime:new Date,initialQueues:{timestamp:new Date,supplyQLength:e,failedQLength:t},presentations:[],queueSnapshots:[]},logger.debug(`[SessionDebugger] Started tracking session: ${n}`)}function recordCardPresentation(e,t,n,r,a,o){if(!activeSession){logger.warn(`[SessionDebugger] No active session to record presentation`);return}activeSession.presentations.push({timestamp:new Date,sequenceNumber:activeSession.presentations.length+1,cardId:e,courseId:t,courseName:n,origin:r,queueSource:a,score:o})}function snapshotQueues(e,t,n){activeSession&&activeSession.queueSnapshots.push({timestamp:new Date,supplyQLength:e,failedQLength:t,supplyQNext3:n})}function endSessionTracking(){activeSession&&(activeSession.endTime=new Date,sessionHistory.unshift(activeSession),sessionHistory.length>MAX_HISTORY&&sessionHistory.pop(),logger.debug(`[SessionDebugger] Ended session: ${activeSession.sessionId}`),activeSession=null)}function showCurrentQueue(){if(!activeSession){logger.info(`[Session Debug] No active session.`);return}let e=activeSession.queueSnapshots[activeSession.queueSnapshots.length-1]||activeSession.initialQueues;console.group(`📊 Current Queue State`),logger.info(`Supply Queue: ${e.supplyQLength} cards`),e.supplyQNext3&&e.supplyQNext3.length>0&&logger.info(` Next: ${e.supplyQNext3.join(`, `)}`),logger.info(`Failed Queue: ${e.failedQLength} cards`),console.groupEnd()}function showPresentationHistory(e=0){let t=e===0&&activeSession?activeSession:sessionHistory[e];if(!t){logger.info(`[Session Debug] No session found at index ${e}`);return}console.group(`\u{1F4DC} Session History: ${t.sessionId}`),logger.info(`Started: ${t.startTime.toLocaleTimeString()}`),t.endTime&&logger.info(`Ended: ${t.endTime.toLocaleTimeString()}`),logger.info(`Cards presented: ${t.presentations.length}`),t.presentations.length>0&&console.table(t.presentations.map(e=>({"#":e.sequenceNumber,course:e.courseName||e.courseId.slice(0,8),origin:e.origin,queue:e.queueSource,score:e.score?.toFixed(3)||`-`,time:e.timestamp.toLocaleTimeString()}))),console.groupEnd()}function showInterleaving(e=0){let t=e===0&&activeSession?activeSession:sessionHistory[e];if(!t){logger.info(`[Session Debug] No session found at index ${e}`);return}console.group(`🔀 Interleaving Analysis`);let n=new Map,r=new Map;if(t.presentations.forEach(e=>{let t=e.courseName||e.courseId;n.set(t,(n.get(t)||0)+1),r.has(t)||r.set(t,{review:0,new:0,failed:0});let a=r.get(t);a[e.origin]++}),logger.info(`Course distribution:`),console.table(Array.from(n.entries()).map(([e,n])=>{let a=r.get(e);return{course:e,total:n,reviews:a.review,new:a.new,failed:a.failed,percentage:(n/t.presentations.length*100).toFixed(1)+`%`}})),t.presentations.length>0){logger.info(`
343
+ `)}};function mountMixerDebugger(){if(typeof window>`u`)return;let e=window;e.skuilder=e.skuilder||{},e.skuilder.mixer=mixerDebugAPI}mountMixerDebugger(),init_logger(),init_PipelineDebugger(),init_SrsDebugger(),init_StrategyPressureDebugger(),init_logger();var activeController=null;function registerActiveController(e){activeController=e}function getActiveController(){return activeController}var OVERLAY_ID=`skuilder-session-overlay`,POLL_MS=300,INLINE_THRESHOLD=5,SPINNER_FRAMES=[`⠋`,`⠙`,`⠹`,`⠸`,`⠼`,`⠴`,`⠦`,`⠧`,`⠇`,`⠏`],spinnerFrame=0,overlayEl=null,pollHandle=null,lastSnapshot=null,copyFlashUntil=0,minified=!1,expanded={supplyQ:!1,failedQ:!1,drawn:!1};function toggleSessionOverlay(){if(typeof document>`u`){logger.info(`[Session Overlay] No DOM available (non-browser host); overlay unavailable.`);return}overlayEl?(teardown(),logger.info(`[Session Overlay] Hidden.`)):(mount(),logger.info(`[Session Overlay] Shown. Toggle off with window.skuilder.session.dbgOverlay().`))}function mount(){minified=!1,overlayEl=document.createElement(`div`),overlayEl.id=OVERLAY_ID,Object.assign(overlayEl.style,{position:`fixed`,top:`8px`,left:`8px`,zIndex:`2147483647`,maxWidth:`320px`,maxHeight:`90vh`,overflowY:`auto`,padding:`8px 10px`,background:`rgba(17, 24, 39, 0.92)`,color:`#e5e7eb`,font:`11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace`,borderRadius:`6px`,boxShadow:`0 4px 16px rgba(0,0,0,0.4)`,pointerEvents:`auto`,userSelect:`none`}),document.body.appendChild(overlayEl),render(),pollHandle=setInterval(render,POLL_MS)}function teardown(){pollHandle!==null&&(clearInterval(pollHandle),pollHandle=null),overlayEl?.parentNode&&overlayEl.parentNode.removeChild(overlayEl),overlayEl=null}function render(){if(!overlayEl)return;spinnerFrame++;let e=getActiveController();if(!e){lastSnapshot=null,overlayEl.innerHTML=headerHtml()+(minified?``:`<div style="opacity:.65">No active session.</div>`),attachHandlers();return}let t=e.getDebugSnapshot();if(lastSnapshot=t,minified){overlayEl.innerHTML=headerHtml(),attachHandlers();return}overlayEl.innerHTML=headerHtml()+replanHtml(t)+metaHtml(t)+hintsHtml(t.sessionHints)+backlogHtml(t.reviewBacklog)+strategyPressureHtml(t.strategyPressure)+queueHtml(`supplyQ`,`supplyQ`,t.supplyQ)+queueHtml(`failedQ`,`failedQ`,t.failedQ)+drawnHtml(`drawn`,t.drawnCards),attachHandlers()}function attachHandlers(){if(!overlayEl)return;overlayEl.querySelectorAll(`[data-q]`).forEach(e=>{e.onclick=()=>{let t=e.dataset.q;t&&(expanded[t]=!expanded[t],render())}});let e=overlayEl.querySelector(`[data-copy]`);e&&(e.onclick=e=>{e.stopPropagation(),copySnapshot()});let t=overlayEl.querySelector(`[data-min]`);t&&(t.onclick=e=>{e.stopPropagation(),minified=!minified,render()})}function copySnapshot(){let e=snapshotToText(lastSnapshot),flash=()=>{copyFlashUntil=Date.now()+1200,render()},t=typeof navigator<`u`?navigator.clipboard:void 0;t?.writeText?t.writeText(e).then(flash,e=>{logger.warn(`[Session Overlay] Clipboard write failed: ${String(e)}`)}):logger.info(`[Session Overlay] Clipboard unavailable; snapshot follows:
344
+ ${e}`)}function headerHtml(){let e=Date.now()<copyFlashUntil;return`<div style="font-weight:600;color:#93c5fd;margin-bottom:${minified?0:4}px">${`<span data-copy style="cursor:pointer;float:right;font-weight:400;color:${e?`#86efac`:`#93c5fd`};border:1px solid currentColor;border-radius:4px;padding:0 4px;line-height:1.3">${e?`✓ copied`:`⎘ copy`}</span>`}${`<span data-min title="${minified?`Restore`:`Minify`}" style="cursor:pointer;float:right;font-weight:400;color:#93c5fd;border:1px solid currentColor;border-radius:4px;padding:0 5px;margin-right:4px;line-height:1.3">${minified?`▢`:`—`}</span>`}\u2699 SessionController</div>`}function replanHtml(e){return e.replanActive?`<div style="margin-bottom:6px;color:#fde047">${SPINNER_FRAMES[spinnerFrame%SPINNER_FRAMES.length]} replanning <span style="opacity:.85">[${esc(e.replanLabel??`(auto)`)}]</span></div>`:`<div style="margin-bottom:6px;opacity:.45">○ idle</div>`}function metaHtml(e){return`<div style="margin-bottom:6px">${[`time ${formatTime(e.secondsRemaining)}${e.hasCardGuarantee?` \xB7 <span style="color:#fbbf24">guarantee ${e.minCardsGuarantee}</span>`:``}`,`well-indicated left: ${e.wellIndicatedRemaining}`,`current: ${e.currentCard?esc(e.currentCard):`<span style="opacity:.6">—</span>`}`].map(e=>`<div>${e}</div>`).join(``)}</div>`}function hintsHtml(e){let t=[];return e&&(e.boostTags&&Object.keys(e.boostTags).length&&t.push(`boost: `+Object.entries(e.boostTags).map(([e,t])=>`${esc(e)}<span style="opacity:.6">\xD7${t}</span>`).join(`, `)),e.boostCards&&Object.keys(e.boostCards).length&&t.push(`boostCards: `+Object.entries(e.boostCards).map(([e,t])=>`${esc(e)}<span style="opacity:.6">\xD7${t}</span>`).join(`, `)),e.requireCards?.length&&t.push(`require: ${e.requireCards.map(esc).join(`, `)}`),e.requireTags?.length&&t.push(`requireTags: ${e.requireTags.map(esc).join(`, `)}`),e.excludeTags?.length&&t.push(`exclude: ${e.excludeTags.map(esc).join(`, `)}`),e.excludeCards?.length&&t.push(`excludeCards: ${e.excludeCards.map(esc).join(`, `)}`)),`<div style="margin-bottom:6px"><div style="color:#86efac">sessionHints</div>${t.length?t.map(e=>`<div style="margin-left:6px">${e}</div>`).join(``):`<div style="margin-left:6px;opacity:.6">none</div>`}</div>`}function backlogHtml(e){return e.length?`<div style="margin-bottom:6px"><div style="color:#93c5fd">review backpressure</div>${e.map(e=>{let t=e.backlogMultiplier>=3,n=e.backlogMultiplier<=1?`#86efac`:t?`#fca5a5`:`#fcd34d`,r=e.backlogMultiplier>1?`climbing (\xD7${e.backlogGrowthRate.toFixed(2)} per ${e.healthyBacklog}-review excess, unbounded)`:`healthy — no pressure`,a=e.topReviewScore===null?`—`:e.topReviewScore.toFixed(2),o=e.nextDueIn?` <span style="opacity:.6">\xB7 next due ${esc(e.nextDueIn)}</span>`:``;return`<div style="margin-left:6px"><span style="opacity:.7">${esc(e.courseId.slice(0,8))}</span> due ${e.dueNow}/${e.scheduledTotal} <span style="opacity:.6">(healthy ${e.healthyBacklog})</span>${o}<div style="margin-left:6px">pressure <span style="color:${n}">\xD7${e.backlogMultiplier.toFixed(2)}</span> <span style="opacity:.6">${r} \xB7 top review ${a}</span></div></div>`}).join(``)}</div>`:``}function pressureColor(e,t){return e<=1.001?`#86efac`:t!==void 0&&t>1?(e-1)/(t-1)>=.75?`#fca5a5`:`#fcd34d`:e>=3?`#fca5a5`:`#fcd34d`}function toggleKey(e){return e.replace(/[^\w:.|-]/g,`_`)}function gaugeHtml(e,t){let n=t.max===void 0?``:`<span style="opacity:.5">/${t.max}</span>`,r=t.detail?` <span style="opacity:.6">${esc(t.detail)}</span>`:``,a=`<div style="margin-left:6px">${esc(t.label)} <span style="color:${pressureColor(t.multiplier,t.max)}">\xD7${t.multiplier.toFixed(2)}</span>${n}${r}`,o=t.items??[];if(o.length>0){let n=toggleKey(`sp|${e}|${t.id}`),r=!!expanded[n],s=r?o:o.slice(0,INLINE_THRESHOLD);if(a+=`<div style="margin-left:6px">`+s.map(e=>`<div style="white-space:nowrap;opacity:.75">${esc(e.label)}${e.value?` <span style="opacity:.6">${esc(e.value)}</span>`:``}</div>`).join(``),o.length>INLINE_THRESHOLD){let e=r?`▾ show less`:`\u2026 +${o.length-s.length} more`;a+=`<div data-q="${n}" style="cursor:pointer;opacity:.6">${e}</div>`}a+=`</div>`}return a+`</div>`}function strategyPressureHtml(e){return e.length?`<div style="margin-bottom:6px"><div style="color:#fdba74">strategy backpressure</div>${e.map(e=>{let t=`${e.source}:${e.courseId.slice(0,8)}`,n=e.topScore!==null&&e.topScore!==void 0?e.topScore.toFixed(2):`—`,r=e.hintsLabel?`<div style="margin-left:6px;opacity:.6">hints: ${esc(e.hintsLabel)}</div>`:``;return`<div style="margin-left:6px"><span style="opacity:.7">${esc(e.source)} \xB7 ${esc(e.courseId.slice(0,8))}</span> <span style="opacity:.6">top score ${n}</span>`+e.gauges.map(e=>gaugeHtml(t,e)).join(``)+r+`</div>`}).join(``)}</div>`:``}function fmtScore(e){return e===void 0?``:Number.isFinite(e)?e.toFixed(2):`REQ`}function queueItemHtml(e){let t=e.origin===`review`?`#93c5fd`:`#fcd34d`,n=fmtScore(e.score),r=`${e.origin===`review`?`r`:`n`}${n?` `+n:``}`;return`${esc(e.cardID)}<span style="color:${t};opacity:.85"> [${r}]</span>`}function queueHtml(e,t,n){let r=n.length>INLINE_THRESHOLD,a=r&&expanded[e],o=r?expanded[e]?`▾ `:`▸ `:``,s=n.dequeueCount?` <span style="opacity:.5">drawn ${n.dequeueCount}</span>`:``,c=r?`cursor:pointer;color:#f9a8d4`:`color:#f9a8d4`,l=`<div${r?` data-q="${e}"`:``} style="${c}">${o}${t}: ${n.length}${s}</div>`;if(!n.cards.length)return l+`<div style="margin:1px 0 6px 6px;opacity:.5">empty</div>`;let u=a?n.cards:n.cards.slice(0,INLINE_THRESHOLD),d=n.length-u.length,p=`<ol style="margin:2px 0 ${r?2:6}px 0;padding-left:20px">`+u.map(e=>`<li style="white-space:nowrap">${queueItemHtml(e)}</li>`).join(``)+`</ol>`;if(r){let t=a?`▾ show less`:`\u2026 +${d} more`;p+=`<div data-q="${e}" style="cursor:pointer;margin:0 0 6px 20px;opacity:.6">${t}</div>`}return l+p}function outcomeGlyph(e){return e===!0?`<span style="color:#86efac">✓</span>`:e===!1?`<span style="color:#fca5a5">✗</span>`:`<span style="opacity:.5">·</span>`}function drawnHtml(e,t){let n=t.length>INLINE_THRESHOLD,r=n&&expanded[e],a=n?expanded[e]?`▾ `:`▸ `:``,o=n?`cursor:pointer;color:#c4b5fd`:`color:#c4b5fd`,s=`<div${n?` data-q="${e}"`:``} style="${o}">${a}drawn: ${t.length}</div>`;if(!t.length)return s+`<div style="margin:1px 0 6px 6px;opacity:.5">none yet</div>`;let c=r?t:t.slice(0,INLINE_THRESHOLD),l=t.length-c.length,u=`<ol style="margin:2px 0 ${n?2:6}px 0;padding-left:20px">${c.map(e=>{let t=e.attempts>1?`<span style="opacity:.5"> \xD7${e.attempts}</span>`:``,n=`<span style="opacity:.45"> ${Math.round(e.timeSpentMs/100)/10}s</span>`;return`<li style="white-space:nowrap">${outcomeGlyph(e.correct)} ${esc(e.cardID)}<span style="opacity:.5"> [${esc(e.status)}]</span>${t}${n}</li>`}).join(``)}</ol>`;if(n){let t=r?`▾ show less`:`\u2026 +${l} more`;u+=`<div data-q="${e}" style="cursor:pointer;margin:0 0 6px 20px;opacity:.6">${t}</div>`}return s+u}function snapshotToText(e){if(!e)return`SessionController — no active session.`;let t=[];t.push(`=== SessionController ===`),t.push(`time ${formatTime(e.secondsRemaining)}`),e.hasCardGuarantee&&t.push(`guarantee: ${e.minCardsGuarantee}`),t.push(`well-indicated left: ${e.wellIndicatedRemaining}`),t.push(`current: ${e.currentCard??`—`}`),t.push(e.replanActive?`replan: ACTIVE [${e.replanLabel??`(auto)`}]`:`replan: idle`),t.push(``),t.push(`sessionHints:`);let n=e.sessionHints,r=[];if(n&&(n.boostTags&&Object.keys(n.boostTags).length&&r.push(` boost: ${Object.entries(n.boostTags).map(([e,t])=>`${e}\xD7${t}`).join(`, `)}`),n.boostCards&&Object.keys(n.boostCards).length&&r.push(` boostCards: ${Object.entries(n.boostCards).map(([e,t])=>`${e}\xD7${t}`).join(`, `)}`),n.requireCards?.length&&r.push(` require: ${n.requireCards.join(`, `)}`),n.requireTags?.length&&r.push(` requireTags: ${n.requireTags.join(`, `)}`),n.excludeTags?.length&&r.push(` exclude: ${n.excludeTags.join(`, `)}`),n.excludeCards?.length&&r.push(` excludeCards: ${n.excludeCards.join(`, `)}`)),t.push(r.length?r.join(`
345
+ `):` none`),e.reviewBacklog.length){t.push(``),t.push(`review backpressure:`);for(let n of e.reviewBacklog){let e=n.backlogMultiplier>1?`climbing`:`healthy`,r=n.topReviewScore===null?`—`:n.topReviewScore.toFixed(2),a=n.nextDueIn?`, next due ${n.nextDueIn}`:``;t.push(` ${n.courseId.slice(0,8)}: due ${n.dueNow}/${n.scheduledTotal} (healthy ${n.healthyBacklog})${a}; pressure \xD7${n.backlogMultiplier.toFixed(2)} ${e}; top review ${r}`)}}if(e.strategyPressure.length){t.push(``),t.push(`strategy backpressure:`);for(let n of e.strategyPressure){let e=n.topScore!==null&&n.topScore!==void 0?n.topScore.toFixed(2):`—`;t.push(` ${n.source} (${n.courseId.slice(0,8)}): top score ${e}`);for(let e of n.gauges){let n=e.max===void 0?``:`/${e.max}`;t.push(` ${e.label} \xD7${e.multiplier.toFixed(2)}${n}${e.detail?` \u2014 ${e.detail}`:``}`);for(let n of e.items??[])t.push(` ${n.label}${n.value?` (${n.value})`:``}`)}n.hintsLabel&&t.push(` hints: ${n.hintsLabel}`)}}let queueText=(e,n)=>{t.push(``),t.push(`${e}: ${n.length} (drawn ${n.dequeueCount})`),n.cards.forEach((e,n)=>{let r=fmtScore(e.score),a=`${e.origin===`review`?`r`:`n`}${r?` `+r:``}`;t.push(` ${n+1}. ${e.cardID} [${a}]`)})};return queueText(`supplyQ`,e.supplyQ),queueText(`failedQ`,e.failedQ),t.push(``),t.push(`drawn: ${e.drawnCards.length}`),e.drawnCards.forEach((e,n)=>{let r=e.correct===!0?`✓`:e.correct===!1?`✗`:`·`,a=`${Math.round(e.timeSpentMs/100)/10}s`;t.push(` ${n+1}. ${r} ${e.cardID} [${e.status}] \xD7${e.attempts} ${a}`)}),t.join(`
346
+ `)}function formatTime(e){let t=Math.max(0,Math.round(e));return`${Math.floor(t/60)}:${(t%60).toString().padStart(2,`0`)}`}function esc(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`)}var activeSession=null,sessionHistory=[],MAX_HISTORY=5;function clearStaleSessionDebugState(){clearRunHistory(),clearSrsBacklogDebug(),clearStrategyPressureDebug()}function startSessionTracking(e,t){let n=`session-${Date.now()}-${Math.random().toString(36).slice(2,8)}`;activeSession={sessionId:n,startTime:new Date,initialQueues:{timestamp:new Date,supplyQLength:e,failedQLength:t},presentations:[],queueSnapshots:[]},logger.debug(`[SessionDebugger] Started tracking session: ${n}`)}function recordCardPresentation(e,t,n,r,a,o){if(!activeSession){logger.warn(`[SessionDebugger] No active session to record presentation`);return}activeSession.presentations.push({timestamp:new Date,sequenceNumber:activeSession.presentations.length+1,cardId:e,courseId:t,courseName:n,origin:r,queueSource:a,score:o})}function snapshotQueues(e,t,n){activeSession&&activeSession.queueSnapshots.push({timestamp:new Date,supplyQLength:e,failedQLength:t,supplyQNext3:n})}function endSessionTracking(){activeSession&&(activeSession.endTime=new Date,sessionHistory.unshift(activeSession),sessionHistory.length>MAX_HISTORY&&sessionHistory.pop(),logger.debug(`[SessionDebugger] Ended session: ${activeSession.sessionId}`),activeSession=null)}function showCurrentQueue(){if(!activeSession){logger.info(`[Session Debug] No active session.`);return}let e=activeSession.queueSnapshots[activeSession.queueSnapshots.length-1]||activeSession.initialQueues;console.group(`📊 Current Queue State`),logger.info(`Supply Queue: ${e.supplyQLength} cards`),e.supplyQNext3&&e.supplyQNext3.length>0&&logger.info(` Next: ${e.supplyQNext3.join(`, `)}`),logger.info(`Failed Queue: ${e.failedQLength} cards`),console.groupEnd()}function showPresentationHistory(e=0){let t=e===0&&activeSession?activeSession:sessionHistory[e];if(!t){logger.info(`[Session Debug] No session found at index ${e}`);return}console.group(`\u{1F4DC} Session History: ${t.sessionId}`),logger.info(`Started: ${t.startTime.toLocaleTimeString()}`),t.endTime&&logger.info(`Ended: ${t.endTime.toLocaleTimeString()}`),logger.info(`Cards presented: ${t.presentations.length}`),t.presentations.length>0&&console.table(t.presentations.map(e=>({"#":e.sequenceNumber,course:e.courseName||e.courseId.slice(0,8),origin:e.origin,queue:e.queueSource,score:e.score?.toFixed(3)||`-`,time:e.timestamp.toLocaleTimeString()}))),console.groupEnd()}function showInterleaving(e=0){let t=e===0&&activeSession?activeSession:sessionHistory[e];if(!t){logger.info(`[Session Debug] No session found at index ${e}`);return}console.group(`🔀 Interleaving Analysis`);let n=new Map,r=new Map;if(t.presentations.forEach(e=>{let t=e.courseName||e.courseId;n.set(t,(n.get(t)||0)+1),r.has(t)||r.set(t,{review:0,new:0,failed:0});let a=r.get(t);a[e.origin]++}),logger.info(`Course distribution:`),console.table(Array.from(n.entries()).map(([e,n])=>{let a=r.get(e);return{course:e,total:n,reviews:a.review,new:a.new,failed:a.failed,percentage:(n/t.presentations.length*100).toFixed(1)+`%`}})),t.presentations.length>0){logger.info(`
347
347
  Presentation sequence (first 20):`);let e=t.presentations.slice(0,20).map((e,t)=>`${t+1}. ${e.courseName||e.courseId.slice(0,8)} (${e.origin})`).join(`
348
348
  `);logger.info(e)}let a=0,o=1,s=t.presentations[0]?.courseId;for(let e=1;e<t.presentations.length;e++)t.presentations[e].courseId===s?(o++,a=Math.max(a,o)):(s=t.presentations[e].courseId,o=1);a>3&&(logger.info(`
349
349
  \u26A0\uFE0F Detected clustering: max ${a} cards from same course in a row`),logger.info(`This suggests cards are sorted by score rather than round-robin by course.`)),console.groupEnd()}var sessionDebugAPI={get sessions(){return[...sessionHistory]},get active(){return activeSession},showQueue(){showCurrentQueue()},dbgOverlay(){toggleSessionOverlay()},showHistory(e=0){showPresentationHistory(e)},showInterleaving(e=0){showInterleaving(e)},listSessions(){if(activeSession&&logger.info(`Active session: ${activeSession.sessionId} (${activeSession.presentations.length} cards presented)`),sessionHistory.length===0){logger.info(`[Session Debug] No completed sessions in history.`);return}console.table(sessionHistory.map((e,t)=>({index:t,id:e.sessionId.slice(-8),started:e.startTime.toLocaleTimeString(),ended:e.endTime?.toLocaleTimeString()||`incomplete`,cards:e.presentations.length})))},export(){let e={active:activeSession,history:sessionHistory},t=JSON.stringify(e,null,2);return logger.info(`[Session Debug] Session data exported. Copy the returned string or use:`),logger.info(` copy(window.skuilder.session.export())`),t},clear(){sessionHistory.length=0,logger.info(`[Session Debug] Session history cleared.`)},help(){logger.info(`
@@ -369,9 +369,9 @@ Example:
369
369
  `+this.failedQ.toString}constructor(e,t,n,r,a,o){super(),_defineProperty(this,`_className`,`SessionController`),_defineProperty(this,`services`,void 0),_defineProperty(this,`srsService`,void 0),_defineProperty(this,`eloService`,void 0),_defineProperty(this,`hydrationService`,void 0),_defineProperty(this,`mixer`,void 0),_defineProperty(this,`dataLayer`,void 0),_defineProperty(this,`courseNameCache`,new Map),_defineProperty(this,`_defaultBatchLimit`,20),_defineProperty(this,`sources`,void 0),_defineProperty(this,`_sessionRecord`,[]),_defineProperty(this,`_currentCard`,null),_defineProperty(this,`supplyQ`,new ItemQueue),_defineProperty(this,`failedQ`,new ItemQueue),_defineProperty(this,`_supplyDrawsSinceFailed`,0),_defineProperty(this,`_replanPromise`,null),_defineProperty(this,`_activeReplanLabel`,null),_defineProperty(this,`_wellIndicatedRemaining`,0),_defineProperty(this,`_suppressQualityReplan`,!1),_defineProperty(this,`_minCardsGuarantee`,0),_defineProperty(this,`_sessionHints`,null),_defineProperty(this,`_servedCardIds`,new Set),_defineProperty(this,`_outcomeObservers`,[]),_defineProperty(this,`_sessionControls`,null),_defineProperty(this,`startTime`,void 0),_defineProperty(this,`endTime`,void 0),_defineProperty(this,`_secondsRemaining`,void 0),_defineProperty(this,`_intervalHandle`,void 0),this.dataLayer=n,this.mixer=a||new QuotaRoundRobinMixer,this.srsService=new SrsService(n.getUserDB()),this.eloService=new EloService(n,n.getUserDB()),this.hydrationService=new CardHydrationService(r,e=>n.getCourseDB(e),()=>this._getItemsToHydrate()),this.services={response:new ResponseProcessor(this.srsService,this.eloService)},this.sources=e,this.startTime=new Date,this._secondsRemaining=t,this.endTime=new Date(this.startTime.valueOf()+1e3*this._secondsRemaining),o?.defaultBatchLimit!==void 0&&(this._defaultBatchLimit=o.defaultBatchLimit),o?.outcomeObservers?.length&&(this._outcomeObservers=[...o.outcomeObservers]),this.log(`Session constructed:
370
370
  startTime: ${this.startTime}
371
371
  endTime: ${this.endTime}
372
- defaultBatchLimit: ${this._defaultBatchLimit}`),registerActiveController(this)}tick(){this._secondsRemaining=Math.floor((this.endTime.valueOf()-Date.now())/1e3),this._secondsRemaining<=0&&clearInterval(this._intervalHandle)}estimateCleanupTime(){let e=0;for(let t=0;t<this.failedQ.length;t++){let n=this.failedQ.peek(t),r=this._sessionRecord.find(e=>e.item.cardID===n.cardID),a=0;if(r){for(let e=0;e<r.records.length;e++)a+=r.records[e].timeSpent;a/=r.records.length,e+=a}}let t=e/1e3;return this.log(`Failed card cleanup estimate: ${Math.round(t)}`),t}async prepareSession(){if(this.sources.some(e=>typeof e.getWeightedCards!=`function`))throw Error(`[SessionController] All content sources must implement getWeightedCards().`);clearStaleSessionDebugState();let e=await this.getWeightedContent();this._wellIndicatedRemaining=e,e>=0&&e<_SessionController.MIN_WELL_INDICATED&&this.log(`[Init] Only ${e}/${_SessionController.MIN_WELL_INDICATED} well-indicated cards in initial load`),await this.hydrationService.ensureHydratedCards(),startSessionTracking(this.supplyQ.length,this.failedQ.length),this._intervalHandle=setInterval(()=>{this.tick()},1e3)}async requestReplan(e){let t=this.normalizeReplanOptions(e),n=this._replanHasIntent(t);if(this._replanPromise){if(!n)return this.log(`Replan already in progress, coalescing unhinted auto-replan`),this._replanPromise;let e=t.label?` [${t.label}]`:``;this.log(`Replan in progress; queueing hint-bearing replan${e} behind in-flight run`);let r=this._replanPromise.catch(()=>void 0).then(()=>this._runReplan(t)),a=r.finally(()=>{this._replanPromise===a&&(this._replanPromise=null,this._activeReplanLabel=null)});return this._replanPromise=a,r}let r=this._runReplan(t),a=r.finally(()=>{this._replanPromise===a&&(this._replanPromise=null,this._activeReplanLabel=null)});this._replanPromise=a,await r}_replanHasIntent(e){return!!(e.limit!==void 0||e.minFollowUpCards!==void 0||e.mode&&e.mode!==`replace`||e.hints&&Object.keys(e.hints).length>0||e.sessionHints!==void 0||e.mergeSessionHints!==void 0)}async _runReplan(e){this._activeReplanLabel=e.label??`(auto)`,e.hints||(e.hints={});let t=e.hints,n=new Set(t.excludeCards??[]);this._currentCard?.item.cardID&&n.add(this._currentCard.item.cardID);for(let e of this._sessionRecord)n.add(e.card.card_id);this.supplyQ.length>0&&n.add(this.supplyQ.peek(0).cardID),t.excludeCards=[...n],e.sessionHints!==void 0&&(this._sessionHints=e.sessionHints,this.log(`[Replan] Session hints ${e.sessionHints?`set`:`cleared`}: ${JSON.stringify(e.sessionHints)}`)),e.mergeSessionHints!==void 0&&(this._sessionHints=mergeHints2([this._sessionHints,e.mergeSessionHints])??null,this.log(`[Replan] Session hints merged: ${JSON.stringify(this._sessionHints)}`)),this._applyHintsToSources(e.hints,e.label);let r=e.label?` [${e.label}]`:``;this.log(`Mid-session replan requested${r} (limit: ${e.limit??`default`}, mode: ${e.mode??`replace`}${e.hints?`, with hints`:``})`),e.minFollowUpCards!==void 0&&e.minFollowUpCards>0&&(this._minCardsGuarantee=Math.max(this._minCardsGuarantee,e.minFollowUpCards),this.log(`[Replan] Card guarantee set to ${this._minCardsGuarantee}`)),await this._executeReplan(e)}setSessionHints(e){this._sessionHints=e,this.log(`Session hints ${e?`set`:`cleared`}: ${JSON.stringify(e)}`)}getSessionHints(){return this._sessionHints}getDebugSnapshot(){let describe=e=>{let t=[];for(let n=0;n<e.length;n++){let r=e.peek(n);t.push({cardID:r.cardID,status:r.status,origin:isReview(r)?`review`:`new`,score:r.score})}return{length:e.length,dequeueCount:e.dequeueCount,cards:t}},e=this._sessionRecord.map(e=>{let t=e.records[e.records.length-1];return{cardID:e.item.cardID,status:e.item.status,attempts:e.records.length,correct:t&&isQuestionRecord(t)?t.isCorrect:null,timeSpentMs:e.records.reduce((e,t)=>e+t.timeSpent,0)}});return{secondsRemaining:this.secondsRemaining,hasCardGuarantee:this.hasCardGuarantee,minCardsGuarantee:this._minCardsGuarantee,wellIndicatedRemaining:this._wellIndicatedRemaining,currentCard:this._currentCard?.item.cardID??null,sessionHints:this._sessionHints,replanActive:this._replanPromise!==null,replanLabel:this._activeReplanLabel,supplyQ:describe(this.supplyQ),failedQ:describe(this.failedQ),reviewBacklog:getSrsBacklogDebug(),drawnCards:e}}mergeSessionHints(e){this._sessionHints=mergeHints2([this._sessionHints,e])??null,this.log(`Session hints merged: ${JSON.stringify(this._sessionHints)}`)}_applyHintsToSources(e,t){let n=e&&t?{...e,_label:t}:e,r=mergeHints2([this._sessionHints,n]);if(r)for(let e of this.sources)e.setEphemeralHints?.(r)}_getSessionControls(){return this._sessionControls||(this._sessionControls={getSessionHints:()=>this.getSessionHints(),setSessionHints:e=>this.setSessionHints(e),mergeSessionHints:e=>this.mergeSessionHints(e),requestReplan:e=>this.requestReplan(e)}),this._sessionControls}async _notifyOutcomeObservers(e,t,n){if(this._outcomeObservers.length===0||!isQuestionRecord(e))return;let r={record:e,card:t.card,result:n},a=this._getSessionControls();for(let e of this._outcomeObservers)try{await e(r,a)}catch(e){this.error(`[OutcomeObserver] observer threw; ignoring`,e)}}async _replanUncoalesced(e){let t=this._runReplan(e),n=t.finally(()=>{this._replanPromise===n&&(this._replanPromise=null,this._activeReplanLabel=null)});this._replanPromise=n,await t}normalizeReplanOptions(e){if(!e)return{};let t=[`hints`,`sessionHints`,`limit`,`mode`,`label`,`minFollowUpCards`];return Object.keys(e).some(e=>t.includes(e))?e:{hints:e}}async _executeReplan(e={}){let t=e.limit,n=e.mode??`replace`,r=await this.getWeightedContent({replan:!0,additive:n===`merge`,limit:t});this._wellIndicatedRemaining=r,t!==void 0&&t<this._defaultBatchLimit?(this._suppressQualityReplan=!0,this.log(`[Replan] Burst mode (limit=${t}): suppressing quality-based auto-replan`)):this._suppressQualityReplan=!1,r>=0&&r<_SessionController.MIN_WELL_INDICATED&&this.log(`[Replan] Only ${r}/${_SessionController.MIN_WELL_INDICATED} well-indicated cards after replan`),await this.hydrationService.ensureHydratedCards();let a=e.label?` [${e.label}]`:``;this.log(`Replan complete${a}: supplyQ now has ${this.supplyQ.length} cards (mode=${n})`),snapshotQueues(this.supplyQ.length,this.failedQ.length)}addTime(e){this.endTime=new Date(this.endTime.valueOf()+1e3*e)}get failedCount(){return this.failedQ.length}toString(){return`Session: ${this.supplyQ.length} supply, ${this.failedQ.length} failed`}reportString(){return`${this.supplyQ.dequeueCount} supply, ${this.failedQ.dequeueCount} failed`}getDebugInfo(){let e=this.sources.some(e=>typeof e.getWeightedCards==`function`),extractQueueItems=(e,t=10)=>{let n=[];for(let r=0;r<Math.min(e.length,t);r++){let t=e.peek(r);n.push({courseID:t.courseID||`unknown`,cardID:t.cardID||`unknown`,status:t.status||`unknown`,score:t.score})}return n};return{api:{mode:e?`weighted`:`legacy`,description:e?`Using getWeightedCards() API with scored candidates`:`ERROR: getWeightedCards() not a function.`},supplyQueue:{length:this.supplyQ.length,dequeueCount:this.supplyQ.dequeueCount,items:extractQueueItems(this.supplyQ)},failedQueue:{length:this.failedQ.length,dequeueCount:this.failedQ.dequeueCount,items:extractQueueItems(this.failedQ)},hydratedCache:{count:this.hydrationService.hydratedCount,cardIds:this.hydrationService.getHydratedCardIds()},replan:{inProgress:this._replanPromise!==null,suppressQualityReplan:this._suppressQualityReplan,defaultBatchLimit:this._defaultBatchLimit,minCardsGuarantee:this._minCardsGuarantee}}}async getWeightedContent(e){let t=e?.replan??!1,n=e?.additive??!1,r=e?.limit??this._defaultBatchLimit,a=r;t||this._applyHintsToSources();let o=[];for(let e=0;e<this.sources.length;e++){let t=this.sources[e];try{let n=(await t.getWeightedCards(a)).cards;o.push({sourceIndex:e,weighted:n})}catch(t){if(this.error(`Failed to get content from source ${e}:`,t),this.sources.length===1)throw Error(`Cannot start session: failed to load content from source ${e}`)}}if(o.length===0){if(t)return this.log(`Replan: no content from any source, keeping existing supplyQ`),-1;throw Error(`Cannot start session: failed to load content from all ${this.sources.length} source(s). Check logs for details.`)}let s=this.mixer.mix(o,a*this.sources.length),c=o.map(e=>e.weighted[0]?.courseId||`source-${e.sourceIndex}`);await Promise.all(c.map(async e=>{if(!this.courseNameCache.has(e))try{let t=await this.dataLayer.getCoursesDB().getCourseConfig(e);this.courseNameCache.set(e,t.name)}catch{}}));let l=c.map(e=>this.courseNameCache.get(e)),u=this.mixer instanceof QuotaRoundRobinMixer?Math.ceil(a*this.sources.length/o.length):void 0;captureMixerRun(this.mixer.constructor.name,o,c,l,a*this.sources.length,u,s);let d=s.filter(e=>!this._servedCardIds.has(e.cardId)),p=d.filter(e=>e.score===1/0),m=d.filter(e=>e.score!==1/0),g=[...p,...m.slice(0,Math.max(0,r-p.length))],_=new Set(p.map(e=>e.cardId)),v=g.filter(e=>e.score>=_SessionController.WELL_INDICATED_SCORE).length,y=t?`Replan content:
372
+ defaultBatchLimit: ${this._defaultBatchLimit}`),registerActiveController(this)}tick(){this._secondsRemaining=Math.floor((this.endTime.valueOf()-Date.now())/1e3),this._secondsRemaining<=0&&clearInterval(this._intervalHandle)}estimateCleanupTime(){let e=0;for(let t=0;t<this.failedQ.length;t++){let n=this.failedQ.peek(t),r=this._sessionRecord.find(e=>e.item.cardID===n.cardID),a=0;if(r){for(let e=0;e<r.records.length;e++)a+=r.records[e].timeSpent;a/=r.records.length,e+=a}}let t=e/1e3;return this.log(`Failed card cleanup estimate: ${Math.round(t)}`),t}async prepareSession(){if(this.sources.some(e=>typeof e.getWeightedCards!=`function`))throw Error(`[SessionController] All content sources must implement getWeightedCards().`);clearStaleSessionDebugState();let e=await this.getWeightedContent();this._wellIndicatedRemaining=e,e>=0&&e<_SessionController.MIN_WELL_INDICATED&&this.log(`[Init] Only ${e}/${_SessionController.MIN_WELL_INDICATED} well-indicated cards in initial load`),await this.hydrationService.ensureHydratedCards(),startSessionTracking(this.supplyQ.length,this.failedQ.length),this._intervalHandle=setInterval(()=>{this.tick()},1e3)}async requestReplan(e){let t=this.normalizeReplanOptions(e),n=this._replanHasIntent(t);if(this._replanPromise){if(!n)return this.log(`Replan already in progress, coalescing unhinted auto-replan`),this._replanPromise;let e=t.label?` [${t.label}]`:``;this.log(`Replan in progress; queueing hint-bearing replan${e} behind in-flight run`);let r=this._replanPromise.catch(()=>void 0).then(()=>this._runReplan(t)),a=r.finally(()=>{this._replanPromise===a&&(this._replanPromise=null,this._activeReplanLabel=null)});return this._replanPromise=a,r}let r=this._runReplan(t),a=r.finally(()=>{this._replanPromise===a&&(this._replanPromise=null,this._activeReplanLabel=null)});this._replanPromise=a,await r}_replanHasIntent(e){return!!(e.limit!==void 0||e.minFollowUpCards!==void 0||e.mode&&e.mode!==`replace`||e.hints&&Object.keys(e.hints).length>0||e.sessionHints!==void 0||e.mergeSessionHints!==void 0)}async _runReplan(e){this._activeReplanLabel=e.label??`(auto)`,e.hints||(e.hints={});let t=e.hints,n=new Set(t.excludeCards??[]);this._currentCard?.item.cardID&&n.add(this._currentCard.item.cardID);for(let e of this._sessionRecord)n.add(e.card.card_id);this.supplyQ.length>0&&n.add(this.supplyQ.peek(0).cardID),t.excludeCards=[...n],e.sessionHints!==void 0&&(this._sessionHints=e.sessionHints,this.log(`[Replan] Session hints ${e.sessionHints?`set`:`cleared`}: ${JSON.stringify(e.sessionHints)}`)),e.mergeSessionHints!==void 0&&(this._sessionHints=mergeHints2([this._sessionHints,e.mergeSessionHints])??null,this.log(`[Replan] Session hints merged: ${JSON.stringify(this._sessionHints)}`)),this._applyHintsToSources(e.hints,e.label);let r=e.label?` [${e.label}]`:``;this.log(`Mid-session replan requested${r} (limit: ${e.limit??`default`}, mode: ${e.mode??`replace`}${e.hints?`, with hints`:``})`),e.minFollowUpCards!==void 0&&e.minFollowUpCards>0&&(this._minCardsGuarantee=Math.max(this._minCardsGuarantee,e.minFollowUpCards),this.log(`[Replan] Card guarantee set to ${this._minCardsGuarantee}`)),await this._executeReplan(e)}setSessionHints(e){this._sessionHints=e,this.log(`Session hints ${e?`set`:`cleared`}: ${JSON.stringify(e)}`)}getSessionHints(){return this._sessionHints}getDebugSnapshot(){let describe=e=>{let t=[];for(let n=0;n<e.length;n++){let r=e.peek(n);t.push({cardID:r.cardID,status:r.status,origin:isReview(r)?`review`:`new`,score:r.score})}return{length:e.length,dequeueCount:e.dequeueCount,cards:t}},e=this._sessionRecord.map(e=>{let t=e.records[e.records.length-1];return{cardID:e.item.cardID,status:e.item.status,attempts:e.records.length,correct:t&&isQuestionRecord(t)?t.isCorrect:null,timeSpentMs:e.records.reduce((e,t)=>e+t.timeSpent,0)}});return{secondsRemaining:this.secondsRemaining,hasCardGuarantee:this.hasCardGuarantee,minCardsGuarantee:this._minCardsGuarantee,wellIndicatedRemaining:this._wellIndicatedRemaining,currentCard:this._currentCard?.item.cardID??null,sessionHints:this._sessionHints,replanActive:this._replanPromise!==null,replanLabel:this._activeReplanLabel,supplyQ:describe(this.supplyQ),failedQ:describe(this.failedQ),reviewBacklog:getSrsBacklogDebug(),strategyPressure:getStrategyPressureDebug(),drawnCards:e}}mergeSessionHints(e){this._sessionHints=mergeHints2([this._sessionHints,e])??null,this.log(`Session hints merged: ${JSON.stringify(this._sessionHints)}`)}_applyHintsToSources(e,t){let n=e&&t?{...e,_label:t}:e,r=mergeHints2([this._sessionHints,n]);if(r)for(let e of this.sources)e.setEphemeralHints?.(r)}_getSessionControls(){return this._sessionControls||(this._sessionControls={getSessionHints:()=>this.getSessionHints(),setSessionHints:e=>this.setSessionHints(e),mergeSessionHints:e=>this.mergeSessionHints(e),requestReplan:e=>this.requestReplan(e)}),this._sessionControls}async _notifyOutcomeObservers(e,t,n){if(this._outcomeObservers.length===0||!isQuestionRecord(e))return;let r={record:e,card:t.card,result:n},a=this._getSessionControls();for(let e of this._outcomeObservers)try{await e(r,a)}catch(e){this.error(`[OutcomeObserver] observer threw; ignoring`,e)}}async _replanUncoalesced(e){let t=this._runReplan(e),n=t.finally(()=>{this._replanPromise===n&&(this._replanPromise=null,this._activeReplanLabel=null)});this._replanPromise=n,await t}normalizeReplanOptions(e){if(!e)return{};let t=[`hints`,`sessionHints`,`limit`,`mode`,`label`,`minFollowUpCards`];return Object.keys(e).some(e=>t.includes(e))?e:{hints:e}}async _executeReplan(e={}){let t=e.limit,n=e.mode??`replace`,r=await this.getWeightedContent({replan:!0,additive:n===`merge`,limit:t});this._wellIndicatedRemaining=r,t!==void 0&&t<this._defaultBatchLimit?(this._suppressQualityReplan=!0,this.log(`[Replan] Burst mode (limit=${t}): suppressing quality-based auto-replan`)):this._suppressQualityReplan=!1,r>=0&&r<_SessionController.MIN_WELL_INDICATED&&this.log(`[Replan] Only ${r}/${_SessionController.MIN_WELL_INDICATED} well-indicated cards after replan`),await this.hydrationService.ensureHydratedCards();let a=e.label?` [${e.label}]`:``;this.log(`Replan complete${a}: supplyQ now has ${this.supplyQ.length} cards (mode=${n})`),snapshotQueues(this.supplyQ.length,this.failedQ.length)}addTime(e){this.endTime=new Date(this.endTime.valueOf()+1e3*e)}get failedCount(){return this.failedQ.length}toString(){return`Session: ${this.supplyQ.length} supply, ${this.failedQ.length} failed`}reportString(){return`${this.supplyQ.dequeueCount} supply, ${this.failedQ.dequeueCount} failed`}getDebugInfo(){let e=this.sources.some(e=>typeof e.getWeightedCards==`function`),extractQueueItems=(e,t=10)=>{let n=[];for(let r=0;r<Math.min(e.length,t);r++){let t=e.peek(r);n.push({courseID:t.courseID||`unknown`,cardID:t.cardID||`unknown`,status:t.status||`unknown`,score:t.score})}return n};return{api:{mode:e?`weighted`:`legacy`,description:e?`Using getWeightedCards() API with scored candidates`:`ERROR: getWeightedCards() not a function.`},supplyQueue:{length:this.supplyQ.length,dequeueCount:this.supplyQ.dequeueCount,items:extractQueueItems(this.supplyQ)},failedQueue:{length:this.failedQ.length,dequeueCount:this.failedQ.dequeueCount,items:extractQueueItems(this.failedQ)},hydratedCache:{count:this.hydrationService.hydratedCount,cardIds:this.hydrationService.getHydratedCardIds()},replan:{inProgress:this._replanPromise!==null,suppressQualityReplan:this._suppressQualityReplan,defaultBatchLimit:this._defaultBatchLimit,minCardsGuarantee:this._minCardsGuarantee}}}async getWeightedContent(e){let t=e?.replan??!1,n=e?.additive??!1,r=e?.limit??this._defaultBatchLimit,a=r;t||this._applyHintsToSources();let o=[];for(let e=0;e<this.sources.length;e++){let t=this.sources[e];try{let n=(await t.getWeightedCards(a)).cards;o.push({sourceIndex:e,weighted:n})}catch(t){if(this.error(`Failed to get content from source ${e}:`,t),this.sources.length===1)throw Error(`Cannot start session: failed to load content from source ${e}`)}}if(o.length===0){if(t)return this.log(`Replan: no content from any source, keeping existing supplyQ`),-1;throw Error(`Cannot start session: failed to load content from all ${this.sources.length} source(s). Check logs for details.`)}let s=this.mixer.mix(o,a*this.sources.length),c=o.map(e=>e.weighted[0]?.courseId||`source-${e.sourceIndex}`);await Promise.all(c.map(async e=>{if(!this.courseNameCache.has(e))try{let t=await this.dataLayer.getCoursesDB().getCourseConfig(e);this.courseNameCache.set(e,t.name)}catch{}}));let l=c.map(e=>this.courseNameCache.get(e)),u=this.mixer instanceof QuotaRoundRobinMixer?Math.ceil(a*this.sources.length/o.length):void 0;captureMixerRun(this.mixer.constructor.name,o,c,l,a*this.sources.length,u,s);let d=s.filter(e=>!this._servedCardIds.has(e.cardId)),p=d.filter(e=>e.score===1/0),m=d.filter(e=>e.score!==1/0),g=[...p,...m.slice(0,Math.max(0,r-p.length))],_=new Set(p.map(e=>e.cardId)),v=g.filter(e=>e.score>=_SessionController.WELL_INDICATED_SCORE).length,y=t?`Replan content:
373
373
  `:`Mixed content session created with:
374
374
  `,b=g.map(e=>{let t=getCardOrigin(e),n=Number.isFinite(e.score)?e.score.toFixed(2):`+INF`;return y+=`${t===`review`?`Review`:`New`}: ${e.courseId}::${e.cardId} (score: ${n})
375
375
  `,this._buildSupplyItem(e,t)});if(n){let e=this.supplyQ.mergeToFront(b,e=>e.cardID,_);y+=`Additive merge: ${e} cards added to front of supplyQ
376
- `}else if(t)this.supplyQ.replaceAll(b,e=>e.cardID);else for(let e of b)this.supplyQ.add(e,e.cardID);return this.log(y),v}_buildSupplyItem(e,t=getCardOrigin(e)){let n={cardID:e.cardId,courseID:e.courseId,contentSourceType:`course`,contentSourceID:e.courseId,score:e.score};return t===`review`?{...n,status:`review`,reviewID:e.reviewID}:{...n,status:`new`}}_getItemsToHydrate(){let e=[],t=3,n=2;for(let t=0;t<Math.min(3,this.supplyQ.length);t++)e.push(this.supplyQ.peek(t));for(let t=0;t<Math.min(2,this.failedQ.length);t++)e.push(this.failedQ.peek(t));return e}_selectNextItemToHydrate(){if(this.supplyQ.length===0&&this.failedQ.length===0||this._secondsRemaining<2&&this.failedQ.length===0&&this._minCardsGuarantee<=0)return null;if(this._secondsRemaining<=0&&this._minCardsGuarantee<=0)return this.failedQ.length>0?this.failedQ.peek(0):null;let e=this.supplyQ.length>0?this.supplyQ.peek(0):null;return this._minCardsGuarantee>0&&e?e:this.failedQ.length>0&&this._shouldInterleaveFailed(e!==null)?this.failedQ.peek(0):e||(this.failedQ.length>0?this.failedQ.peek(0):(this.log(`No more cards available for the session!`),null))}_shouldInterleaveFailed(e){return this.failedQ.length===0?!1:!e||this._secondsRemaining-this.estimateCleanupTime()<=_SessionController.FAILED_ENDGAME_SLACK_SECONDS?!0:this._supplyDrawsSinceFailed>=_SessionController.FAILED_INTERLEAVE_EVERY}async nextCard(e=`dismiss-success`){if(this.dismissCurrentCard(e),this._minCardsGuarantee>0&&(this._minCardsGuarantee--,this.log(`[CardGuarantee] ${this._minCardsGuarantee} guaranteed cards remaining`)),this._replanPromise&&this.supplyQ.length===0&&this.failedQ.length===0&&(this.log(`nextCard: queues empty, awaiting in-flight replan before drawing`),await this._replanPromise),this.supplyQ.length<=_SessionController.DEPLETION_PREFETCH_THRESHOLD&&this._secondsRemaining>0&&!this._replanPromise&&(this._suppressQualityReplan=!1,this.log(`[AutoReplan:depletion] supplyQ has ${this.supplyQ.length} card(s) (${this.failedQ.length} failed pending) with ${this._secondsRemaining}s remaining. Triggering background replan.`),this.requestReplan({label:`auto:depletion`,mode:`merge`})),!this._suppressQualityReplan&&this._wellIndicatedRemaining<=3&&this.supplyQ.length>0&&!this._replanPromise&&(this.log(`[AutoReplan:quality] ${this._wellIndicatedRemaining} well-indicated cards remaining (supplyQ: ${this.supplyQ.length}). Triggering background replan.`),this.requestReplan({label:`auto:quality`})),this._secondsRemaining<=0&&this.failedQ.length===0&&this._minCardsGuarantee<=0)return this._currentCard=null,endSessionTracking(),null;let t=3,n=250,r=0;for(;this._secondsRemaining>0&&this.supplyQ.length===0&&this.failedQ.length===0;)if(this.log(`[WedgeBreaker] All queues empty with ${this._secondsRemaining}s remaining. Running pipeline (attempt ${r+1}/3).`),await this._replanUncoalesced({label:`wedge-breaker`}),this.supplyQ.length===0&&this.failedQ.length===0){if(r++,r>=3){this.log(`[WedgeBreaker] Pipeline returned no content 3 consecutive times. Giving up; session will end.`);break}await new Promise(e=>setTimeout(e,250))}else r=0;let a=20;for(let e=0;e<20;e++){let e=this._selectNextItemToHydrate();if(!e)return this._currentCard=null,endSessionTracking(),null;let t=this.hydrationService.getHydratedCard(e.cardID);if(t||(t=await this.hydrationService.waitForCard(e.cardID)),this.removeItemFromQueue(e),t){await this.hydrationService.ensureHydratedCards(),this._currentCard=t;let n=e.status===`review`||e.status===`failed-review`?`review`:e.status===`new`||e.status===`failed-new`?`new`:`failed`,r=e.status.startsWith(`failed`)?`failedQ`:`supplyQ`;return recordCardPresentation(e.cardID,e.courseID,this.courseNameCache.get(e.courseID),n,r,e.score),snapshotQueues(this.supplyQ.length,this.failedQ.length),t}this.log(`Skipping card ${e.cardID}: hydration failed, trying next`),isReview(e)&&this.srsService.removeReview(e.reviewID)}return this.log(`Exhausted 20 skip attempts finding a hydratable card`),this._currentCard=null,endSessionTracking(),null}async submitResponse(e,t,n,r,a,o,s,c,l){let u={...r.item},d=await this.services.response.processResponse(e,t,u,n,r,a,o,s,c,l);return await this._notifyOutcomeObservers(e,r,d),d}dismissCurrentCard(e=`dismiss-success`){if(this._currentCard)if(e===`dismiss-success`)this.hydrationService.removeCard(this._currentCard.item.cardID);else if(e===`marked-failed`){let e;e=isReview(this._currentCard.item)?{cardID:this._currentCard.item.cardID,courseID:this._currentCard.item.courseID,contentSourceID:this._currentCard.item.contentSourceID,contentSourceType:this._currentCard.item.contentSourceType,status:`failed-review`,reviewID:this._currentCard.item.reviewID}:{cardID:this._currentCard.item.cardID,courseID:this._currentCard.item.courseID,contentSourceID:this._currentCard.item.contentSourceID,contentSourceType:this._currentCard.item.contentSourceType,status:`failed-new`},this.failedQ.add(e,e.cardID),this._supplyDrawsSinceFailed=0}else (e===`dismiss-error`||e===`dismiss-failed`)&&this.hydrationService.removeCard(this._currentCard.item.cardID)}removeItemFromQueue(e){this._clearDurableRequirement(e.cardID),this._servedCardIds.add(e.cardID),this.failedQ.peek(0)?.cardID===e.cardID?(this.failedQ.dequeue(e=>e.cardID),this._supplyDrawsSinceFailed=0):this.supplyQ.peek(0)?.cardID===e.cardID&&(this.supplyQ.dequeue(e=>e.cardID),this._supplyDrawsSinceFailed++,this._wellIndicatedRemaining>0&&this._wellIndicatedRemaining--)}_clearDurableRequirement(e){let t=this._sessionHints?.requireCards;if(!t||t.length===0)return;let n=t.filter(t=>t!==e);n.length!==t.length&&(this._sessionHints={...this._sessionHints,requireCards:n.length>0?n:void 0},this.log(`[Replan] Durable requirement satisfied & cleared on draw: ${e}`))}async endSession(){if(!this._sessionRecord||this._sessionRecord.length===0)return;let e=this._sessionRecord.flatMap(e=>e.records).filter(e=>e.userAnswer!==void 0);if(e.length===0)return;let t=null,n=[];for(let e of this.sources)if(e.getOrchestrationContext){try{t=await e.getOrchestrationContext(),e.getStrategyIds&&n.push(...e.getStrategyIds())}catch(e){logger.warn(`[SessionController] Failed to get orchestration context: ${e}`)}if(t)break}if(!t){logger.debug(`[SessionController] No orchestration context available, skipping outcome recording`);return}let r=new Date().toISOString(),a=new Date(this.startTime).toISOString();await recordUserOutcome(t,a,r,e,n)}},_defineProperty(_SessionController2,`MIN_WELL_INDICATED`,5),_defineProperty(_SessionController2,`WELL_INDICATED_SCORE`,.1),_defineProperty(_SessionController2,`DEPLETION_PREFETCH_THRESHOLD`,3),_defineProperty(_SessionController2,`FAILED_INTERLEAVE_EVERY`,4),_defineProperty(_SessionController2,`FAILED_ENDGAME_SLACK_SECONDS`,20),_SessionController2);init_TagFilteredContentSource(),init_factory(),init_userDBHelpers();export{isReview as $,endSessionTracking as A,getRegisteredNavigatorNames as B,clearStaleSessionDebugState as C,toggleSessionOverlay as Ct,computeSpread as D,validateMigration as Dt,computeOutcomeSignal as E,userDBDebugAPI as Et,getCardHistoryID as F,importParsedCards as G,getSrsBacklogDebug as H,getCardOrigin as I,initializeNavigatorRegistry as J,initializeDataDirectory as K,getDataLayer as L,getActiveController as M,getActivePipeline as N,createOrchestrationContext as O,validateProcessorConfig as Ot,getAppDataDirectory as P,isQuestionTypeRegistered as Q,getDbPath as R,clearSrsBacklogDebug as S,startSessionTracking as St,computeEffectiveWeight as T,updateStrategyWeight as Tt,getStudySource as U,getRegisteredNavigatorRole as V,hasRegisteredNavigator as W,isGenerator as X,isFilter as Y,isQuestionRecord as Z,SessionController as _,removeDataShape as _t,DocType as a,mountSessionDebugger as at,buildStrategyStateId as b,sessionDebugAPI as bt,FileSystemError as c,pipelineDebugAPI as ct,NOT_SET as d,recordUserOutcome as dt,log as et,NavigatorRole as f,registerActiveController as ft,REVIEW_TIME_FORMAT as g,registerSeedData as gt,QuotaRoundRobinMixer as h,registerQuestionType as ht,DIVERSITY_STRENGTH as i,mountPipelineDebugger as it,ensureAppDataDirectory as j,diversityRerank as k,validateStaticCourse as kt,GuestUsername as l,processCustomQuestionsData as lt,Navigators as m,registerNavigator as mt,CourseLookup as n,mixerDebugAPI as nt,DocTypePrefixes as o,mountUserDBDebugger as ot,NavigatorRoles as p,registerDataShape as pt,initializeDataLayer as q,DIVERSITY_FLOOR as r,mountMixerDebugger as rt,ENV as s,newInterval as st,ContentNavigator as t,makeScheduledCardId as tt,Loggable as u,recordCardPresentation as ut,TagFilteredContentSource as v,removeQuestionType as vt,computeDeviation as w,updateLearningState as wt,captureMixerRun as x,snapshotQueues as xt,areQuestionRecords as y,scoreAccuracyInZone as yt,getRegisteredNavigator as z};
377
- //# sourceMappingURL=dist-DFv5eKpx.js.map
376
+ `}else if(t)this.supplyQ.replaceAll(b,e=>e.cardID);else for(let e of b)this.supplyQ.add(e,e.cardID);return this.log(y),v}_buildSupplyItem(e,t=getCardOrigin(e)){let n={cardID:e.cardId,courseID:e.courseId,contentSourceType:`course`,contentSourceID:e.courseId,score:e.score};return t===`review`?{...n,status:`review`,reviewID:e.reviewID}:{...n,status:`new`}}_getItemsToHydrate(){let e=[],t=3,n=2;for(let t=0;t<Math.min(3,this.supplyQ.length);t++)e.push(this.supplyQ.peek(t));for(let t=0;t<Math.min(2,this.failedQ.length);t++)e.push(this.failedQ.peek(t));return e}_selectNextItemToHydrate(){if(this.supplyQ.length===0&&this.failedQ.length===0||this._secondsRemaining<2&&this.failedQ.length===0&&this._minCardsGuarantee<=0)return null;if(this._secondsRemaining<=0&&this._minCardsGuarantee<=0)return this.failedQ.length>0?this.failedQ.peek(0):null;let e=this.supplyQ.length>0?this.supplyQ.peek(0):null;return this._minCardsGuarantee>0&&e?e:this.failedQ.length>0&&this._shouldInterleaveFailed(e!==null)?this.failedQ.peek(0):e||(this.failedQ.length>0?this.failedQ.peek(0):(this.log(`No more cards available for the session!`),null))}_shouldInterleaveFailed(e){return this.failedQ.length===0?!1:!e||this._secondsRemaining-this.estimateCleanupTime()<=_SessionController.FAILED_ENDGAME_SLACK_SECONDS?!0:this._supplyDrawsSinceFailed>=_SessionController.FAILED_INTERLEAVE_EVERY}async nextCard(e=`dismiss-success`){if(this.dismissCurrentCard(e),this._minCardsGuarantee>0&&(this._minCardsGuarantee--,this.log(`[CardGuarantee] ${this._minCardsGuarantee} guaranteed cards remaining`)),this._replanPromise&&this.supplyQ.length===0&&this.failedQ.length===0&&(this.log(`nextCard: queues empty, awaiting in-flight replan before drawing`),await this._replanPromise),this.supplyQ.length<=_SessionController.DEPLETION_PREFETCH_THRESHOLD&&this._secondsRemaining>0&&!this._replanPromise&&(this._suppressQualityReplan=!1,this.log(`[AutoReplan:depletion] supplyQ has ${this.supplyQ.length} card(s) (${this.failedQ.length} failed pending) with ${this._secondsRemaining}s remaining. Triggering background replan.`),this.requestReplan({label:`auto:depletion`,mode:`merge`})),!this._suppressQualityReplan&&this._wellIndicatedRemaining<=3&&this.supplyQ.length>0&&!this._replanPromise&&(this.log(`[AutoReplan:quality] ${this._wellIndicatedRemaining} well-indicated cards remaining (supplyQ: ${this.supplyQ.length}). Triggering background replan.`),this.requestReplan({label:`auto:quality`})),this._secondsRemaining<=0&&this.failedQ.length===0&&this._minCardsGuarantee<=0)return this._currentCard=null,endSessionTracking(),null;let t=3,n=250,r=0;for(;this._secondsRemaining>0&&this.supplyQ.length===0&&this.failedQ.length===0;)if(this.log(`[WedgeBreaker] All queues empty with ${this._secondsRemaining}s remaining. Running pipeline (attempt ${r+1}/3).`),await this._replanUncoalesced({label:`wedge-breaker`}),this.supplyQ.length===0&&this.failedQ.length===0){if(r++,r>=3){this.log(`[WedgeBreaker] Pipeline returned no content 3 consecutive times. Giving up; session will end.`);break}await new Promise(e=>setTimeout(e,250))}else r=0;let a=20;for(let e=0;e<20;e++){let e=this._selectNextItemToHydrate();if(!e)return this._currentCard=null,endSessionTracking(),null;let t=this.hydrationService.getHydratedCard(e.cardID);if(t||(t=await this.hydrationService.waitForCard(e.cardID)),this.removeItemFromQueue(e),t){await this.hydrationService.ensureHydratedCards(),this._currentCard=t;let n=e.status===`review`||e.status===`failed-review`?`review`:e.status===`new`||e.status===`failed-new`?`new`:`failed`,r=e.status.startsWith(`failed`)?`failedQ`:`supplyQ`;return recordCardPresentation(e.cardID,e.courseID,this.courseNameCache.get(e.courseID),n,r,e.score),snapshotQueues(this.supplyQ.length,this.failedQ.length),t}this.log(`Skipping card ${e.cardID}: hydration failed, trying next`),isReview(e)&&this.srsService.removeReview(e.reviewID)}return this.log(`Exhausted 20 skip attempts finding a hydratable card`),this._currentCard=null,endSessionTracking(),null}async submitResponse(e,t,n,r,a,o,s,c,l){let u={...r.item},d=await this.services.response.processResponse(e,t,u,n,r,a,o,s,c,l);return await this._notifyOutcomeObservers(e,r,d),d}dismissCurrentCard(e=`dismiss-success`){if(this._currentCard)if(e===`dismiss-success`)this.hydrationService.removeCard(this._currentCard.item.cardID);else if(e===`marked-failed`){let e;e=isReview(this._currentCard.item)?{cardID:this._currentCard.item.cardID,courseID:this._currentCard.item.courseID,contentSourceID:this._currentCard.item.contentSourceID,contentSourceType:this._currentCard.item.contentSourceType,status:`failed-review`,reviewID:this._currentCard.item.reviewID}:{cardID:this._currentCard.item.cardID,courseID:this._currentCard.item.courseID,contentSourceID:this._currentCard.item.contentSourceID,contentSourceType:this._currentCard.item.contentSourceType,status:`failed-new`},this.failedQ.add(e,e.cardID),this._supplyDrawsSinceFailed=0}else (e===`dismiss-error`||e===`dismiss-failed`)&&this.hydrationService.removeCard(this._currentCard.item.cardID)}removeItemFromQueue(e){this._clearDurableRequirement(e.cardID),this._servedCardIds.add(e.cardID),this.failedQ.peek(0)?.cardID===e.cardID?(this.failedQ.dequeue(e=>e.cardID),this._supplyDrawsSinceFailed=0):this.supplyQ.peek(0)?.cardID===e.cardID&&(this.supplyQ.dequeue(e=>e.cardID),this._supplyDrawsSinceFailed++,this._wellIndicatedRemaining>0&&this._wellIndicatedRemaining--)}_clearDurableRequirement(e){let t=this._sessionHints?.requireCards;if(!t||t.length===0)return;let n=t.filter(t=>t!==e);n.length!==t.length&&(this._sessionHints={...this._sessionHints,requireCards:n.length>0?n:void 0},this.log(`[Replan] Durable requirement satisfied & cleared on draw: ${e}`))}async endSession(){if(!this._sessionRecord||this._sessionRecord.length===0)return;let e=this._sessionRecord.flatMap(e=>e.records).filter(e=>e.userAnswer!==void 0);if(e.length===0)return;let t=null,n=[];for(let e of this.sources)if(e.getOrchestrationContext){try{t=await e.getOrchestrationContext(),e.getStrategyIds&&n.push(...e.getStrategyIds())}catch(e){logger.warn(`[SessionController] Failed to get orchestration context: ${e}`)}if(t)break}if(!t){logger.debug(`[SessionController] No orchestration context available, skipping outcome recording`);return}let r=new Date().toISOString(),a=new Date(this.startTime).toISOString();await recordUserOutcome(t,a,r,e,n)}},_defineProperty(_SessionController2,`MIN_WELL_INDICATED`,5),_defineProperty(_SessionController2,`WELL_INDICATED_SCORE`,.1),_defineProperty(_SessionController2,`DEPLETION_PREFETCH_THRESHOLD`,3),_defineProperty(_SessionController2,`FAILED_INTERLEAVE_EVERY`,4),_defineProperty(_SessionController2,`FAILED_ENDGAME_SLACK_SECONDS`,20),_SessionController2);init_TagFilteredContentSource(),init_factory(),init_userDBHelpers();export{isFilter as $,computeSpread as A,userDBDebugAPI as At,getDataLayer as B,captureStrategyPressure as C,scoreAccuracyInZone as Ct,computeDeviation as D,toggleSessionOverlay as Dt,clearStrategyPressureDebug as E,startSessionTracking as Et,getActiveController as F,getSrsBacklogDebug as G,getRegisteredNavigator as H,getActivePipeline as I,hasRegisteredNavigator as J,getStrategyPressureDebug as K,getAppDataDirectory as L,diversityRerank as M,validateProcessorConfig as Mt,endSessionTracking as N,validateStaticCourse as Nt,computeEffectiveWeight as O,updateLearningState as Ot,ensureAppDataDirectory as P,initializeNavigatorRegistry as Q,getCardHistoryID as R,captureMixerRun as S,removeQuestionType as St,clearStaleSessionDebugState as T,snapshotQueues as Tt,getRegisteredNavigatorNames as U,getDbPath as V,getRegisteredNavigatorRole as W,initializeDataDirectory as X,importParsedCards as Y,initializeDataLayer as Z,REVIEW_TIME_FORMAT as _,registerDataShape as _t,DocType as a,makeScheduledCardId as at,areQuestionRecords as b,registerSeedData as bt,FileSystemError as c,mountPipelineDebugger as ct,Loggable as d,newInterval as dt,isGenerator as et,NOT_SET as f,pipelineDebugAPI as ft,QuotaRoundRobinMixer as g,registerActiveController as gt,Navigators as h,recordUserOutcome as ht,DIVERSITY_STRENGTH as i,log as it,createOrchestrationContext as j,validateMigration as jt,computeOutcomeSignal as k,updateStrategyWeight as kt,GuestUsername as l,mountSessionDebugger as lt,NavigatorRoles as m,recordCardPresentation as mt,CourseLookup as n,isQuestionTypeRegistered as nt,DocTypePrefixes as o,mixerDebugAPI as ot,NavigatorRole as p,processCustomQuestionsData as pt,getStudySource as q,DIVERSITY_FLOOR as r,isReview as rt,ENV as s,mountMixerDebugger as st,ContentNavigator as t,isQuestionRecord as tt,HYDRATION_MARKER_ID as u,mountUserDBDebugger as ut,SessionController as v,registerNavigator as vt,clearSrsBacklogDebug as w,sessionDebugAPI as wt,buildStrategyStateId as x,removeDataShape as xt,TagFilteredContentSource as y,registerQuestionType as yt,getCardOrigin as z};
377
+ //# sourceMappingURL=dist-8LnqWDZ6.js.map