@vue-skuilder/platform-ui 0.1.33 → 0.1.34

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 (63) hide show
  1. package/dist/assets/{About-C1rcp0Ex.js → About-BpqIdlZQ.js} +2 -2
  2. package/dist/assets/{About-C1rcp0Ex.js.map → About-BpqIdlZQ.js.map} +1 -1
  3. package/dist/assets/{AdminDashboard-BOHgXD45.js → AdminDashboard-BObO6egG.js} +2 -2
  4. package/dist/assets/{AdminDashboard-BOHgXD45.js.map → AdminDashboard-BObO6egG.js.map} +1 -1
  5. package/dist/assets/{ClassroomCtrlPanel-ezMgP7A3.js → ClassroomCtrlPanel-lgN6cdTd.js} +2 -2
  6. package/dist/assets/{ClassroomCtrlPanel-ezMgP7A3.js.map → ClassroomCtrlPanel-lgN6cdTd.js.map} +1 -1
  7. package/dist/assets/{Classrooms-CdN-ogiI.js → Classrooms-BWoQ4i1_.js} +2 -2
  8. package/dist/assets/{Classrooms-CdN-ogiI.js.map → Classrooms-BWoQ4i1_.js.map} +1 -1
  9. package/dist/assets/{CourseRouter-BKHj3HHh.js → CourseRouter-CODD0Qm5.js} +2 -2
  10. package/dist/assets/{CourseRouter-BKHj3HHh.js.map → CourseRouter-CODD0Qm5.js.map} +1 -1
  11. package/dist/assets/{Courses-DTsQz202.js → Courses-ClDdLQUO.js} +2 -2
  12. package/dist/assets/{Courses-DTsQz202.js.map → Courses-ClDdLQUO.js.map} +1 -1
  13. package/dist/assets/{DataInputFormTester-BC2ZDakI.js → DataInputFormTester-ywHjm2yC.js} +2 -2
  14. package/dist/assets/{DataInputFormTester-BC2ZDakI.js.map → DataInputFormTester-ywHjm2yC.js.map} +1 -1
  15. package/dist/assets/{EloModeration-DqViuzI8.js → EloModeration-B6LM11G_.js} +2 -2
  16. package/dist/assets/{EloModeration-DqViuzI8.js.map → EloModeration-B6LM11G_.js.map} +1 -1
  17. package/dist/assets/{JoinCode-BjbhKTgK.js → JoinCode-DgPD_OFL.js} +2 -2
  18. package/dist/assets/{JoinCode-BjbhKTgK.js.map → JoinCode-DgPD_OFL.js.map} +1 -1
  19. package/dist/assets/{NewCourseDialog-4YsIKoUU.js → NewCourseDialog-DhwvBT3y.js} +2 -2
  20. package/dist/assets/{NewCourseDialog-4YsIKoUU.js.map → NewCourseDialog-DhwvBT3y.js.map} +1 -1
  21. package/dist/assets/{ReleaseNotes-BYXN8IZ4.js → ReleaseNotes-DtwDIIfH.js} +2 -2
  22. package/dist/assets/{ReleaseNotes-BYXN8IZ4.js.map → ReleaseNotes-DtwDIIfH.js.map} +1 -1
  23. package/dist/assets/{RequestPasswordReset-Zxt4oKHx.js → RequestPasswordReset-DBv0XCIk.js} +2 -2
  24. package/dist/assets/{RequestPasswordReset-Zxt4oKHx.js.map → RequestPasswordReset-DBv0XCIk.js.map} +1 -1
  25. package/dist/assets/{ResetPassword-rAc69I0Q.js → ResetPassword-BtBplqsQ.js} +2 -2
  26. package/dist/assets/{ResetPassword-rAc69I0Q.js.map → ResetPassword-BtBplqsQ.js.map} +1 -1
  27. package/dist/assets/{Study-DJlTc-ZH.js → Study-D9W1DKes.js} +2 -2
  28. package/dist/assets/{Study-DJlTc-ZH.js.map → Study-D9W1DKes.js.map} +1 -1
  29. package/dist/assets/{TagInformation-CSUtrZy7.js → TagInformation-DgUkKxoU.js} +2 -2
  30. package/dist/assets/{TagInformation-CSUtrZy7.js.map → TagInformation-DgUkKxoU.js.map} +1 -1
  31. package/dist/assets/{User-D2R6CphB.js → User-DorFkRx_.js} +2 -2
  32. package/dist/assets/{User-D2R6CphB.js.map → User-DorFkRx_.js.map} +1 -1
  33. package/dist/assets/{UserStats-BgnHXg6C.js → UserStats-ck9Oh4qt.js} +2 -2
  34. package/dist/assets/{UserStats-BgnHXg6C.js.map → UserStats-ck9Oh4qt.js.map} +1 -1
  35. package/dist/assets/{VerifyEmail-BLfciV-4.js → VerifyEmail-BYt5xfhm.js} +2 -2
  36. package/dist/assets/{VerifyEmail-BLfciV-4.js.map → VerifyEmail-BYt5xfhm.js.map} +1 -1
  37. package/dist/assets/{common-ui.es-I814CYyx.js → common-ui.es-BrXWBn7p.js} +6 -6
  38. package/dist/assets/common-ui.es-BrXWBn7p.js.map +1 -0
  39. package/dist/assets/common-ui.es-DKUQOgQx.js +1 -0
  40. package/dist/assets/dist-5cG6aSxW.js +1 -0
  41. package/dist/assets/{dist-DkeESCP4.js → dist-CzLl7g6t.js} +11 -10
  42. package/dist/assets/dist-CzLl7g6t.js.map +1 -0
  43. package/dist/assets/{dist-BwVFN4Xe.js → dist-D5-LrzD6.js} +3 -3
  44. package/dist/assets/{dist-BwVFN4Xe.js.map → dist-D5-LrzD6.js.map} +1 -1
  45. package/dist/assets/dist-y6_rrqhr.js +1 -0
  46. package/dist/assets/{edit-ui.es-Cs9EI-Ey.js → edit-ui.es-C9MhXwkt.js} +2 -2
  47. package/dist/assets/{edit-ui.es-Cs9EI-Ey.js.map → edit-ui.es-C9MhXwkt.js.map} +1 -1
  48. package/dist/assets/edit-ui.es-DT9CMaNG.js +1 -0
  49. package/dist/assets/{index-DotoyByQ.css → index-C8gy3Ea0.css} +1 -1
  50. package/dist/assets/{index-CF-NaZ2k.js → index-DUZItsH-.js} +4 -4
  51. package/dist/assets/{index-CF-NaZ2k.js.map → index-DUZItsH-.js.map} +1 -1
  52. package/dist/assets/{server-Cl1ZnRx7.js → server-Db_WegNj.js} +2 -2
  53. package/dist/assets/{server-Cl1ZnRx7.js.map → server-Db_WegNj.js.map} +1 -1
  54. package/dist/index.html +4 -4
  55. package/dist/sw.js +1 -1
  56. package/dist/sw.js.map +1 -1
  57. package/package.json +8 -8
  58. package/dist/assets/common-ui.es-B6tKmOxI.js +0 -1
  59. package/dist/assets/common-ui.es-I814CYyx.js.map +0 -1
  60. package/dist/assets/dist-BjDnRspm.js +0 -1
  61. package/dist/assets/dist-CFgc27Ho.js +0 -1
  62. package/dist/assets/dist-DkeESCP4.js.map +0 -1
  63. package/dist/assets/edit-ui.es-C_I54ckd.js +0 -1
@@ -0,0 +1 @@
1
+ import"./vue.runtime.esm-bundler-C3q8JS9f.js";import"./common-ui.es-BrXWBn7p.js";import"./dist-CrcJCHFk.js";import{T as e,m as t}from"./MarkdownRenderer-DoVbFpA6-BIbDxYC4.js";import"./dist-CzLl7g6t.js";export{e as MarkdownRenderer,t as piniaPlugin};
@@ -0,0 +1 @@
1
+ import"./vue.runtime.esm-bundler-C3q8JS9f.js";import"./common-ui.es-BrXWBn7p.js";import"./dist-CrcJCHFk.js";import"./MarkdownRenderer-DoVbFpA6-BIbDxYC4.js";import"./dist-CzLl7g6t.js";import{g as e}from"./dist-D5-LrzD6.js";export{e as allCourseWare};
@@ -224,7 +224,7 @@ Example:
224
224
  window.skuilder.pipeline.showLastRun()
225
225
  window.skuilder.pipeline.showRun(1)
226
226
  await window.skuilder.pipeline.diagnoseCardSpace()
227
- `)}},mountPipelineDebugger()}}),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 ELONavigator,init_elo=__esm({"src/core/navigators/generators/elo.ts"(){init_navigators(),init_logger(),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`})),o=a.map(e=>e.cardID),s=await this.course.getCardEloData(o),c=a.map((e,t)=>{let r=s[t]?.global?.score??1e3,a=Math.abs(r-n),o=Math.max(0,1-a/500),c=o>0?Math.random()**(1/o):0;return{cardId:e.cardID,courseId:e.courseID,score:c,provenance:[{strategy:`elo`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-ELO-default`,action:`generated`,score:c,reason:`ELO distance ${Math.round(a)} (card: ${Math.round(r)}, user: ${Math.round(n)}), raw ${o.toFixed(3)}, key ${c.toFixed(3)}`}]}});c.sort((e,t)=>t.score-e.score);let l=c.slice(0,e);if(l.length>0){let e=l.slice(0,3).map(e=>e.score.toFixed(2)).join(`, `);logger.info(`[ELO] Course ${this.course.getCourseID()}: ${l.length} new cards (top scores: ${e})`)}else logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);return{cards:l}}}}}),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,BASE_TARGET_SCORE,BASE_SUPPORT_SCORE,DISCOVERED_SUPPORT_SCORE,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,BASE_TARGET_SCORE=1,BASE_SUPPORT_SCORE=.8,DISCOVERED_SUPPORT_SCORE=12,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=[];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);S.push(...r,...o,...u)}let T=this.buildSupportHintSummary(w),E=Object.keys(T.boostTags).length>0?{boostTags:T.boostTags,_label:`prescribed-support (${T.supportTags.length} tags; blocked=${T.blockedTargetIds.length}; testversion=${PRESCRIBED_DEBUG_VERSION})`}:void 0;if(E){let e=Object.entries(E.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)`+(E?` — boost hints emitted but may not survive filters`:``)),await this.putStrategyState(x).catch(e=>{logger.debug(`[Prescribed] Failed to persist empty-state update: ${e}`)}),E?{cards:[],hints:E}:{cards:[]};let D=pickTopByScore(S,e),O=new Map;for(let e of D){let t=e.provenance[0],n=t?.reason.match(/group=([^;]+)/)?.[1],r=t?.reason.includes(`mode=support`)?`supportIds`:`targetIds`;n&&(O.has(n)||O.set(n,{targetIds:[],supportIds:[]}),O.get(n)[r].push(e.cardId))}for(let e of this.config.groups){let t=x.groups[e.id],n=O.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 ${D.length} cards (${D.filter(e=>e.provenance[0]?.reason.includes(`mode=target`)).length} target, ${D.filter(e=>e.provenance[0]?.reason.includes(`mode=support`)).length} support, ${D.filter(e=>e.provenance[0]?.reason.includes(`mode=discovered-support`)).length} discovered support)`),E?{cards:D,hints:E}:{cards:D}}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})).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}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,MAX_BACKLOG_PRESSURE,SRSNavigator,init_srs=__esm({"src/core/navigators/generators/srs.ts"(){init_navigators(),init_logger(),DEFAULT_HEALTHY_BACKLOG=20,MAX_BACKLOG_PRESSURE=.5,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))),s=this.computeBacklogPressure(o.length);if(o.length>0){let e=s>0?` [backlog pressure: +${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`);return{cards: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).slice(0,e)}}computeBacklogPressure(e){if(e<=this.healthyBacklog)return 0;let t=(e-this.healthyBacklog)/this.healthyBacklog*(MAX_BACKLOG_PRESSURE/2);return Math.min(MAX_BACKLOG_PRESSURE,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,d=Math.min(1,u+n),p=[`${Math.round(s)}h overdue`,`interval: ${Math.round(o)}h`,`relative: ${c.toFixed(2)}`,`recency: ${l.toFixed(2)}`];return n>0&&p.push(`backlog: +${n.toFixed(2)}`),p.push(`review`),{score:d,reason:p.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});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?`
227
+ `)}},mountPipelineDebugger()}}),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 ELONavigator,init_elo=__esm({"src/core/navigators/generators/elo.ts"(){init_navigators(),init_logger(),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`})),o=a.map(e=>e.cardID),s=await this.course.getCardEloData(o),c=a.map((e,t)=>{let r=s[t]?.global?.score??1e3,a=Math.abs(r-n),o=Math.max(0,1-a/500),c=o>0?Math.random()**(1/o):0;return{cardId:e.cardID,courseId:e.courseID,score:c,provenance:[{strategy:`elo`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-ELO-default`,action:`generated`,score:c,reason:`ELO distance ${Math.round(a)} (card: ${Math.round(r)}, user: ${Math.round(n)}), raw ${o.toFixed(3)}, key ${c.toFixed(3)}`}]}});c.sort((e,t)=>t.score-e.score);let l=c.slice(0,e);if(l.length>0){let e=l.slice(0,3).map(e=>e.score.toFixed(2)).join(`, `);logger.info(`[ELO] Course ${this.course.getCourseID()}: ${l.length} new cards (top scores: ${e})`)}else logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);return{cards:l}}}}}),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,BASE_TARGET_SCORE,BASE_SUPPORT_SCORE,DISCOVERED_SUPPORT_SCORE,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,BASE_TARGET_SCORE=1,BASE_SUPPORT_SCORE=.8,DISCOVERED_SUPPORT_SCORE=12,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=[];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);S.push(...r,...o,...u)}let T=this.buildSupportHintSummary(w),E=Object.keys(T.boostTags).length>0?{boostTags:T.boostTags,_label:`prescribed-support (${T.supportTags.length} tags; blocked=${T.blockedTargetIds.length}; testversion=${PRESCRIBED_DEBUG_VERSION})`}:void 0;if(E){let e=Object.entries(E.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)`+(E?` — boost hints emitted but may not survive filters`:``)),await this.putStrategyState(x).catch(e=>{logger.debug(`[Prescribed] Failed to persist empty-state update: ${e}`)}),E?{cards:[],hints:E}:{cards:[]};let D=pickTopByScore(S,e),O=new Map;for(let e of D){let t=e.provenance[0],n=t?.reason.match(/group=([^;]+)/)?.[1],r=t?.reason.includes(`mode=support`)?`supportIds`:`targetIds`;n&&(O.has(n)||O.set(n,{targetIds:[],supportIds:[]}),O.get(n)[r].push(e.cardId))}for(let e of this.config.groups){let t=x.groups[e.id],n=O.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 ${D.length} cards (${D.filter(e=>e.provenance[0]?.reason.includes(`mode=target`)).length} target, ${D.filter(e=>e.provenance[0]?.reason.includes(`mode=support`)).length} support, ${D.filter(e=>e.provenance[0]?.reason.includes(`mode=discovered-support`)).length} discovered support)`),E?{cards:D,hints:E}:{cards:D}}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})).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}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,MAX_BACKLOG_PRESSURE,SRSNavigator,init_srs=__esm({"src/core/navigators/generators/srs.ts"(){init_navigators(),init_logger(),DEFAULT_HEALTHY_BACKLOG=20,MAX_BACKLOG_PRESSURE=.5,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.computeBacklogPressure(o.length);if(o.length>0){let e=s>0?` [backlog pressure: +${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`);return{cards: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).slice(0,e)}}computeBacklogPressure(e){if(e<=this.healthyBacklog)return 0;let t=(e-this.healthyBacklog)/this.healthyBacklog*(MAX_BACKLOG_PRESSURE/2);return Math.min(MAX_BACKLOG_PRESSURE,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,d=Math.min(1,u+n),p=[`${Math.round(s)}h overdue`,`interval: ${Math.round(o)}h`,`relative: ${c.toFixed(2)}`,`recency: ${l.toFixed(2)}`];return n>0&&p.push(`backlog: +${n.toFixed(2)}`),p.push(`review`),{score:d,reason:p.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});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?`
228
228
  - `+t.map(e=>e.name).join(`
229
229
  - `):` none`;logger.info(`[Pipeline] Configuration:
230
230
  Generator: ${e.name}
@@ -251,7 +251,7 @@ Currently logged-in as ${this._username}.`);let n=await this.syncStrategy.create
251
251
  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();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){let t=hooks.utc().add(e,`days`);return this.getReviewstoDate(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 instanceof Error&&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 instanceof Error&&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:
252
252
  name:${r.name}
253
253
  error: ${r.error}
254
- 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(DocTypePrefixes.CARDRECORD)&&r.push(e.id.substr(DocTypePrefixes.CARDRECORD.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:
254
+ 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:
255
255
  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) {
256
256
  if (doc._id && doc._id.indexOf('card_review') === 0 && doc.courseId && doc.cardId) {
257
257
  emit(doc._id, doc.courseId + '-' + doc.cardId);
@@ -294,7 +294,7 @@ Examples:
294
294
  Card: ${JSON.stringify(e.cardElo)})
295
295
  `)}}else logger.warn(`[EloService] Partial ELO update (per-tag):
296
296
  User ELO update: ${c?`SUCCESS`:`FAILED`}
297
- Card ELO update: ${l?`SUCCESS`:`FAILED`}`),!c&&a[0].status===`rejected`&&logger.error(`[EloService] User ELO update error:`,a[0].reason),!l&&a[1].status===`rejected`&&logger.error(`[EloService] Card ELO update error:`,a[1].reason)}}};init_core(),init_logger();var ResponseProcessor=class{constructor(e,t){_defineProperty(this,`srsService`,void 0),_defineProperty(this,`eloService`,void 0),this.srsService=e,this.eloService=t}parsePerformance(e){return typeof e==`number`?{globalScore:e,taggedPerformance:null}:isTaggedPerformance(e)?{globalScore:e._global,taggedPerformance:e}:(logger.warn(`[ResponseProcessor] Unexpected performance structure, using neutral score`,{performance:e}),{globalScore:.5,taggedPerformance:null})}async processResponse(e,t,n,r,a,o,s,c,l,u){if(!isQuestionRecord(e))return{nextCardAction:`dismiss-success`,shouldLoadNextCard:!0,isCorrect:!0,shouldClearFeedbackShadow:!0};try{let d=await t,p;return p=e.isCorrect?this.processCorrectResponse(e,d,n,r,a,o,s):this.processIncorrectResponse(e,d,r,a,o,s,c,l,u),e.deferAdvance&&p.shouldLoadNextCard&&(logger.info(`[ResponseProcessor] deferAdvance requested — suppressing navigation, action stashed:`,{nextCardAction:p.nextCardAction}),p={...p,shouldLoadNextCard:!1,deferred:!0}),p}catch(e){throw logger.error(`[ResponseProcessor] Failed to load card history`,{e,cardId:s}),e}}processCorrectResponse(e,t,n,r,a,o,s){if(e.priorAttemps===0){this.srsService.scheduleReview(t,n);let{globalScore:c,taggedPerformance:l}=this.parsePerformance(e.performance);if(l){let e=Object.keys(l).filter(e=>e!==`_global`),t=e.filter(e=>l[e]===null),n=e.filter(e=>l[e]!==null);logger.info(`[ResponseProcessor] per-tag ELO update for ${s}: scored=[${n.join(`, `)}] count-only=[${t.join(`, `)}]`),this.eloService.updateUserAndCardEloPerTag(l,o,s,r,a)}else{let e=.5+c/2;if(t.records.length===1)this.eloService.updateUserAndCardElo(e,o,s,r,a);else{let n=Math.ceil(32/t.records.length);this.eloService.updateUserAndCardElo(e,o,s,r,a,n)}logger.info(`[ResponseProcessor] Processed correct response with SRS scheduling and ELO update`)}return{nextCardAction:`dismiss-success`,shouldLoadNextCard:!0,isCorrect:!0,performanceScore:c,shouldClearFeedbackShadow:!0}}else{logger.info(`[ResponseProcessor] Processed correct response (retry attempt - no scheduling/ELO)`);let{globalScore:t}=this.parsePerformance(e.performance);return{nextCardAction:`marked-failed`,shouldLoadNextCard:!0,isCorrect:!0,performanceScore:t,shouldClearFeedbackShadow:!0}}}processIncorrectResponse(e,t,n,r,a,o,s,c,l){let{taggedPerformance:u}=this.parsePerformance(e.performance);return t.records.length!==1&&e.priorAttemps===0?u?(this.eloService.updateUserAndCardEloPerTag(u,a,o,n,r),logger.info(`[ResponseProcessor] Processed incorrect response with per-tag ELO update (${Object.keys(u).length-1} tags)`)):(this.eloService.updateUserAndCardElo(0,a,o,n,r),logger.info(`[ResponseProcessor] Processed incorrect response with ELO update`)):logger.info(`[ResponseProcessor] Processed incorrect response (no ELO update needed)`),r.records.length>=s?l>=c?(u?this.eloService.updateUserAndCardEloPerTag(u,a,o,n,r):this.eloService.updateUserAndCardElo(0,a,o,n,r),{nextCardAction:`dismiss-failed`,shouldLoadNextCard:!0,isCorrect:!1,shouldClearFeedbackShadow:!0}):{nextCardAction:`marked-failed`,shouldLoadNextCard:!0,isCorrect:!1,shouldClearFeedbackShadow:!0}:{nextCardAction:`none`,shouldLoadNextCard:!1,isCorrect:!1,shouldClearFeedbackShadow:!0}}};init_logger();function parseAudioURIs(e){return typeof e==`string`?e.match(/https?:\/\/[^\s"'<>]+\.(wav|mp3|ogg|m4a|aac|webm)/gi)??[]:[]}function prefetchAudio(e){return new Promise(t=>{let n=new Audio;n.preload=`auto`;let cleanup=()=>{n.oncanplaythrough=null,n.onerror=null};n.oncanplaythrough=()=>{cleanup(),t()},n.onerror=()=>{cleanup(),logger.warn(`[CardHydrationService] Failed to prefetch audio: ${e}`),t()},n.src=e})}var CardHydrationService=class{constructor(e,t,n){_defineProperty(this,`hydratedCards`,new Map),_defineProperty(this,`hydrationInFlight`,new Set),_defineProperty(this,`hydrationInProgress`,!1),this.getViewComponent=e,this.getCourseDB=t,this.getItemsToHydrate=n}getHydratedCard(e){return this.hydratedCards.get(e)??null}hasHydratedCard(e){return this.hydratedCards.has(e)}removeCard(e){this.hydratedCards.delete(e)}async ensureHydratedCards(){this.fillHydratedCards()}async waitForCard(e){if(this.hydratedCards.has(e))return this.hydratedCards.get(e);this.hydrationInProgress||this.fillHydratedCards();let t=1e4,n=25,r=0;for(;r<1e4;){if(this.hydratedCards.has(e))return this.hydratedCards.get(e);if(!this.hydrationInFlight.has(e)&&!this.hydrationInProgress)break;await new Promise(e=>setTimeout(e,25)),r+=25}return this.hydratedCards.get(e)??null}get hydratedCount(){return this.hydratedCards.size}getHydratedCardIds(){return Array.from(this.hydratedCards.keys())}async fillHydratedCards(){if(!this.hydrationInProgress){this.hydrationInProgress=!0;try{let e=this.getItemsToHydrate();for(let t of e)if(!(this.hydratedCards.has(t.cardID)||this.hydrationInFlight.has(t.cardID)))try{await this.hydrateCard(t)}catch(e){logger.error(`[CardHydrationService] Error hydrating card ${t.cardID}:`,e)}}finally{this.hydrationInProgress=!1}}}async hydrateCard(e){if(!(this.hydratedCards.has(e.cardID)||this.hydrationInFlight.has(e.cardID))){this.hydrationInFlight.add(e.cardID);try{let t=this.getCourseDB(e.courseID),[n,r]=await Promise.all([t.getCourseDoc(e.cardID),t.getAppliedTagsBatch([e.cardID])]);isCourseElo(n.elo)||(n.elo=toCourseElo(n.elo));let a=this.getViewComponent(n.id_view),o=await Promise.all(n.id_displayable_data.map(e=>t.getCourseDoc(e,{attachments:!0,binary:!0}))),s=[];o.forEach(e=>{e.data.forEach(e=>{s.push(...parseAudioURIs(e.data))})});let c=[...new Set(s)];c.length>0&&(logger.debug(`[CardHydrationService] Prefetching ${c.length} audio files for card ${e.cardID}`),await Promise.allSettled(c.map(prefetchAudio)));let l=o.map(displayableDataToViewData).reverse();this.hydratedCards.set(e.cardID,{item:e,view:a,data:l,tags:r.get(e.cardID)??[]}),logger.debug(`[CardHydrationService] Hydrated card ${e.cardID}`)}finally{this.hydrationInFlight.delete(e.cardID)}}}},ItemQueue=class{constructor(){_defineProperty(this,`q`,[]),_defineProperty(this,`seenCardIds`,[]),_defineProperty(this,`_dequeueCount`,0)}get dequeueCount(){return this._dequeueCount}add(e,t){this.seenCardIds.find(e=>e===t)||(this.seenCardIds.push(t),this.q.push(e))}addAll(e,t){e.forEach(e=>this.add(e,t(e)))}get length(){return this.q.length}peek(e){return this.q[e]}dequeue(e){if(this.q.length!==0){this._dequeueCount++;let t=this.q.splice(0,1)[0];if(e){let n=e(t),r=this.seenCardIds.indexOf(n);r>-1&&this.seenCardIds.splice(r,1)}return t}else return null}replaceAll(e,t){this.q=[],this.seenCardIds=[];for(let n of e){let e=t(n);this.seenCardIds.includes(e)||(this.seenCardIds.push(e),this.q.push(n))}}mergeToFront(e,t){let n=0,r=[];for(let a of e){let e=t(a);this.seenCardIds.includes(e)||(this.seenCardIds.push(e),r.push(a),n++)}return this.q.unshift(...r),n}get toString(){return`${typeof this.q[0]}:
297
+ Card ELO update: ${l?`SUCCESS`:`FAILED`}`),!c&&a[0].status===`rejected`&&logger.error(`[EloService] User ELO update error:`,a[0].reason),!l&&a[1].status===`rejected`&&logger.error(`[EloService] Card ELO update error:`,a[1].reason)}}};init_core(),init_logger();var ResponseProcessor=class{constructor(e,t){_defineProperty(this,`srsService`,void 0),_defineProperty(this,`eloService`,void 0),this.srsService=e,this.eloService=t}parsePerformance(e){return typeof e==`number`?{globalScore:e,taggedPerformance:null}:isTaggedPerformance(e)?{globalScore:e._global,taggedPerformance:e}:(logger.warn(`[ResponseProcessor] Unexpected performance structure, using neutral score`,{performance:e}),{globalScore:.5,taggedPerformance:null})}async processResponse(e,t,n,r,a,o,s,c,l,u){if(!isQuestionRecord(e))return{nextCardAction:`dismiss-success`,shouldLoadNextCard:!0,isCorrect:!0,shouldClearFeedbackShadow:!0};try{let d=await t,p;return p=e.isCorrect?this.processCorrectResponse(e,d,n,r,a,o,s):this.processIncorrectResponse(e,d,r,a,o,s,c,l,u),e.deferAdvance&&p.shouldLoadNextCard&&(logger.info(`[ResponseProcessor] deferAdvance requested — suppressing navigation, action stashed:`,{nextCardAction:p.nextCardAction}),p={...p,shouldLoadNextCard:!1,deferred:!0}),p}catch(e){throw logger.error(`[ResponseProcessor] Failed to load card history`,{e,cardId:s}),e}}processCorrectResponse(e,t,n,r,a,o,s){if(e.priorAttemps===0){a.card.tags.includes(`srs:skip`)||this.srsService.scheduleReview(t,n);let{globalScore:c,taggedPerformance:l}=this.parsePerformance(e.performance);if(l){let e=Object.keys(l).filter(e=>e!==`_global`),t=e.filter(e=>l[e]===null),n=e.filter(e=>l[e]!==null);logger.info(`[ResponseProcessor] per-tag ELO update for ${s}: scored=[${n.join(`, `)}] count-only=[${t.join(`, `)}]`),this.eloService.updateUserAndCardEloPerTag(l,o,s,r,a)}else{let e=.5+c/2;if(t.records.length===1)this.eloService.updateUserAndCardElo(e,o,s,r,a);else{let n=Math.ceil(32/t.records.length);this.eloService.updateUserAndCardElo(e,o,s,r,a,n)}logger.info(`[ResponseProcessor] Processed correct response with SRS scheduling and ELO update`)}return{nextCardAction:`dismiss-success`,shouldLoadNextCard:!0,isCorrect:!0,performanceScore:c,shouldClearFeedbackShadow:!0}}else{logger.info(`[ResponseProcessor] Processed correct response (retry attempt - no scheduling/ELO)`);let{globalScore:t}=this.parsePerformance(e.performance);return{nextCardAction:`marked-failed`,shouldLoadNextCard:!0,isCorrect:!0,performanceScore:t,shouldClearFeedbackShadow:!0}}}processIncorrectResponse(e,t,n,r,a,o,s,c,l){let{taggedPerformance:u}=this.parsePerformance(e.performance);return t.records.length!==1&&e.priorAttemps===0?u?(this.eloService.updateUserAndCardEloPerTag(u,a,o,n,r),logger.info(`[ResponseProcessor] Processed incorrect response with per-tag ELO update (${Object.keys(u).length-1} tags)`)):(this.eloService.updateUserAndCardElo(0,a,o,n,r),logger.info(`[ResponseProcessor] Processed incorrect response with ELO update`)):logger.info(`[ResponseProcessor] Processed incorrect response (no ELO update needed)`),r.records.length>=s?l>=c?(u?this.eloService.updateUserAndCardEloPerTag(u,a,o,n,r):this.eloService.updateUserAndCardElo(0,a,o,n,r),{nextCardAction:`dismiss-failed`,shouldLoadNextCard:!0,isCorrect:!1,shouldClearFeedbackShadow:!0}):{nextCardAction:`marked-failed`,shouldLoadNextCard:!0,isCorrect:!1,shouldClearFeedbackShadow:!0}:{nextCardAction:`none`,shouldLoadNextCard:!1,isCorrect:!1,shouldClearFeedbackShadow:!0}}};init_logger();function parseAudioURIs(e){return typeof e==`string`?e.match(/https?:\/\/[^\s"'<>]+\.(wav|mp3|ogg|m4a|aac|webm)/gi)??[]:[]}function prefetchAudio(e){return new Promise(t=>{let n=new Audio;n.preload=`auto`;let cleanup=()=>{n.oncanplaythrough=null,n.onerror=null};n.oncanplaythrough=()=>{cleanup(),t()},n.onerror=()=>{cleanup(),logger.warn(`[CardHydrationService] Failed to prefetch audio: ${e}`),t()},n.src=e})}var CardHydrationService=class{constructor(e,t,n){_defineProperty(this,`hydratedCards`,new Map),_defineProperty(this,`hydrationInFlight`,new Set),_defineProperty(this,`hydrationInProgress`,!1),this.getViewComponent=e,this.getCourseDB=t,this.getItemsToHydrate=n}getHydratedCard(e){return this.hydratedCards.get(e)??null}hasHydratedCard(e){return this.hydratedCards.has(e)}removeCard(e){this.hydratedCards.delete(e)}async ensureHydratedCards(){this.fillHydratedCards()}async waitForCard(e){if(this.hydratedCards.has(e))return this.hydratedCards.get(e);this.hydrationInProgress||this.fillHydratedCards();let t=1e4,n=25,r=0;for(;r<1e4;){if(this.hydratedCards.has(e))return this.hydratedCards.get(e);if(!this.hydrationInFlight.has(e)&&!this.hydrationInProgress)break;await new Promise(e=>setTimeout(e,25)),r+=25}return this.hydratedCards.get(e)??null}get hydratedCount(){return this.hydratedCards.size}getHydratedCardIds(){return Array.from(this.hydratedCards.keys())}async fillHydratedCards(){if(!this.hydrationInProgress){this.hydrationInProgress=!0;try{let e=this.getItemsToHydrate();for(let t of e)if(!(this.hydratedCards.has(t.cardID)||this.hydrationInFlight.has(t.cardID)))try{await this.hydrateCard(t)}catch(e){logger.error(`[CardHydrationService] Error hydrating card ${t.cardID}:`,e)}}finally{this.hydrationInProgress=!1}}}async hydrateCard(e){if(!(this.hydratedCards.has(e.cardID)||this.hydrationInFlight.has(e.cardID))){this.hydrationInFlight.add(e.cardID);try{let t=this.getCourseDB(e.courseID),[n,r]=await Promise.all([t.getCourseDoc(e.cardID),t.getAppliedTagsBatch([e.cardID])]);isCourseElo(n.elo)||(n.elo=toCourseElo(n.elo));let a=this.getViewComponent(n.id_view),o=await Promise.all(n.id_displayable_data.map(e=>t.getCourseDoc(e,{attachments:!0,binary:!0}))),s=[];o.forEach(e=>{e.data.forEach(e=>{s.push(...parseAudioURIs(e.data))})});let c=[...new Set(s)];c.length>0&&(logger.debug(`[CardHydrationService] Prefetching ${c.length} audio files for card ${e.cardID}`),await Promise.allSettled(c.map(prefetchAudio)));let l=o.map(displayableDataToViewData).reverse();this.hydratedCards.set(e.cardID,{item:e,view:a,data:l,tags:r.get(e.cardID)??[]}),logger.debug(`[CardHydrationService] Hydrated card ${e.cardID}`)}finally{this.hydrationInFlight.delete(e.cardID)}}}},ItemQueue=class{constructor(){_defineProperty(this,`q`,[]),_defineProperty(this,`seenCardIds`,[]),_defineProperty(this,`_dequeueCount`,0)}get dequeueCount(){return this._dequeueCount}add(e,t){this.seenCardIds.find(e=>e===t)||(this.seenCardIds.push(t),this.q.push(e))}addAll(e,t){e.forEach(e=>this.add(e,t(e)))}get length(){return this.q.length}peek(e){return this.q[e]}dequeue(e){if(this.q.length!==0){this._dequeueCount++;let t=this.q.splice(0,1)[0];if(e){let n=e(t),r=this.seenCardIds.indexOf(n);r>-1&&this.seenCardIds.splice(r,1)}return t}else return null}replaceAll(e,t){this.q=[],this.seenCardIds=[];for(let n of e){let e=t(n);this.seenCardIds.includes(e)||(this.seenCardIds.push(e),this.q.push(n))}}mergeToFront(e,t){let n=0,r=[];for(let a of e){let e=t(a);this.seenCardIds.includes(e)||(this.seenCardIds.push(e),r.push(a),n++)}return this.q.unshift(...r),n}get toString(){return`${typeof this.q[0]}:
298
298
  `+this.q.map(e=>` ${e.courseID}+${e.cardID}: ${e.status}`).join(`
299
299
  `)}};init_couch(),init_recording(),init_Loggable(),init_types_legacy(),init_logger();var CouchDBToStaticPacker=class{constructor(e={}){_defineProperty(this,`config`,void 0),_defineProperty(this,`sourceDB`,null),this.config={chunkSize:1e3,includeAttachments:!0,...e}}async packCourse(e,t){logger.info(`Starting static pack for course: ${t}`),this.sourceDB=e;let n={version:`1.0.0`,courseId:t,courseName:``,courseConfig:null,lastUpdated:new Date().toISOString(),documentCount:0,chunks:[],indices:[],designDocs:[]},r=await this.extractCourseConfig(e);n.courseName=r.name,n.courseConfig=r,n.designDocs=await this.extractDesignDocs(e);let a=await this.extractDocumentsByType(e),o=new Map;this.config.includeAttachments&&await this.extractAllAttachments(a,o);let s=new Map;for(let[e,t]of Object.entries(a)){let r=this.createChunks(t,e);n.chunks.push(...r),n.documentCount+=t.length,this.prepareChunkData(r,t,s)}let c=new Map;return n.indices=await this.buildIndices(a,n.designDocs,c),{manifest:n,chunks:s,indices:c,attachments:o}}async packCourseToFiles(e,t,n,r){logger.info(`Packing course ${t} to files in ${n}`);let a=await this.packCourse(e,t),o=await this.writePackedDataToFiles(a,n,r);return{manifest:a.manifest,filesWritten:o,attachmentsFound:a.attachments?a.attachments.size:0}}async writePackedDataToFiles(e,t,n){let r=0;await n.ensureDir(t);let a=n.joinPath(t,`manifest.json`);await n.writeJson(a,e.manifest,{spaces:2}),r++,logger.info(`Wrote manifest: ${a}`);let o=n.joinPath(t,`chunks`),s=n.joinPath(t,`indices`);await n.ensureDir(o),await n.ensureDir(s);for(let[t,a]of e.chunks){let e=n.joinPath(o,`${t}.json`);await n.writeJson(e,a),r++}logger.info(`Wrote ${e.chunks.size} chunk files`);for(let[t,a]of e.indices){let e=n.joinPath(s,`${t}.json`);await n.writeJson(e,a,{spaces:2}),r++}if(logger.info(`Wrote ${e.indices.size} index files`),e.attachments&&e.attachments.size>0){for(let[a,o]of e.attachments){let e=n.joinPath(t,a),s=n.dirname(e);await n.ensureDir(s),await n.writeFile(e,o.buffer),r++}logger.info(`Wrote ${e.attachments.size} attachment files`)}return r}async extractCourseConfig(e){try{return await e.get(`CourseConfig`)}catch(e){throw logger.error(`Failed to extract course config:`,e),Error(`Course config not found`)}}async extractDesignDocs(e){return(await e.allDocs({startkey:`_design/`,endkey:`_design/￰`,include_docs:!0})).rows.map(e=>({_id:e.id,views:e.doc.views||{}}))}async extractDocumentsByType(e){let t=await e.allDocs({include_docs:!0}),n={};for(let e of t.rows){if(e.id.startsWith(`_`))continue;let t=e.doc;t.docType&&(n[t.docType]||(n[t.docType]=[]),n[t.docType].push(t))}return n}createChunks(e,t){let n=[],r=e.sort((e,t)=>e._id.localeCompare(t._id));for(let e=0;e<r.length;e+=this.config.chunkSize){let a=r.slice(e,e+this.config.chunkSize),o=`${t}-${String(Math.floor(e/this.config.chunkSize)).padStart(4,`0`)}`;n.push({id:o,docType:t,startKey:a[0]._id,endKey:a[a.length-1]._id,documentCount:a.length,path:`chunks/${o}.json`})}return n}prepareChunkData(e,t,n){let r=t.sort((e,t)=>e._id.localeCompare(t._id));for(let t of e){let e=r.filter(e=>e._id>=t.startKey&&e._id<=t.endKey).map(e=>{let t={...e};return delete t._rev,this.config.includeAttachments&&t._attachments?t._attachments=this.transformAttachmentStubs(t._attachments,t._id):this.config.includeAttachments||delete t._attachments,t});n.set(t.id,e)}}async buildIndices(e,t,n){let r=[];if(e.CARD){let t=await this.buildEloIndex(e.CARD,n);r.push(t)}if(e.TAG){let t=await this.buildTagIndex(e.TAG,n);r.push(t)}for(let e of t)for(let[t,a]of Object.entries(e.views))if(a.map){logger.info(`Processing view: ${e._id}/${t}`);let a=await this.buildViewIndex(t,e,n);a?(r.push(a),logger.info(`Successfully built index: ${a.name}`)):logger.warn(`Skipped view index: ${e._id}/${t}`)}return r}async buildEloIndex(e,t){let n=[];for(let t of e)t.elo?.global?.score&&n.push({elo:t.elo.global.score,cardId:t._id});n.sort((e,t)=>e.elo-t.elo);let r={},a=50;for(let e of n){let t=Math.floor(e.elo/50)*50;r[t]||(r[t]=[]),r[t].push(e.cardId)}return t.set(`elo`,{sorted:n,buckets:r,stats:{min:n[0]?.elo||0,max:n[n.length-1]?.elo||0,count:n.length}}),{name:`elo`,type:`btree`,path:`indices/elo.json`}}async buildTagIndex(e,t){let n={};for(let t of e)n[t.name]={cardIds:t.taggedCards,snippet:t.snippet,count:t.taggedCards.length};let r={};for(let t of e)for(let e of t.taggedCards)r[e]||(r[e]=[]),r[e].push(t.name);return t.set(`tags`,{byTag:n,byCard:r}),{name:`tags`,type:`hash`,path:`indices/tags.json`}}async buildViewIndex(e,t,n){if(!this.sourceDB)return logger.error(`Source database not available for view querying`),null;try{let r=`${t._id.replace(`_design/`,``)}/${e}`;logger.info(`Querying CouchDB view: ${r}`);let a=await this.sourceDB.query(r,{include_docs:!1});if(!a.rows||a.rows.length===0)return logger.warn(`View ${r} returned no results`),null;logger.info(`Successfully queried view ${r}: ${a.rows.length} results`);let o=this.formatViewResults(e,a.rows,t),s=`view-${t._id.replace(`_design/`,``)}-${e}`;return n.set(s,o),{name:s,type:`view`,path:`indices/${s}.json`}}catch(n){return logger.error(`Failed to query view ${t._id}/${e}:`,n),null}}formatViewResults(e,t,n){let r={type:`couchdb-view`,viewName:e,designDoc:n._id,results:t,metadata:{resultCount:t.length,generatedAt:new Date().toISOString()}};switch(e){case`elo`:return this.formatEloViewIndex(t,r);case`getTags`:return this.formatTagsViewIndex(t,r);case`cardsByInexperience`:return this.formatInexperienceViewIndex(t,r);default:return this.formatGenericViewIndex(t,r)}}formatEloViewIndex(e,t){let n=e.sort((e,t)=>typeof e.key==`number`&&typeof t.key==`number`?e.key-t.key:0);return{...t,sorted:n,stats:{min:n[0]?.key||0,max:n[n.length-1]?.key||0,count:n.length}}}formatTagsViewIndex(e,t){let n={};for(let t of e){let e=t.key;typeof e==`string`&&(n[e]||(n[e]=[]),n[e].push(t.id))}return{...t,byTag:n,tagCount:Object.keys(n).length}}formatInexperienceViewIndex(e,t){let n=e.sort((e,t)=>typeof e.key==`number`&&typeof t.key==`number`?e.key-t.key:0);return{...t,sorted:n,stats:{minInexperience:n[0]?.key||0,maxInexperience:n[n.length-1]?.key||0,count:n.length}}}formatGenericViewIndex(e,t){return{...t}}async extractAllAttachments(e,t){logger.info(`Extracting attachments...`);let n=[];for(let t of Object.values(e))n.push(...t);let r=n.filter(e=>e._attachments&&Object.keys(e._attachments).length>0);if(r.length===0){logger.info(`No attachments found`);return}logger.info(`Found ${r.length} documents with attachments`);let a=r.map(e=>this.extractDocumentAttachments(e,t));await Promise.all(a),logger.info(`Extracted ${t.size} attachment files`)}async extractDocumentAttachments(e,t){if(!e._attachments||!this.sourceDB)return;let n=e._id;for(let[r,a]of Object.entries(e._attachments))try{let e=await this.sourceDB.getAttachment(n,r),o;if(e instanceof ArrayBuffer)o=Buffer.from(e);else if(Buffer.isBuffer(e))o=e;else{let t=e;o=Buffer.from(await t.arrayBuffer())}let s=`${r}${this.getFileExtension(a.content_type)}`,c=`attachments/${n}/${s}`;t.set(c,{docId:n,attachmentName:r,filename:s,path:c,contentType:a.content_type,length:a.length||o.length,digest:a.digest,buffer:o}),logger.debug(`Extracted attachment: ${c}`)}catch(e){throw logger.error(`Failed to extract attachment ${n}/${r}:`,e),Error(`Failed to extract attachment ${n}/${r}: ${e}`)}}transformAttachmentStubs(e,t){let n={};for(let[r,a]of Object.entries(e))n[r]={path:`attachments/${t}/${`${r}${this.getFileExtension(a.content_type)}`}`,content_type:a.content_type,length:a.length,digest:a.digest,stub:!1};return n}getFileExtension(e){return{"image/jpeg":`.jpg`,"image/jpg":`.jpg`,"image/png":`.png`,"image/gif":`.gif`,"image/webp":`.webp`,"audio/mpeg":`.mp3`,"audio/mp3":`.mp3`,"audio/wav":`.wav`,"audio/ogg":`.ogg`,"video/mp4":`.mp4`,"video/webm":`.webm`,"application/pdf":`.pdf`,"text/plain":`.txt`,"application/json":`.json`}[e]||``}};init_logger();var DEFAULT_MIGRATION_OPTIONS={chunkBatchSize:100,validateRoundTrip:!1,cleanupOnFailure:!0,timeout:3e5};init_logger();var FileSystemError=class extends Error{constructor(e,t,n,r){super(e),this.operation=t,this.filePath=n,this.cause=r,this.name=`FileSystemError`}},nodeFS2=null;try{typeof window>`u`&&typeof process<`u`&&process.versions?.node&&(nodeFS2=eval(`require`)(`fs`),nodeFS2.promises=nodeFS2.promises||eval(`require`)(`fs`).promises)}catch{}async function validateStaticCourse(e,t){let n={valid:!0,manifestExists:!1,chunksExist:!1,attachmentsExist:!1,errors:[],warnings:[]};try{if(t){if(!(await t.stat(e)).isDirectory())return n.errors.push(`Path is not a directory: ${e}`),n.valid=!1,n}else if(!nodeFS2)return n.errors.push(`File system access not available - validation skipped`),n.valid=!1,n;else if(!(await nodeFS2.promises.stat(e)).isDirectory())return n.errors.push(`Path is not a directory: ${e}`),n.valid=!1,n;let r=`${e}/manifest.json`;try{if(t)if(r=t.joinPath(e,`manifest.json`),await t.exists(r)){n.manifestExists=!0;let e=await t.readFile(r),a=JSON.parse(e);n.courseId=a.courseId,n.courseName=a.courseName,(!a.version||!a.courseId||!a.chunks||!Array.isArray(a.chunks))&&(n.errors.push(`Invalid manifest structure`),n.valid=!1)}else n.errors.push(`Manifest not found: ${r}`),n.valid=!1;else{r=`${e}/manifest.json`,await nodeFS2.promises.access(r),n.manifestExists=!0;let t=await nodeFS2.promises.readFile(r,`utf8`),a=JSON.parse(t);n.courseId=a.courseId,n.courseName=a.courseName,(!a.version||!a.courseId||!a.chunks||!Array.isArray(a.chunks))&&(n.errors.push(`Invalid manifest structure`),n.valid=!1)}}catch(e){let t=e instanceof FileSystemError?e.message:`Manifest not found or invalid: ${r}`;n.errors.push(t),n.valid=!1}let a=`${e}/chunks`;try{t?(a=t.joinPath(e,`chunks`),await t.exists(a)?(await t.stat(a)).isDirectory()?n.chunksExist=!0:(n.errors.push(`Chunks path is not a directory: ${a}`),n.valid=!1):(n.errors.push(`Chunks directory not found: ${a}`),n.valid=!1)):(a=`${e}/chunks`,(await nodeFS2.promises.stat(a)).isDirectory()?n.chunksExist=!0:(n.errors.push(`Chunks path is not a directory: ${a}`),n.valid=!1))}catch(e){let t=e instanceof FileSystemError?e.message:`Chunks directory not found: ${a}`;n.errors.push(t),n.valid=!1}let o;try{t?(o=t.joinPath(e,`attachments`),await t.exists(o)?(await t.stat(o)).isDirectory()&&(n.attachmentsExist=!0):n.warnings.push(`Attachments directory not found: ${o} (this is OK if course has no attachments)`)):(o=`${e}/attachments`,(await nodeFS2.promises.stat(o)).isDirectory()&&(n.attachmentsExist=!0))}catch(t){o=o||`${e}/attachments`;let r=t instanceof FileSystemError?t.message:`Attachments directory not found: ${o} (this is OK if course has no attachments)`;n.warnings.push(r)}}catch(e){n.errors.push(`Failed to validate static course: ${e instanceof Error?e.message:String(e)}`),n.valid=!1}return n}async function validateMigration(e,t,n){let r={valid:!0,documentCountMatch:!1,attachmentIntegrity:!1,viewFunctionality:!1,issues:[]};try{logger.info(`Starting migration validation...`),r.documentCountMatch=compareDocumentCounts(t,await getActualDocumentCounts(e),r.issues),await validateCourseConfig(e,n,r.issues),r.viewFunctionality=await validateViews(e,n,r.issues),r.attachmentIntegrity=await validateAttachmentIntegrity(e,r.issues),r.valid=r.documentCountMatch&&r.viewFunctionality&&r.attachmentIntegrity,logger.info(`Migration validation completed. Valid: ${r.valid}`),r.issues.length>0&&(logger.info(`Validation issues: ${r.issues.length}`),r.issues.forEach(e=>{e.type===`error`?logger.error(`${e.category}: ${e.message}`):logger.warn(`${e.category}: ${e.message}`)}))}catch(e){r.valid=!1,r.issues.push({type:`error`,category:`metadata`,message:`Validation failed: ${e instanceof Error?e.message:String(e)}`})}return r}async function getActualDocumentCounts(e){let t={};try{let n=await e.allDocs({include_docs:!0});for(let e of n.rows){if(e.id.startsWith(`_design/`)){t._design=(t._design||0)+1;continue}let n=e.doc;n&&n.docType?t[n.docType]=(t[n.docType]||0)+1:t.unknown=(t.unknown||0)+1}}catch(e){logger.error(`Failed to get actual document counts:`,e)}return t}function compareDocumentCounts(e,t,n){let r=!0;for(let[a,o]of Object.entries(e)){let e=t[a]||0;e!==o&&(r=!1,n.push({type:`error`,category:`documents`,message:`Document count mismatch for ${a}: expected ${o}, got ${e}`}))}for(let[r,a]of Object.entries(t))!e[r]&&r!==`_design`&&n.push({type:`warning`,category:`documents`,message:`Unexpected document type found: ${r} (${a} documents)`});return r}async function validateCourseConfig(e,t,n){try{let r=await e.get(`CourseConfig`);if(!r){n.push({type:`error`,category:`course_config`,message:`CourseConfig document not found after migration`});return}r.courseID||n.push({type:`warning`,category:`course_config`,message:`CourseConfig document missing courseID field`}),r.courseID!==t.courseId&&n.push({type:`warning`,category:`course_config`,message:`CourseConfig courseID mismatch: expected ${t.courseId}, got ${r.courseID}`}),logger.debug(`CourseConfig document validation passed`)}catch(e){e.status===404?n.push({type:`error`,category:`course_config`,message:`CourseConfig document not found in database`}):n.push({type:`error`,category:`course_config`,message:`Failed to validate CourseConfig document: ${e instanceof Error?e.message:String(e)}`})}}async function validateViews(e,t,n){let r=!0;try{for(let a of t.designDocs)try{if(!await e.get(a._id)){r=!1,n.push({type:`error`,category:`views`,message:`Design document not found: ${a._id}`});continue}for(let t of Object.keys(a.views))try{let n=`${a._id}/${t}`;await e.query(n,{limit:1})}catch(e){r=!1,n.push({type:`error`,category:`views`,message:`View not accessible: ${a._id}/${t} - ${e}`})}}catch(e){r=!1,n.push({type:`error`,category:`views`,message:`Failed to validate design document ${a._id}: ${e}`})}}catch(e){r=!1,n.push({type:`error`,category:`views`,message:`View validation failed: ${e instanceof Error?e.message:String(e)}`})}return r}async function validateAttachmentIntegrity(e,t){let n=!0;try{let r=await e.allDocs({include_docs:!0,limit:10}),a=0,o=0;for(let s of r.rows){let r=s.doc;if(r&&r._attachments)for(let[s,c]of Object.entries(r._attachments)){a++;try{await e.getAttachment(r._id,s)&&o++}catch(e){n=!1,t.push({type:`error`,category:`attachments`,message:`Attachment not accessible: ${r._id}/${s} - ${e}`})}}}a===0?t.push({type:`warning`,category:`attachments`,message:`No attachments found in sampled documents`}):logger.info(`Validated ${o}/${a} sampled attachments`)}catch(e){n=!1,t.push({type:`error`,category:`attachments`,message:`Attachment validation failed: ${e instanceof Error?e.message:String(e)}`})}return n}var nodeFS3=null,nodePath=null;try{typeof window>`u`&&typeof process<`u`&&process.versions?.node&&(nodeFS3=eval(`require`)(`fs`),nodePath=eval(`require`)(`path`),nodeFS3.promises=nodeFS3.promises||eval(`require`)(`fs`).promises)}catch{}var StaticToCouchDBMigrator=class{constructor(e={},t){_defineProperty(this,`options`,void 0),_defineProperty(this,`progressCallback`,void 0),_defineProperty(this,`fs`,void 0),this.options={...DEFAULT_MIGRATION_OPTIONS,...e},this.fs=t}setProgressCallback(e){this.progressCallback=e}async migrateCourse(e,t){let n=Date.now(),r={success:!1,documentsRestored:0,attachmentsRestored:0,designDocsRestored:0,courseConfigRestored:0,errors:[],warnings:[],migrationTime:0};try{logger.info(`Starting migration from ${e} to CouchDB`),this.reportProgress(`manifest`,0,1,`Validating static course...`);let a=await validateStaticCourse(e,this.fs);if(!a.valid)throw r.errors.push(...a.errors),Error(`Static course validation failed: ${a.errors.join(`, `)}`);r.warnings.push(...a.warnings),this.reportProgress(`manifest`,1,1,`Loading course manifest...`);let o=await this.loadManifest(e);logger.info(`Loaded manifest for course: ${o.courseId} (${o.courseName})`),this.reportProgress(`design_docs`,0,o.designDocs.length,`Restoring design documents...`);let s=await this.restoreDesignDocuments(o.designDocs,t);r.designDocsRestored=s.restored,r.errors.push(...s.errors),r.warnings.push(...s.warnings),this.reportProgress(`course_config`,0,1,`Restoring CourseConfig document...`);let c=await this.restoreCourseConfig(o,t);r.courseConfigRestored=c.restored,r.errors.push(...c.errors),r.warnings.push(...c.warnings),this.reportProgress(`course_config`,1,1,`CourseConfig document restored`);let l=this.calculateExpectedCounts(o);this.reportProgress(`documents`,0,o.documentCount,`Aggregating documents from chunks...`);let u=await this.aggregateDocuments(e,o),d=u.filter(e=>e._id!==`CourseConfig`);u.length!==d.length&&r.warnings.push(`Filtered out ${u.length-d.length} CourseConfig document(s) from chunks to prevent conflicts`),this.reportProgress(`documents`,d.length,o.documentCount,`Uploading documents to CouchDB...`);let p=await this.uploadDocuments(d,t);r.documentsRestored=p.restored,r.errors.push(...p.errors),r.warnings.push(...p.warnings);let m=u.filter(e=>e._attachments&&Object.keys(e._attachments).length>0);this.reportProgress(`attachments`,0,m.length,`Uploading attachments...`);let g=await this.uploadAttachments(e,m,t);if(r.attachmentsRestored=g.restored,r.errors.push(...g.errors),r.warnings.push(...g.warnings),this.options.validateRoundTrip){this.reportProgress(`validation`,0,1,`Validating migration...`);let e=await validateMigration(t,l,o);e.valid||(r.warnings.push(`Migration validation found issues`),e.issues.forEach(e=>{e.type===`error`?r.errors.push(`Validation: ${e.message}`):r.warnings.push(`Validation: ${e.message}`)})),this.reportProgress(`validation`,1,1,`Migration validation completed`)}r.success=r.errors.length===0,r.migrationTime=Date.now()-n,logger.info(`Migration completed in ${r.migrationTime}ms`),logger.info(`Documents restored: ${r.documentsRestored}`),logger.info(`Attachments restored: ${r.attachmentsRestored}`),logger.info(`Design docs restored: ${r.designDocsRestored}`),logger.info(`CourseConfig restored: ${r.courseConfigRestored}`),r.errors.length>0&&logger.error(`Migration completed with ${r.errors.length} errors`),r.warnings.length>0&&logger.warn(`Migration completed with ${r.warnings.length} warnings`)}catch(e){r.success=!1,r.migrationTime=Date.now()-n;let a=e instanceof Error?e.message:String(e);if(r.errors.push(`Migration failed: ${a}`),logger.error(`Migration failed:`,e),this.options.cleanupOnFailure)try{await this.cleanupFailedMigration(t)}catch(e){logger.error(`Failed to cleanup after migration failure:`,e),r.warnings.push(`Failed to cleanup after migration failure`)}}return r}async loadManifest(e){try{let t,n;if(this.fs)n=this.fs.joinPath(e,`manifest.json`),t=await this.fs.readFile(n);else if(n=nodeFS3&&nodePath?nodePath.join(e,`manifest.json`):`${e}/manifest.json`,nodeFS3&&this.isLocalPath(e))t=await nodeFS3.promises.readFile(n,`utf8`);else{let e=await fetch(n);if(!e.ok)throw Error(`Failed to fetch manifest: ${e.status} ${e.statusText}`);t=await e.text()}let r=JSON.parse(t);if(!r.version||!r.courseId||!r.chunks)throw Error(`Invalid manifest structure`);return r}catch(e){let t=e instanceof FileSystemError?e.message:`Failed to load manifest: ${e instanceof Error?e.message:String(e)}`;throw Error(t)}}async restoreDesignDocuments(e,t){let n={restored:0,errors:[],warnings:[]};for(let r=0;r<e.length;r++){let a=e[r];this.reportProgress(`design_docs`,r,e.length,`Restoring ${a._id}...`);try{let e;try{e=await t.get(a._id)}catch{}let r={_id:a._id,views:a.views};e?(r._rev=e._rev,logger.debug(`Updating existing design document: ${a._id}`)):logger.debug(`Creating new design document: ${a._id}`),await t.put(r),n.restored++}catch(e){let t=`Failed to restore design document ${a._id}: ${e instanceof Error?e.message:String(e)}`;n.errors.push(t),logger.error(t)}}return this.reportProgress(`design_docs`,e.length,e.length,`Restored ${n.restored} design documents`),n}async aggregateDocuments(e,t){let n=[],r=new Map;for(let a=0;a<t.chunks.length;a++){let o=t.chunks[a];this.reportProgress(`documents`,n.length,t.documentCount,`Loading chunk ${o.id}...`);try{let t=await this.loadChunk(e,o);for(let e of t){if(!e._id){logger.warn(`Document without _id found in chunk ${o.id}, skipping`);continue}r.has(e._id)&&logger.warn(`Duplicate document ID found: ${e._id}, using latest version`),r.set(e._id,e)}}catch(e){throw Error(`Failed to load chunk ${o.id}: ${e instanceof Error?e.message:String(e)}`)}}return n.push(...r.values()),logger.info(`Aggregated ${n.length} unique documents from ${t.chunks.length} chunks`),n}async loadChunk(e,t){try{let n,r;if(this.fs)r=this.fs.joinPath(e,t.path),n=await this.fs.readFile(r);else if(r=nodeFS3&&nodePath?nodePath.join(e,t.path):`${e}/${t.path}`,nodeFS3&&this.isLocalPath(e))n=await nodeFS3.promises.readFile(r,`utf8`);else{let e=await fetch(r);if(!e.ok)throw Error(`Failed to fetch chunk: ${e.status} ${e.statusText}`);n=await e.text()}let a=JSON.parse(n);if(!Array.isArray(a))throw Error(`Chunk file does not contain an array of documents`);return a}catch(e){let t=e instanceof FileSystemError?e.message:`Failed to load chunk: ${e instanceof Error?e.message:String(e)}`;throw Error(t)}}async uploadDocuments(e,t){let n={restored:0,errors:[],warnings:[]},r=this.options.chunkBatchSize;for(let a=0;a<e.length;a+=r){let o=e.slice(a,a+r);this.reportProgress(`documents`,a,e.length,`Uploading batch ${Math.floor(a/r)+1}...`);try{let e=o.map(e=>{let t={...e};return delete t._rev,delete t._attachments,t}),r=await t.bulkDocs(e);for(let e=0;e<r.length;e++){let t=r[e],a=o[e];if(`error`in t){let e=`Failed to upload document ${a._id}: ${t.error} - ${t.reason}`;n.errors.push(e),logger.error(e)}else n.restored++}}catch(e){let t;t=e instanceof Error||e&&typeof e==`object`&&`message`in e?`Failed to upload document batch starting at index ${a}: ${e.message}`:`Failed to upload document batch starting at index ${a}: ${JSON.stringify(e)}`,n.errors.push(t),logger.error(t)}}return this.reportProgress(`documents`,e.length,e.length,`Uploaded ${n.restored} documents`),n}async uploadAttachments(e,t,n){let r={restored:0,errors:[],warnings:[]},a=0;for(let o of t)if(this.reportProgress(`attachments`,a,t.length,`Processing attachments for ${o._id}...`),a++,o._attachments)for(let[t,a]of Object.entries(o._attachments))try{let s=await this.uploadSingleAttachment(e,o._id,t,a,n);s.success?r.restored++:r.errors.push(s.error||`Unknown attachment upload error`)}catch(e){let n=`Failed to upload attachment ${o._id}/${t}: ${e instanceof Error?e.message:String(e)}`;r.errors.push(n),logger.error(n)}return this.reportProgress(`attachments`,t.length,t.length,`Uploaded ${r.restored} attachments`),r}async uploadSingleAttachment(e,t,n,r,a){let o={success:!1,attachmentName:n,docId:t};try{if(!r.path)return o.error=`Attachment metadata missing file path`,o;let s,c;if(this.fs)c=this.fs.joinPath(e,r.path),s=await this.fs.readBinary(c);else if(c=nodeFS3&&nodePath?nodePath.join(e,r.path):`${e}/${r.path}`,nodeFS3&&this.isLocalPath(e))s=await nodeFS3.promises.readFile(c);else{let e=await fetch(c);if(!e.ok)return o.error=`Failed to fetch attachment: ${e.status} ${e.statusText}`,o;s=await e.arrayBuffer()}let l=await a.get(t);await a.putAttachment(t,n,l._rev,s,r.content_type),o.success=!0}catch(e){o.error=e instanceof Error?e.message:String(e)}return o}async restoreCourseConfig(e,t){let n={restored:0,errors:[],warnings:[]};try{if(!e.courseConfig)return n.warnings.push(`No courseConfig found in manifest, skipping CourseConfig document creation`),n;let r={_id:`CourseConfig`,...e.courseConfig,courseID:e.courseId};delete r._rev,await t.put(r),n.restored=1,logger.info(`CourseConfig document created for course: ${e.courseId}`)}catch(e){let t=e instanceof Error?e.message:JSON.stringify(e);n.errors.push(`Failed to restore CourseConfig: ${t}`),logger.error(`CourseConfig restoration failed:`,e)}return n}calculateExpectedCounts(e){let t={};for(let n of e.chunks)t[n.docType]=(t[n.docType]||0)+n.documentCount;return e.designDocs.length>0&&(t._design=e.designDocs.length),t}async cleanupFailedMigration(e){logger.info(`Cleaning up failed migration...`);try{let t=(await e.allDocs()).rows.map(e=>({_id:e.id,_rev:e.value.rev,_deleted:!0}));t.length>0&&(await e.bulkDocs(t),logger.info(`Cleaned up ${t.length} documents from failed migration`))}catch(e){throw logger.error(`Failed to cleanup documents:`,e),e}}reportProgress(e,t,n,r){this.progressCallback&&this.progressCallback({phase:e,current:t,total:n,message:r})}isLocalPath(e){return!e.startsWith(`http://`)&&!e.startsWith(`https://`)}};init_dataDirectory(),init_navigators();var QuotaRoundRobinMixer=class{mix(e,t){if(e.length===0)return[];let n=Math.ceil(t/e.length),r=e.map(e=>[...e.weighted].sort((e,t)=>t.score-e.score).slice(0,n));for(let e=r.length-1;e>0;e--){let t=Math.floor(Math.random()*(e+1));[r[e],r[t]]=[r[t],r[e]]}let a=[],o=0,s=Array(r.length).fill(0);for(;a.length<t&&o<r.length;){o=0;for(let e=0;e<r.length&&!(a.length>=t);e++)s[e]<r[e].length?(a.push(r[e][s[e]]),s[e]++):o++}return a}};init_logger(),init_navigators();var MAX_RUNS2=10,runHistory2=[];function buildSourceSummary(e,t,n){let r=e.weighted.map(e=>e.score),a=e.weighted.filter(e=>getCardOrigin(e)===`review`).length,o=e.weighted.filter(e=>getCardOrigin(e)===`new`).length;return{sourceIndex:e.sourceIndex,sourceId:t,sourceName:n,totalCards:e.weighted.length,reviewCount:a,newCount:o,topScore:r.length>0?Math.max(...r):0,bottomScore:r.length>0?Math.min(...r):0,scoreRange:r.length>0?[Math.min(...r),Math.max(...r)]:[0,0],avgScore:r.length>0?r.reduce((e,t)=>e+t,0)/r.length:0}}function buildSourceBreakdown(e,t,n){let r=n.filter(t=>t.courseId===e),a=r.filter(e=>e.selected);return{sourceId:e,sourceName:t,reviewsProvided:r.filter(e=>e.origin===`review`).length,newProvided:r.filter(e=>e.origin===`new`).length,reviewsSelected:a.filter(e=>e.origin===`review`).length,newSelected:a.filter(e=>e.origin===`new`).length,totalSelected:a.length,selectionRate:r.length>0?a.length/r.length*100:0}}function captureMixerRun(e,t,n,r,a,o,s){let c=t.map((e,t)=>buildSourceSummary(e,n[t]||`source-${t}`,r[t])),l=new Set(s.map(e=>e.cardId)),u=new Map;t.forEach(e=>{let t=[...e.weighted].sort((e,t)=>t.score-e.score),r=new Map;t.forEach((e,t)=>{r.set(e.cardId,t+1)}),u.set(n[e.sourceIndex]||`source-${e.sourceIndex}`,r)});let d=new Map;s.forEach((e,t)=>{d.set(e.cardId,t+1)});let p=new Map;t.forEach(e=>{e.weighted.forEach(e=>{p.set(e.cardId,e)})});let m=Array.from(p.values()).map(e=>({cardId:e.cardId,courseId:e.courseId,origin:getCardOrigin(e),score:e.score,sourceIndex:t.findIndex(t=>t.weighted.some(t=>t.cardId===e.cardId)),selected:l.has(e.cardId),rankInSource:u.get(e.courseId)?.get(e.cardId),rankInMix:d.get(e.cardId)})),g=Array.from(new Set(n.filter(e=>e))).map((e,t)=>buildSourceBreakdown(e,r[t],m)),_={runId:`mix-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,timestamp:new Date,mixerType:e,requestedLimit:a,quotaPerSource:o,sourceSummaries:c,cards:m,finalCount:s.length,reviewsSelected:s.filter(e=>getCardOrigin(e)===`review`).length,newSelected:s.filter(e=>getCardOrigin(e)===`new`).length,sourceBreakdowns:g};runHistory2.unshift(_),runHistory2.length>MAX_RUNS2&&runHistory2.pop()}function printMixerSummary(e){console.group(`\u{1F3A8} Mixer Run: ${e.mixerType}`),logger.info(`Run ID: ${e.runId}`),logger.info(`Time: ${e.timestamp.toISOString()}`),logger.info(`Config: limit=${e.requestedLimit}${e.quotaPerSource?`, quota/source=${e.quotaPerSource}`:``}`),console.group(`\u{1F4E5} Input: ${e.sourceSummaries.length} sources`);for(let t of e.sourceSummaries)logger.info(` ${t.sourceName||t.sourceId}: ${t.totalCards} cards (${t.reviewCount} reviews, ${t.newCount} new)`),logger.info(` Score range: [${t.scoreRange[0].toFixed(2)}, ${t.scoreRange[1].toFixed(2)}], avg: ${t.avgScore.toFixed(2)}`);console.groupEnd(),console.group(`\u{1F4E4} Output: ${e.finalCount} cards selected (${e.reviewsSelected} reviews, ${e.newSelected} new)`);for(let t of e.sourceBreakdowns){let e=t.sourceName||t.sourceId;logger.info(` ${e}: ${t.totalSelected} selected (${t.reviewsSelected} reviews, ${t.newSelected} new) - ${t.selectionRate.toFixed(1)}% selection rate`)}console.groupEnd(),console.groupEnd()}var mixerDebugAPI={get runs(){return[...runHistory2]},showRun(e=0){if(runHistory2.length===0){logger.info(`[Mixer Debug] No runs captured yet.`);return}let t;if(typeof e==`number`){if(t=runHistory2[e],!t){logger.info(`[Mixer Debug] No run found at index ${e}. History length: ${runHistory2.length}`);return}}else if(t=runHistory2.find(t=>t.runId.endsWith(e)),!t){logger.info(`[Mixer Debug] No run found matching ID '${e}'.`);return}printMixerSummary(t)},showLastMix(){this.showRun(0)},explainSourceBalance(){if(runHistory2.length===0){logger.info(`[Mixer Debug] No runs captured yet.`);return}let e=runHistory2[0];console.group(`⚖️ Source Balance Analysis`),logger.info(`Mixer: ${e.mixerType}`),logger.info(`Requested limit: ${e.requestedLimit}`),e.quotaPerSource&&logger.info(`Quota per source: ${e.quotaPerSource}`),console.group(`Input Distribution:`);for(let t of e.sourceSummaries){let e=t.sourceName||t.sourceId;logger.info(`${e}:`),logger.info(` Provided: ${t.totalCards} cards (${t.reviewCount} reviews, ${t.newCount} new)`),logger.info(` Score range: [${t.scoreRange[0].toFixed(2)}, ${t.scoreRange[1].toFixed(2)}]`)}console.groupEnd(),console.group(`Selection Results:`);for(let t of e.sourceBreakdowns){let e=t.sourceName||t.sourceId;logger.info(`${e}:`),logger.info(` Selected: ${t.totalSelected}/${t.reviewsProvided+t.newProvided} (${t.selectionRate.toFixed(1)}%)`),logger.info(` Reviews: ${t.reviewsSelected}/${t.reviewsProvided}`),logger.info(` New: ${t.newSelected}/${t.newProvided}`),t.reviewsProvided>0&&t.reviewsSelected===0&&logger.info(` ⚠️ Had reviews but none selected!`),t.totalSelected===0&&t.reviewsProvided+t.newProvided>0&&logger.info(` ⚠️ Had cards but none selected!`)}console.groupEnd();let t=e.sourceBreakdowns.map(e=>e.selectionRate),n=t.reduce((e,t)=>e+t,0)/t.length,r=Math.max(...t.map(e=>Math.abs(e-n)));r>20&&(logger.info(`
300
300
  \u26A0\uFE0F Significant imbalance detected (max deviation: ${r.toFixed(1)}%)`),logger.info(`Possible causes:`),logger.info(` - Score range differences between sources`),logger.info(` - One source has much better quality cards`),logger.info(` - Different card availability (reviews vs new)`)),console.groupEnd()},compareScores(){if(runHistory2.length===0){logger.info(`[Mixer Debug] No runs captured yet.`);return}let e=runHistory2[0];console.group(`📊 Score Distribution Comparison`),console.table(e.sourceSummaries.map(e=>({source:e.sourceName||e.sourceId,cards:e.totalCards,min:e.bottomScore.toFixed(3),max:e.topScore.toFixed(3),avg:e.avgScore.toFixed(3),range:(e.topScore-e.bottomScore).toFixed(3)})));let t=e.sourceSummaries.map(e=>e.topScore-e.bottomScore),n=e.sourceSummaries.map(e=>e.avgScore),r=Math.max(...t)-Math.min(...t),a=Math.max(...n)-Math.min(...n);(r>.3||a>.2)&&(logger.info(`
@@ -341,13 +341,14 @@ Example:
341
341
  window.skuilder.session.showQueue()
342
342
  `)}};function mountSessionDebugger(){if(typeof window>`u`)return;let e=window;e.skuilder=e.skuilder||{},e.skuilder.session=sessionDebugAPI}mountSessionDebugger(),init_logger();var SessionController=(_SessionController2=class _SessionController extends Loggable{set sessionRecord(e){this._sessionRecord=e}get secondsRemaining(){return this._secondsRemaining}get hasCardGuarantee(){return this._minCardsGuarantee>0}get report(){let e=this.reviewQ.dequeueCount,t=this.newQ.dequeueCount;return`${e} ${e===1?`review`:`reviews`}, ${t} ${t===1?`new card`:`new cards`}`}get detailedReport(){return this.newQ.toString+`
343
343
  `+this.reviewQ.toString+`
344
- `+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,`reviewQ`,new ItemQueue),_defineProperty(this,`newQ`,new ItemQueue),_defineProperty(this,`failedQ`,new ItemQueue),_defineProperty(this,`_replanPromise`,null),_defineProperty(this,`_wellIndicatedRemaining`,0),_defineProperty(this,`_suppressQualityReplan`,!1),_defineProperty(this,`_depletionReplanAttempted`,!1),_defineProperty(this,`_minCardsGuarantee`,0),_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),this.log(`Session constructed:
344
+ `+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,`_initialReviewCap`,200),_defineProperty(this,`sources`,void 0),_defineProperty(this,`_sessionRecord`,[]),_defineProperty(this,`_currentCard`,null),_defineProperty(this,`reviewQ`,new ItemQueue),_defineProperty(this,`newQ`,new ItemQueue),_defineProperty(this,`failedQ`,new ItemQueue),_defineProperty(this,`_replanPromise`,null),_defineProperty(this,`_wellIndicatedRemaining`,0),_defineProperty(this,`_suppressQualityReplan`,!1),_defineProperty(this,`_depletionReplanAttempted`,!1),_defineProperty(this,`_minCardsGuarantee`,0),_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?.initialReviewCap!==void 0&&(this._initialReviewCap=o.initialReviewCap),this.log(`Session constructed:
345
345
  startTime: ${this.startTime}
346
346
  endTime: ${this.endTime}
347
- defaultBatchLimit: ${this._defaultBatchLimit}`)}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}estimateReviewTime(){let e=5*this.reviewQ.length;return this.log(`Review card time estimate: ${e}`),e}async prepareSession(){if(this.sources.some(e=>typeof e.getWeightedCards!=`function`))throw Error(`[SessionController] All content sources must implement getWeightedCards().`);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.reviewQ.length,this.newQ.length,this.failedQ.length),this._intervalHandle=setInterval(()=>{this.tick()},1e3)}async requestReplan(e){let t=this.normalizeReplanOptions(e);if((t.hints||t.label||t.limit)&&(this._depletionReplanAttempted=!1),this._replanPromise)return this.log(`Replan already in progress, awaiting existing replan`),this._replanPromise;if(this._currentCard?.item.cardID){let e=this._currentCard.item.cardID;t.hints||(t.hints={});let n=t.hints,r=n.excludeCards??[];r.includes(e)||r.push(e),n.excludeCards=r}if(t.hints){let e=t.label?{...t.hints,_label:t.label}:t.hints;for(let t of this.sources)t.setEphemeralHints?.(e)}let n=t.label?` [${t.label}]`:``;this.log(`Mid-session replan requested${n} (limit: ${t.limit??`default`}, mode: ${t.mode??`replace`}${t.hints?`, with hints`:``})`),t.minFollowUpCards!==void 0&&t.minFollowUpCards>0&&(this._minCardsGuarantee=Math.max(this._minCardsGuarantee,t.minFollowUpCards),this.log(`[Replan] Card guarantee set to ${this._minCardsGuarantee}`)),this._replanPromise=this._executeReplan(t);try{await this._replanPromise}finally{this._replanPromise=null}}normalizeReplanOptions(e){if(!e)return{};let t=[`hints`,`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`),this.newQ.length>0&&(this._depletionReplanAttempted=!1),await this.hydrationService.ensureHydratedCards();let a=e.label?` [${e.label}]`:``;this.log(`Replan complete${a}: newQ now has ${this.newQ.length} cards (mode=${n})`),snapshotQueues(this.reviewQ.length,this.newQ.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.reviewQ.length} Reviews, ${this.newQ.length} New, ${this.failedQ.length} failed`}reportString(){return`${this.reviewQ.dequeueCount} Reviews, ${this.newQ.dequeueCount} New, ${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`})}return n};return{api:{mode:e?`weighted`:`legacy`,description:e?`Using getWeightedCards() API with scored candidates`:`ERROR: getWeightedCards() not a function.`},reviewQueue:{length:this.reviewQ.length,dequeueCount:this.reviewQ.dequeueCount,items:extractQueueItems(this.reviewQ)},newQueue:{length:this.newQ.length,dequeueCount:this.newQ.dequeueCount,items:extractQueueItems(this.newQ)},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=[];for(let e=0;e<this.sources.length;e++){let t=this.sources[e];try{let n=(await t.getWeightedCards(r)).cards;a.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(a.length===0){if(t)return this.log(`Replan: no content from any source, keeping existing newQ`),-1;throw Error(`Cannot start session: failed to load content from all ${this.sources.length} source(s). Check logs for details.`)}let o=this.mixer.mix(a,r*this.sources.length),s=a.map(e=>e.weighted[0]?.courseId||`source-${e.sourceIndex}`);await Promise.all(s.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 c=s.map(e=>this.courseNameCache.get(e)),l=this.mixer instanceof QuotaRoundRobinMixer?Math.ceil(r*this.sources.length/a.length):void 0;captureMixerRun(this.mixer.constructor.name,a,s,c,r*this.sources.length,l,o);let u=o.filter(e=>getCardOrigin(e)===`review`),d=o.filter(e=>getCardOrigin(e)===`new`);logger.debug(`[reviews] got ${u.length} reviews from mixer`);let p=t?`Replan content:
347
+ defaultBatchLimit: ${this._defaultBatchLimit}
348
+ initialReviewCap: ${this._initialReviewCap}`)}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}estimateReviewTime(){let e=5*this.reviewQ.length;return this.log(`Review card time estimate: ${e}`),e}async prepareSession(){if(this.sources.some(e=>typeof e.getWeightedCards!=`function`))throw Error(`[SessionController] All content sources must implement getWeightedCards().`);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.reviewQ.length,this.newQ.length,this.failedQ.length),this._intervalHandle=setInterval(()=>{this.tick()},1e3)}async requestReplan(e){let t=this.normalizeReplanOptions(e);if((t.hints||t.label||t.limit)&&(this._depletionReplanAttempted=!1),this._replanPromise)return this.log(`Replan already in progress, awaiting existing replan`),this._replanPromise;t.hints||(t.hints={});let n=t.hints,r=new Set(n.excludeCards??[]);this._currentCard?.item.cardID&&r.add(this._currentCard.item.cardID);for(let e of this._sessionRecord)r.add(e.card.card_id);if(n.excludeCards=[...r],t.hints){let e=t.label?{...t.hints,_label:t.label}:t.hints;for(let t of this.sources)t.setEphemeralHints?.(e)}let a=t.label?` [${t.label}]`:``;this.log(`Mid-session replan requested${a} (limit: ${t.limit??`default`}, mode: ${t.mode??`replace`}${t.hints?`, with hints`:``})`),t.minFollowUpCards!==void 0&&t.minFollowUpCards>0&&(this._minCardsGuarantee=Math.max(this._minCardsGuarantee,t.minFollowUpCards),this.log(`[Replan] Card guarantee set to ${this._minCardsGuarantee}`)),this._replanPromise=this._executeReplan(t);try{await this._replanPromise}finally{this._replanPromise=null}}normalizeReplanOptions(e){if(!e)return{};let t=[`hints`,`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`),this.newQ.length>0&&(this._depletionReplanAttempted=!1),await this.hydrationService.ensureHydratedCards();let a=e.label?` [${e.label}]`:``;this.log(`Replan complete${a}: newQ now has ${this.newQ.length} cards (mode=${n})`),snapshotQueues(this.reviewQ.length,this.newQ.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.reviewQ.length} Reviews, ${this.newQ.length} New, ${this.failedQ.length} failed`}reportString(){return`${this.reviewQ.dequeueCount} Reviews, ${this.newQ.dequeueCount} New, ${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`})}return n};return{api:{mode:e?`weighted`:`legacy`,description:e?`Using getWeightedCards() API with scored candidates`:`ERROR: getWeightedCards() not a function.`},reviewQueue:{length:this.reviewQ.length,dequeueCount:this.reviewQ.dequeueCount,items:extractQueueItems(this.reviewQ)},newQueue:{length:this.newQ.length,dequeueCount:this.newQ.dequeueCount,items:extractQueueItems(this.newQ)},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=t?r:r+this._initialReviewCap,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 newQ`),-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=>getCardOrigin(e)===`review`).slice(0,this._initialReviewCap),p=s.filter(e=>getCardOrigin(e)===`new`).slice(0,r);logger.debug(`[reviews] got ${d.length} reviews from mixer`);let m=t?`Replan content:
348
349
  `:`Mixed content session created with:
349
- `;if(!t)for(let e of u){let t={cardID:e.cardId,courseID:e.courseId,contentSourceType:`course`,contentSourceID:e.courseId,reviewID:e.reviewID,status:`review`};this.reviewQ.add(t,t.cardID),p+=`Review: ${e.courseId}::${e.cardId} (score: ${e.score.toFixed(2)})
350
- `}let m=d.filter(e=>e.score>=_SessionController.WELL_INDICATED_SCORE).length,g=[];for(let e of d){let t={cardID:e.cardId,courseID:e.courseId,contentSourceType:`course`,contentSourceID:e.courseId,status:`new`};g.push(t),p+=`New: ${e.courseId}::${e.cardId} (score: ${e.score.toFixed(2)})
351
- `}if(n){let e=this.newQ.mergeToFront(g,e=>e.cardID);p+=`Additive merge: ${e} new cards added to front of newQ
352
- `}else if(t)this.newQ.replaceAll(g,e=>e.cardID);else for(let e of g)this.newQ.add(e,e.cardID);return this.log(p),m}_getItemsToHydrate(){let e=[],t=2;for(let t=0;t<Math.min(2,this.reviewQ.length);t++)e.push(this.reviewQ.peek(t));for(let t=0;t<Math.min(2,this.newQ.length);t++)e.push(this.newQ.peek(t));for(let t=0;t<Math.min(2,this.failedQ.length);t++)e.push(this.failedQ.peek(t));return e}_selectNextItemToHydrate(){let e=Math.random(),t=.1,n=.75;if(this.reviewQ.length===0&&this.failedQ.length===0&&this.newQ.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;if(this.newQ.dequeueCount<this.sources.length&&this.newQ.length)return this.newQ.peek(0);let r=this.estimateCleanupTime(),a=this.estimateReviewTime();return this._secondsRemaining-(r+a)>20?(t=.5,n=.9):this._secondsRemaining-r>20?(t=.05,n=.9):(t=.01,n=.1),this.failedQ.length===0&&(n=1),this.reviewQ.length===0&&(t=n),e<t&&this.newQ.length?this.newQ.peek(0):e<n&&this.reviewQ.length?this.reviewQ.peek(0):this.failedQ.length?this.failedQ.peek(0):(this.log(`No more cards available for the session!`),null)}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.log(`nextCard: awaiting in-flight replan before drawing`),await this._replanPromise),this.newQ.length<=1&&this._secondsRemaining>0&&!this._replanPromise&&!this._depletionReplanAttempted){this._suppressQualityReplan=!1,this._depletionReplanAttempted=!0;let e=this.reviewQ.length+this.failedQ.length;this.newQ.length===0&&e===0?(this.log(`[AutoReplan:depletion] All queues empty with ${this._secondsRemaining}s remaining. Awaiting replan.`),await this.requestReplan()):(this.log(`[AutoReplan:depletion] newQ has ${this.newQ.length} card(s) (${e} in other queues) with ${this._secondsRemaining}s remaining. Triggering background replan.`),this.requestReplan())}if(!this._suppressQualityReplan&&this._wellIndicatedRemaining<=3&&this.newQ.length>0&&!this._replanPromise&&(this.log(`[AutoReplan:quality] ${this._wellIndicatedRemaining} well-indicated cards remaining (newQ: ${this.newQ.length}). Triggering background replan.`),this.requestReplan()),this._secondsRemaining<=0&&this.failedQ.length===0&&this._minCardsGuarantee<=0)return this._currentCard=null,endSessionTracking(),null;let t=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`:e.status===`review`?`reviewQ`:`newQ`;return recordCardPresentation(e.cardID,e.courseID,this.courseNameCache.get(e.courseID),n,r),snapshotQueues(this.reviewQ.length,this.newQ.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};return await this.services.response.processResponse(e,t,u,n,r,a,o,s,c,l)}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)}else (e===`dismiss-error`||e===`dismiss-failed`)&&this.hydrationService.removeCard(this._currentCard.item.cardID)}removeItemFromQueue(e){this.reviewQ.peek(0)?.cardID===e.cardID?this.reviewQ.dequeue(e=>e.cardID):this.newQ.peek(0)?.cardID===e.cardID?(this.newQ.dequeue(e=>e.cardID),this._wellIndicatedRemaining>0&&this._wellIndicatedRemaining--):this.failedQ.peek(0)?.cardID===e.cardID&&this.failedQ.dequeue(e=>e.cardID)}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),_SessionController2);init_TagFilteredContentSource(),init_factory();export{processCustomQuestionsData as $,getDbPath as A,isFilter as B,createOrchestrationContext as C,getCardHistoryID as D,getAppDataDirectory as E,hasRegisteredNavigator as F,log as G,isQuestionRecord as H,importParsedCards as I,mountPipelineDebugger as J,mixerDebugAPI as K,initializeDataDirectory as L,getRegisteredNavigatorNames as M,getRegisteredNavigatorRole as N,getCardOrigin as O,getStudySource as P,pipelineDebugAPI as Q,initializeDataLayer as R,computeSpread as S,ensureAppDataDirectory as T,isQuestionTypeRegistered as U,isGenerator as V,isReview as W,mountUserDBDebugger as X,mountSessionDebugger as Y,newInterval as Z,buildStrategyStateId as _,validateStaticCourse as _t,ENV as a,registerSeedData as at,computeEffectiveWeight as b,Loggable as c,scoreAccuracyInZone as ct,NavigatorRoles as d,startSessionTracking as dt,recordCardPresentation as et,Navigators as f,updateLearningState as ft,areQuestionRecords as g,validateProcessorConfig as gt,TagFilteredContentSource as h,validateMigration as ht,DocTypePrefixes as i,registerQuestionType as it,getRegisteredNavigator as j,getDataLayer as k,NOT_SET as l,sessionDebugAPI as lt,SessionController as m,userDBDebugAPI as mt,CourseLookup as n,registerDataShape as nt,FileSystemError as o,removeDataShape as ot,QuotaRoundRobinMixer as p,updateStrategyWeight as pt,mountMixerDebugger as q,DocType as r,registerNavigator as rt,GuestUsername as s,removeQuestionType as st,ContentNavigator as t,recordUserOutcome as tt,NavigatorRole as u,snapshotQueues as ut,captureMixerRun as v,endSessionTracking as w,computeOutcomeSignal as x,computeDeviation as y,initializeNavigatorRegistry as z};
353
- //# sourceMappingURL=dist-DkeESCP4.js.map
350
+ `;if(!t)for(let e of d){let t={cardID:e.cardId,courseID:e.courseId,contentSourceType:`course`,contentSourceID:e.courseId,reviewID:e.reviewID,status:`review`};this.reviewQ.add(t,t.cardID),m+=`Review: ${e.courseId}::${e.cardId} (score: ${e.score.toFixed(2)})
351
+ `}let g=p.filter(e=>e.score>=_SessionController.WELL_INDICATED_SCORE).length,_=[];for(let e of p){let t={cardID:e.cardId,courseID:e.courseId,contentSourceType:`course`,contentSourceID:e.courseId,status:`new`};_.push(t),m+=`New: ${e.courseId}::${e.cardId} (score: ${e.score.toFixed(2)})
352
+ `}if(n){let e=this.newQ.mergeToFront(_,e=>e.cardID);m+=`Additive merge: ${e} new cards added to front of newQ
353
+ `}else if(t)this.newQ.replaceAll(_,e=>e.cardID);else for(let e of _)this.newQ.add(e,e.cardID);return this.log(m),g}_getItemsToHydrate(){let e=[],t=2;for(let t=0;t<Math.min(2,this.reviewQ.length);t++)e.push(this.reviewQ.peek(t));for(let t=0;t<Math.min(2,this.newQ.length);t++)e.push(this.newQ.peek(t));for(let t=0;t<Math.min(2,this.failedQ.length);t++)e.push(this.failedQ.peek(t));return e}_selectNextItemToHydrate(){let e=Math.random(),t=.1,n=.75;if(this.reviewQ.length===0&&this.failedQ.length===0&&this.newQ.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;if(this.newQ.dequeueCount<this.sources.length&&this.newQ.length)return this.newQ.peek(0);let r=this.estimateCleanupTime(),a=this.estimateReviewTime();return this._secondsRemaining-(r+a)>20?(t=.5,n=.9):this._secondsRemaining-r>20?(t=.05,n=.9):(t=.01,n=.1),this.failedQ.length===0&&(n=1),this.reviewQ.length===0&&(t=n),e<t&&this.newQ.length?this.newQ.peek(0):e<n&&this.reviewQ.length?this.reviewQ.peek(0):this.failedQ.length?this.failedQ.peek(0):(this.log(`No more cards available for the session!`),null)}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.log(`nextCard: awaiting in-flight replan before drawing`),await this._replanPromise),this.newQ.length<=1&&this._secondsRemaining>0&&!this._replanPromise&&!this._depletionReplanAttempted){this._suppressQualityReplan=!1,this._depletionReplanAttempted=!0;let e=this.reviewQ.length+this.failedQ.length;this.newQ.length===0&&e===0?(this.log(`[AutoReplan:depletion] All queues empty with ${this._secondsRemaining}s remaining. Awaiting replan.`),await this.requestReplan()):(this.log(`[AutoReplan:depletion] newQ has ${this.newQ.length} card(s) (${e} in other queues) with ${this._secondsRemaining}s remaining. Triggering background replan.`),this.requestReplan())}if(!this._suppressQualityReplan&&this._wellIndicatedRemaining<=3&&this.newQ.length>0&&!this._replanPromise&&(this.log(`[AutoReplan:quality] ${this._wellIndicatedRemaining} well-indicated cards remaining (newQ: ${this.newQ.length}). Triggering background replan.`),this.requestReplan()),this._secondsRemaining<=0&&this.failedQ.length===0&&this._minCardsGuarantee<=0)return this._currentCard=null,endSessionTracking(),null;let t=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`:e.status===`review`?`reviewQ`:`newQ`;return recordCardPresentation(e.cardID,e.courseID,this.courseNameCache.get(e.courseID),n,r),snapshotQueues(this.reviewQ.length,this.newQ.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};return await this.services.response.processResponse(e,t,u,n,r,a,o,s,c,l)}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)}else (e===`dismiss-error`||e===`dismiss-failed`)&&this.hydrationService.removeCard(this._currentCard.item.cardID)}removeItemFromQueue(e){this.reviewQ.peek(0)?.cardID===e.cardID?this.reviewQ.dequeue(e=>e.cardID):this.newQ.peek(0)?.cardID===e.cardID?(this.newQ.dequeue(e=>e.cardID),this._wellIndicatedRemaining>0&&this._wellIndicatedRemaining--):this.failedQ.peek(0)?.cardID===e.cardID&&this.failedQ.dequeue(e=>e.cardID)}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),_SessionController2);init_TagFilteredContentSource(),init_factory();export{processCustomQuestionsData as $,getDbPath as A,isFilter as B,createOrchestrationContext as C,getCardHistoryID as D,getAppDataDirectory as E,hasRegisteredNavigator as F,log as G,isQuestionRecord as H,importParsedCards as I,mountPipelineDebugger as J,mixerDebugAPI as K,initializeDataDirectory as L,getRegisteredNavigatorNames as M,getRegisteredNavigatorRole as N,getCardOrigin as O,getStudySource as P,pipelineDebugAPI as Q,initializeDataLayer as R,computeSpread as S,ensureAppDataDirectory as T,isQuestionTypeRegistered as U,isGenerator as V,isReview as W,mountUserDBDebugger as X,mountSessionDebugger as Y,newInterval as Z,buildStrategyStateId as _,validateStaticCourse as _t,ENV as a,registerSeedData as at,computeEffectiveWeight as b,Loggable as c,scoreAccuracyInZone as ct,NavigatorRoles as d,startSessionTracking as dt,recordCardPresentation as et,Navigators as f,updateLearningState as ft,areQuestionRecords as g,validateProcessorConfig as gt,TagFilteredContentSource as h,validateMigration as ht,DocTypePrefixes as i,registerQuestionType as it,getRegisteredNavigator as j,getDataLayer as k,NOT_SET as l,sessionDebugAPI as lt,SessionController as m,userDBDebugAPI as mt,CourseLookup as n,registerDataShape as nt,FileSystemError as o,removeDataShape as ot,QuotaRoundRobinMixer as p,updateStrategyWeight as pt,mountMixerDebugger as q,DocType as r,registerNavigator as rt,GuestUsername as s,removeQuestionType as st,ContentNavigator as t,recordUserOutcome as tt,NavigatorRole as u,snapshotQueues as ut,captureMixerRun as v,endSessionTracking as w,computeOutcomeSignal as x,computeDeviation as y,initializeNavigatorRegistry as z};
354
+ //# sourceMappingURL=dist-CzLl7g6t.js.map