@vue-skuilder/standalone-ui 0.2.1 → 0.2.3

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.
@@ -346,9 +346,9 @@ Example:
346
346
  window.skuilder.pipeline.showLastRun()
347
347
  window.skuilder.pipeline.showRun(1)
348
348
  await window.skuilder.pipeline.diagnoseCardSpace()
349
- `)}},mountPipelineDebugger()}}),CompositeGenerator_exports={},__export(CompositeGenerator_exports,{AggregationMode:()=>AggregationMode,default:()=>CompositeGenerator}),init_CompositeGenerator=__esm({"src/core/navigators/generators/CompositeGenerator.ts"(){"use strict";init_navigators(),init_logger(),AggregationMode=(t=>(t.MAX=`max`,t.AVERAGE=`average`,t.FREQUENCY_BOOST=`frequencyBoost`,t))(AggregationMode||{}),DEFAULT_AGGREGATION_MODE=`frequencyBoost`,FREQUENCY_BOOST_FACTOR=.1,CompositeGenerator=class _CompositeGenerator extends ContentNavigator{constructor(t,c=DEFAULT_AGGREGATION_MODE){if(super(),_defineProperty$2(this,`name`,`Composite Generator`),_defineProperty$2(this,`generators`,void 0),_defineProperty$2(this,`aggregationMode`,void 0),this.generators=t,this.aggregationMode=c,t.length===0)throw Error(`CompositeGenerator requires at least one generator`);logger.debug(`[CompositeGenerator] Created with ${t.length} generators, mode: ${c}`)}static async fromStrategies(t,c,u,d=DEFAULT_AGGREGATION_MODE){return new _CompositeGenerator(await Promise.all(u.map(u=>ContentNavigator.create(t,c,u))),d)}async getWeightedCards(t,c){if(!c)throw Error(`CompositeGenerator.getWeightedCards requires a GeneratorContext. It should be called via Pipeline, not directly.`);let u=await Promise.all(this.generators.map(u=>u.getWeightedCards(t,c))),d=[];u.forEach((t,c)=>{let u=t.cards,m=this.generators[c].name||`Generator ${c}`,g=u.filter(t=>t.provenance[0]?.reason?.includes(`new card`)),b=u.filter(t=>t.provenance[0]?.reason?.includes(`review`));if(u.length>0){let t=Math.max(...u.map(t=>t.score)).toFixed(2),c=[];g.length>0&&c.push(`${g.length} new`),b.length>0&&c.push(`${b.length} reviews`);let S=c.length>0?c.join(`, `):`${u.length} cards`;d.push(`${m}: ${S} (top: ${t})`)}else d.push(`${m}: 0 cards`)}),logger.info(`[Composite] Generator breakdown: ${d.join(` | `)}`);let m=new Map;u.forEach((t,u)=>{let d=t.cards,g=this.generators[u],b=g.learnable?.weight??1,S;if(g.learnable&&!g.staticWeight&&c.orchestration){let t=g.strategyId;t&&(b=c.orchestration.getEffectiveWeight(t,g.learnable),S=c.orchestration.getDeviation(t))}for(let t of d){t.provenance.length>0&&(t.provenance[0].effectiveWeight=b,t.provenance[0].deviation=S);let c=m.get(t.cardId)||[];c.push({card:t,weight:b}),m.set(t.cardId,c)}});let g=[];for(let[,t]of m){let c=t.map(t=>t.card),u=this.aggregateScores(t),d=Math.max(0,u),m=c.flatMap(t=>t.provenance),b=c[0].score,S=d>b?`boosted`:d<b?`penalized`:`passed`,C=this.buildAggregationReason(t,d);g.push({...c[0],score:d,provenance:[...m,{strategy:`composite`,strategyName:`Composite Generator`,strategyId:`COMPOSITE_GENERATOR`,action:S,score:d,reason:C}]})}return{cards:g.sort((t,c)=>c.score-t.score).slice(0,t),hints:mergeHints(u.map(t=>t.hints))}}buildAggregationReason(t,c){let u=t.map(t=>t.card),d=u.length,m=u.map(t=>t.score.toFixed(2)).join(`, `);if(d===1){let u=Math.abs(t[0].weight-1)>.001?` (w=${t[0].weight.toFixed(2)})`:``;return`Single generator, score ${c.toFixed(2)}${u}`}let g=u.map(t=>t.provenance[0]?.strategy||`unknown`).join(`, `);switch(this.aggregationMode){case`max`:return`Max of ${d} generators (${g}): scores [${m}] \u2192 ${c.toFixed(2)}`;case`average`:return`Weighted Avg of ${d} generators (${g}): scores [${m}] \u2192 ${c.toFixed(2)}`;case`frequencyBoost`:{let u=t.reduce((t,c)=>t+c.weight,0),m=t.reduce((t,c)=>t+c.card.score*c.weight,0),b=u>0?m/u:0,S=1+FREQUENCY_BOOST_FACTOR*(d-1);return`Frequency boost from ${d} generators (${g}): w-avg ${b.toFixed(2)} \xD7 ${S.toFixed(2)} \u2192 ${c.toFixed(2)}`}default:return`Aggregated from ${d} generators: ${c.toFixed(2)}`}}aggregateScores(t){let c=t.map(t=>t.card.score);switch(this.aggregationMode){case`max`:return Math.max(...c);case`average`:{let c=t.reduce((t,c)=>t+c.weight,0);return c===0?0:t.reduce((t,c)=>t+c.card.score*c.weight,0)/c}case`frequencyBoost`:{let c=t.reduce((t,c)=>t+c.weight,0),u=t.reduce((t,c)=>t+c.card.score*c.weight,0);return(c>0?u/c:0)*(1+FREQUENCY_BOOST_FACTOR*(t.length-1))}default:return c[0]}}}}}),elo_exports={},__export(elo_exports,{default:()=>ELONavigator}),init_elo=__esm({"src/core/navigators/generators/elo.ts"(){"use strict";init_navigators(),init_logger(),ELONavigator=class extends ContentNavigator{constructor(t,c,u){super(t,c,u),_defineProperty$2(this,`name`,void 0),this.name=u?.name||`ELO`}async getWeightedCards(t,c){let u;u=c?.userElo===void 0?toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo).global.score:c.userElo;let d=await this.user.getActiveCards(),m=(await this.course.getCardsCenteredAtELO({limit:t,elo:`user`},t=>!d.some(c=>t.cardID===c.cardID))).map(t=>({...t,status:`new`})),g=m.map(t=>t.cardID),b=await this.course.getCardEloData(g),S=m.map((t,c)=>{let d=b[c]?.global?.score??1e3,m=Math.abs(d-u),g=Math.max(0,1-m/500),S=g>0?Math.random()**(1/g):0;return{cardId:t.cardID,courseId:t.courseID,score:S,provenance:[{strategy:`elo`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-ELO-default`,action:`generated`,score:S,reason:`ELO distance ${Math.round(m)} (card: ${Math.round(d)}, user: ${Math.round(u)}), raw ${g.toFixed(3)}, key ${S.toFixed(3)}`}]}});S.sort((t,c)=>c.score-t.score);let C=S.slice(0,t);if(C.length>0){let t=C.slice(0,3).map(t=>t.score.toFixed(2)).join(`, `);logger.info(`[ELO] Course ${this.course.getCourseID()}: ${C.length} new cards (top scores: ${t})`)}else logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);return{cards:C}}}}}),generators_exports={},init_generators=__esm({"src/core/navigators/generators/index.ts"(){"use strict";}}),prescribed_exports={},__export(prescribed_exports,{default:()=>PrescribedCardsGenerator}),init_prescribed=__esm({"src/core/navigators/generators/prescribed.ts"(){"use strict";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(t,c,u){super(t,c,u),_defineProperty$2(this,`name`,void 0),_defineProperty$2(this,`config`,void 0),this.name=u.name||`Prescribed Cards`,this.config=this.parseConfig(u.serializedData),logger.debug(`[Prescribed] Initialized with ${this.config.groups.length} groups and ${this.config.groups.reduce((t,c)=>t+c.targetCardIds.length,0)} targets`)}get strategyKey(){return`PrescribedProgress`}async getWeightedCards(t,c){if(this.config.groups.length===0||t<=0)return{cards:[]};let u=this.course.getCourseID(),d=await this.user.getActiveCards(),m=new Set(d.map(t=>t.cardID)),g=await this.user.getSeenCards(u).catch(()=>[]),b=new Set(g),S=await this.getStrategyState()??{updatedAt:isoNow(),groups:{}},C=await this.loadHierarchyConfigs(),w=await this.user.getCourseRegDoc(u).catch(()=>null),T=typeof w?.elo==`number`?w.elo:w?.elo?.global?.score??c?.userElo??1e3,E=typeof w?.elo==`number`?{}:w?.elo?.tags??{},D=dedupe(this.config.groups.flatMap(t=>t.targetCardIds)),O=dedupe(this.config.groups.flatMap(t=>t.supportCardIds??[])),Or=dedupe([...D,...O]),kr=Or.length>0?await this.course.getAppliedTagsBatch(Or):new Map,Ar=await this.course.getCourseTagStubs().catch(()=>({rows:[],offset:0,total_rows:0})),jr=new Map;for(let t of Ar.rows??[]){let c=t.doc;c?.name&&Array.isArray(c.taggedCards)&&jr.set(c.name,[...c.taggedCards])}let Mr={updatedAt:isoNow(),groups:{}},Nr=[],Pr=new Set,Fr=[];for(let t of this.config.groups){let c=this.buildGroupRuntimeState({group:t,priorState:S.groups[t.id],activeIds:m,seenIds:b,tagsByCard:kr,cardsByTag:jr,hierarchyConfigs:C,userTagElo:E,userGlobalElo:T});Fr.push(c),logger.info(`[Prescribed] Group '${t.id}': ${t.targetCardIds.length} targets total, ${c.encounteredTargets.size} encountered, ${c.pendingTargets.length} pending (${c.surfaceableTargets.length} surfaceable, ${c.blockedTargets.length} blocked), ${c.supportCandidates.length} authored support candidates, ${c.discoveredSupportCandidates.length} discovered support candidates, pressure=${c.pressureMultiplier.toFixed(2)}`),c.blockedTargets.length>0&&(logger.info(`[Prescribed] Group '${t.id}' blocked targets: ${c.blockedTargets.join(`, `)}`),logger.info(`[Prescribed] Group '${t.id}' support tags needed: ${c.supportTags.join(`, `)||`(none)`}`),logger.info(`[Prescribed] Group '${t.id}' escalation mode: `+(c.supportCandidates.length>0?`direct-support`:c.discoveredSupportCandidates.length>0?`inserted-support-candidates`:`boost-only`)),c.discoveredSupportCandidates.length>0&&logger.info(`[Prescribed] Group '${t.id}' discovered support candidates: ${c.discoveredSupportCandidates.join(`, `)}`)),Mr.groups[t.id]=this.buildNextGroupState(c,S.groups[t.id]);let d=this.buildDirectTargetCards(c,u,Pr),g=this.buildSupportCards(c,u,Pr),w=this.buildDiscoveredSupportCards(c,u,Pr);Nr.push(...d,...g,...w)}let Ir=this.buildSupportHintSummary(Fr),Lr=Object.keys(Ir.boostTags).length>0?{boostTags:Ir.boostTags,_label:`prescribed-support (${Ir.supportTags.length} tags; blocked=${Ir.blockedTargetIds.length}; testversion=${PRESCRIBED_DEBUG_VERSION})`}:void 0;if(Lr){let t=Object.entries(Lr.boostTags??{});logger.info(`[Prescribed] Emitting ${t.length} boost hint(s): `+t.map(([t,c])=>`${t}\xD7${c.toFixed(1)}`).join(`, `))}else logger.info(`[Prescribed] No hints to emit (no blocked targets or no support tags)`);if(Nr.length===0)return logger.info(`[Prescribed] 0 cards emitted (all targets blocked, authored/discovered support candidates exhausted)`+(Lr?` — boost hints emitted but may not survive filters`:``)),await this.putStrategyState(Mr).catch(t=>{logger.debug(`[Prescribed] Failed to persist empty-state update: ${t}`)}),Lr?{cards:[],hints:Lr}:{cards:[]};let Rr=pickTopByScore(Nr,t),zr=new Map;for(let t of Rr){let c=t.provenance[0],u=c?.reason.match(/group=([^;]+)/)?.[1],d=c?.reason.includes(`mode=support`)?`supportIds`:`targetIds`;u&&(zr.has(u)||zr.set(u,{targetIds:[],supportIds:[]}),zr.get(u)[d].push(t.cardId))}for(let t of this.config.groups){let c=Mr.groups[t.id],u=zr.get(t.id);u&&(u.targetIds.length>0||u.supportIds.length>0)&&(c.lastSurfacedAt=isoNow(),c.sessionsSinceSurfaced=0,u.supportIds.length>0&&(c.lastSupportAt=isoNow()))}return await this.putStrategyState(Mr).catch(t=>{logger.debug(`[Prescribed] Failed to persist prescribed progress: ${t}`)}),logger.info(`[Prescribed] Emitting ${Rr.length} cards (${Rr.filter(t=>t.provenance[0]?.reason.includes(`mode=target`)).length} target, ${Rr.filter(t=>t.provenance[0]?.reason.includes(`mode=support`)).length} support, ${Rr.filter(t=>t.provenance[0]?.reason.includes(`mode=discovered-support`)).length} discovered support)`),Lr?{cards:Rr,hints:Lr}:{cards:Rr}}buildSupportHintSummary(t){let c={},u=new Set,d=new Set;for(let m of t)if(!(m.blockedTargets.length===0||m.supportTags.length===0)){m.blockedTargets.forEach(t=>u.add(t));for(let t of m.supportTags)d.add(t),c[t]=(c[t]??1)*m.supportMultiplier}return{boostTags:c,blockedTargetIds:[...u].sort(),supportTags:[...d].sort()}}parseConfig(t){try{let c=JSON.parse(t);return{groups:(Array.isArray(c.groups)?c.groups:[]).map((t,c)=>({id:typeof t.id==`string`&&t.id.trim().length>0?t.id:`group-${c+1}`,targetCardIds:dedupe(Array.isArray(t.targetCardIds)?t.targetCardIds.filter(t=>typeof t==`string`):[]),supportCardIds:dedupe(Array.isArray(t.supportCardIds)?t.supportCardIds.filter(t=>typeof t==`string`):[]),supportTagPatterns:dedupe(Array.isArray(t.supportTagPatterns)?t.supportTagPatterns.filter(t=>typeof t==`string`):[]),freshnessWindowSessions:typeof t.freshnessWindowSessions==`number`?t.freshnessWindowSessions:DEFAULT_FRESHNESS_WINDOW,maxDirectTargetsPerRun:typeof t.maxDirectTargetsPerRun==`number`?t.maxDirectTargetsPerRun:DEFAULT_MAX_DIRECT_PER_RUN,maxSupportCardsPerRun:typeof t.maxSupportCardsPerRun==`number`?t.maxSupportCardsPerRun:DEFAULT_MAX_SUPPORT_PER_RUN,hierarchyWalk:{enabled:t.hierarchyWalk?.enabled!==!1,maxDepth:typeof t.hierarchyWalk?.maxDepth==`number`?t.hierarchyWalk.maxDepth:DEFAULT_HIERARCHY_DEPTH},retireOnEncounter:t.retireOnEncounter!==!1})).filter(t=>t.targetCardIds.length>0)}}catch{return{groups:[]}}}async loadHierarchyConfigs(){try{return(await this.course.getAllNavigationStrategies()).filter(t=>t.implementingClass===`hierarchyDefinition`).map(t=>{try{return{prerequisites:JSON.parse(t.serializedData).prerequisites||{}}}catch{return{prerequisites:{}}}})}catch(t){return logger.debug(`[Prescribed] Failed to load hierarchy configs: ${t}`),[]}}buildGroupRuntimeState(t){let{group:c,priorState:u,activeIds:d,seenIds:m,tagsByCard:g,cardsByTag:b,hierarchyConfigs:S,userTagElo:C,userGlobalElo:w}=t,T=new Set;for(let t of c.targetCardIds)(d.has(t)||m.has(t))&&T.add(t);if(u?.encounteredCardIds?.length)for(let t of u.encounteredCardIds)T.add(t);let E=c.targetCardIds.filter(t=>!T.has(t)),D=new Map;for(let t of E)D.set(t,g.get(t)??[]);let O=[],Or=[],kr=new Set;for(let t of E){let u=D.get(t)??[],d=this.resolveBlockedSupportTags(u,S,C,w,c.hierarchyWalk?.enabled!==!1,c.hierarchyWalk?.maxDepth??DEFAULT_HIERARCHY_DEPTH),m=u.filter(t=>t.startsWith(`gpc:intro:`)),g=new Set(u.filter(t=>t.startsWith(`gpc:expose:`)));for(let t of m){let c=t.slice(10);c&&g.add(`gpc:expose:${c}`)}let b=[...g].filter(t=>{let c=C[t];return!c||c.count<DEFAULT_MIN_COUNT});b.length>0&&b.forEach(t=>kr.add(t)),d.blocked||b.length>0?(O.push(t),d.supportTags.forEach(t=>kr.add(t))):Or.push(t)}let Ar=dedupe([...c.supportCardIds??[],...this.findSupportCardsByTags(c,g,[...kr])]).filter(t=>!d.has(t)&&!m.has(t)),jr=O.length>0&&kr.size>0&&Ar.length===0?this.findDiscoveredSupportCards({supportTags:[...kr],cardsByTag:b,activeIds:d,seenIds:m,excludedIds:new Set([...c.targetCardIds,...c.supportCardIds??[]]),limit:c.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN}):[];O.length>0&&kr.size>0&&jr.length===0&&logger.info(`[Prescribed] Group '${c.id}' discovered 0 broader support candidates (blocked=${O.length}; authoredSupport=${Ar.length})`);let Mr=u?.sessionsSinceSurfaced??0,Nr=c.freshnessWindowSessions??DEFAULT_FRESHNESS_WINDOW,Pr=Math.max(0,Mr-Nr),Fr=E.length===0?1:clamp(1+Pr*.75+Math.min(2,E.length*.1),1,MAX_TARGET_MULTIPLIER),Ir=O.length===0?1:clamp(1+Pr*.5+Math.min(1.5,O.length*.15),1,MAX_SUPPORT_MULTIPLIER);return{group:c,encounteredTargets:T,pendingTargets:E,blockedTargets:O,surfaceableTargets:Or,targetTags:D,supportCandidates:Ar,discoveredSupportCandidates:jr,supportTags:[...kr],pressureMultiplier:Fr,supportMultiplier:Ir,debugVersion:PRESCRIBED_DEBUG_VERSION}}buildNextGroupState(t,c){let u=c?.sessionsSinceSurfaced??0,d=!1;return{encounteredCardIds:[...t.encounteredTargets].sort(),pendingTargetIds:[...t.pendingTargets].sort(),lastSurfacedAt:c?.lastSurfacedAt??null,sessionsSinceSurfaced:u+1,lastSupportAt:c?.lastSupportAt??null,blockedTargetIds:[...t.blockedTargets].sort(),lastResolvedSupportTags:[...t.supportTags].sort()}}buildDirectTargetCards(t,c,u){let d=t.group.maxDirectTargetsPerRun??DEFAULT_MAX_DIRECT_PER_RUN,m=t.surfaceableTargets.filter(t=>!u.has(t)).slice(0,d),g=[];for(let d of m)u.add(d),g.push({cardId:d,courseId:c,score:BASE_TARGET_SCORE*t.pressureMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:BASE_TARGET_SCORE*t.pressureMultiplier,reason:`mode=target;group=${t.group.id};pending=${t.pendingTargets.length};surfaceable=${t.surfaceableTargets.length};blocked=${t.blockedTargets.length};blockedTargets=${t.blockedTargets.join(`|`)||`none`};supportTags=${t.supportTags.join(`|`)||`none`};multiplier=${t.pressureMultiplier.toFixed(2)};testversion=${t.debugVersion}`}]});return g}buildSupportCards(t,c,u){if(t.blockedTargets.length===0||t.supportCandidates.length===0)return[];let d=t.group.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN,m=t.supportCandidates.filter(t=>!u.has(t)).slice(0,d),g=[];for(let d of m)u.add(d),g.push({cardId:d,courseId:c,score:BASE_SUPPORT_SCORE*t.supportMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:BASE_SUPPORT_SCORE*t.supportMultiplier,reason:`mode=support;group=${t.group.id};pending=${t.pendingTargets.length};blocked=${t.blockedTargets.length};blockedTargets=${t.blockedTargets.join(`|`)||`none`};supportCard=${d};supportTags=${t.supportTags.join(`|`)||`none`};multiplier=${t.supportMultiplier.toFixed(2)};testversion=${t.debugVersion}`}]});return g}buildDiscoveredSupportCards(t,c,u){if(t.blockedTargets.length===0||t.discoveredSupportCandidates.length===0)return[];let d=t.group.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN,m=t.discoveredSupportCandidates.filter(t=>!u.has(t)).slice(0,d),g=[];for(let d of m)u.add(d),g.push({cardId:d,courseId:c,score:DISCOVERED_SUPPORT_SCORE*t.supportMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:DISCOVERED_SUPPORT_SCORE*t.supportMultiplier,reason:`mode=discovered-support;group=${t.group.id};pending=${t.pendingTargets.length};blocked=${t.blockedTargets.length};blockedTargets=${t.blockedTargets.join(`|`)||`none`};supportCard=${d};supportTags=${t.supportTags.join(`|`)||`none`};multiplier=${t.supportMultiplier.toFixed(2)};testversion=${t.debugVersion}`}]});return g}findSupportCardsByTags(t,c,u){if(u.length===0)return[];let d=t.supportCardIds??[],m=t.supportTagPatterns??[];if(d.length===0&&m.length===0)return[];let g=new Set;for(let t of d){let d=c.get(t)??[],b=u.some(t=>d.includes(t)),S=m.some(t=>d.some(c=>matchesTagPattern(c,t)));(b||S)&&g.add(t)}return[...g]}findDiscoveredSupportCards(t){let{supportTags:c,cardsByTag:u,activeIds:d,seenIds:m,excludedIds:g,limit:b}=t,S=new Map;for(let t of c){let c=u.get(t)??[];for(let t of c){if(d.has(t)||m.has(t)||g.has(t))continue;let c=S.get(t);c?c.matches+=1:S.set(t,{cardId:t,matches:1})}}let C=[...S.values()].sort((t,c)=>c.matches-t.matches||t.cardId.localeCompare(c.cardId)),w=new Set,T=[],E=[];for(let t of C){let c=extractWordStem(t.cardId);w.has(c)?E.push(t):(w.add(c),T.push(t))}return shuffleInPlace(T),shuffleInPlace(E),[...T,...E].slice(0,b).map(t=>t.cardId)}resolveBlockedSupportTags(t,c,u,d,m,g){let b=new Set,S=!1;for(let C of t){let t=c.map(t=>t.prerequisites[C]).filter(t=>Array.isArray(t)&&t.length>0);if(t.length!==0&&t.some(t=>t.some(t=>!this.isPrerequisiteMet(t,u[t.tag],d)))){if(S=!0,!m){for(let c of t)for(let t of c)this.isPrerequisiteMet(t,u[t.tag],d)||b.add(t.tag);continue}for(let m of t)for(let t of m)this.isPrerequisiteMet(t,u[t.tag],d)||this.collectSupportTagsRecursive(t.tag,c,u,d,g,new Set,b)}}return{blocked:S,supportTags:[...b]}}collectSupportTagsRecursive(t,c,u,d,m,g,b){if(m<0||g.has(t))return;g.add(t);let S=!1;for(let C of c){let w=C.prerequisites[t];if(!w||w.length===0)continue;let T=w.filter(t=>!this.isPrerequisiteMet(t,u[t.tag],d));if(T.length>0&&m>0){S=!0;for(let t of T)this.collectSupportTagsRecursive(t.tag,c,u,d,m-1,g,b)}}S||b.add(t)}isPrerequisiteMet(t,c,u){if(!c)return!1;let d=t.masteryThreshold?.minCount??DEFAULT_MIN_COUNT;return c.count<d?!1:t.masteryThreshold?.minElo===void 0?t.masteryThreshold?.minCount===void 0?c.score>=u:!0:c.score>=t.masteryThreshold.minElo}}}}),srs_exports={},__export(srs_exports,{default:()=>SRSNavigator}),init_srs=__esm({"src/core/navigators/generators/srs.ts"(){"use strict";init_navigators(),init_logger(),DEFAULT_HEALTHY_BACKLOG=20,MAX_BACKLOG_PRESSURE=.5,SRSNavigator=class extends ContentNavigator{constructor(t,c,u){super(t,c,u),_defineProperty$2(this,`name`,void 0),_defineProperty$2(this,`healthyBacklog`,void 0),this.name=u?.name||`SRS`,this.healthyBacklog=this.parseConfig(u?.serializedData).healthyBacklog??DEFAULT_HEALTHY_BACKLOG}parseConfig(t){if(!t)return{};try{return JSON.parse(t)}catch{return logger.warn(`[SRS] Failed to parse strategy config, using defaults`),{}}}async getWeightedCards(t,c){if(!this.user||!this.course)throw Error(`SRSNavigator requires user and course to be set`);let u=this.course.getCourseID(),d=await this.user.getPendingReviews(u),m=hooks.utc(),g=d.filter(t=>m.isAfter(hooks.utc(t.reviewTime)));if(g.length>0){let t=[...new Set(g.map(t=>t.cardId))],c=await this.course.getAppliedTagsBatch(t),u=[];if(g=g.filter(t=>(c.get(t.cardId)??[]).includes(`srs:skip`)?(u.push(t._id),!1):!0),u.length>0){logger.info(`[SRS] Removing ${u.length} scheduled reviews for srs:skip cards`);for(let t of u)this.user.removeScheduledCardReview(t)}}let b=this.computeBacklogPressure(g.length);if(g.length>0){let t=b>0?` [backlog pressure: +${b.toFixed(2)}]`:` [healthy backlog]`;logger.info(`[SRS] Course ${u}: ${g.length} reviews due now (of ${d.length} scheduled)${t}`)}else if(d.length>0){let t=[...d].sort((t,c)=>hooks.utc(t.reviewTime).diff(hooks.utc(c.reviewTime)))[0],c=hooks.utc(t.reviewTime),g=hooks.duration(c.diff(m)),b=g.asHours()<1?`${Math.round(g.asMinutes())}m`:g.asHours()<24?`${Math.round(g.asHours())}h`:`${Math.round(g.asDays())}d`;logger.info(`[SRS] Course ${u}: 0 reviews due now (${d.length} scheduled, next in ${b})`)}else logger.info(`[SRS] Course ${u}: No reviews scheduled`);return{cards:g.map(t=>{let{score:c,reason:u}=this.computeUrgencyScore(t,m,b);return{cardId:t.cardId,courseId:t.courseId,score:c,reviewID:t._id,provenance:[{strategy:`srs`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-SRS-default`,action:`generated`,score:c,reason:u}]}}).sort((t,c)=>c.score-t.score).slice(0,t)}}computeBacklogPressure(t){if(t<=this.healthyBacklog)return 0;let c=(t-this.healthyBacklog)/this.healthyBacklog*(MAX_BACKLOG_PRESSURE/2);return Math.min(MAX_BACKLOG_PRESSURE,c)}computeUrgencyScore(t,c,u){let d=hooks.utc(t.scheduledAt),m=hooks.utc(t.reviewTime),g=Math.max(1,m.diff(d,`hours`)),b=c.diff(m,`hours`),S=b/g,C=.3+.7*Math.exp(-g/720),w=.5+(Math.min(1,Math.max(0,S))*.5+C*.5)*.45,T=Math.min(1,w+u),E=[`${Math.round(b)}h overdue`,`interval: ${Math.round(g)}h`,`relative: ${S.toFixed(2)}`,`recency: ${C.toFixed(2)}`];return u>0&&E.push(`backlog: +${u.toFixed(2)}`),E.push(`review`),{score:T,reason:E.join(`, `)}}}}}),types_exports={},init_types=__esm({"src/core/navigators/generators/types.ts"(){"use strict";}}),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))})}}),init_contentNavigationStrategy=__esm({"src/core/types/contentNavigationStrategy.ts"(){"use strict";DEFAULT_LEARNABLE_WEIGHT={weight:1,confidence:.1,sampleSize:0}}}),WeightedFilter_exports={},__export(WeightedFilter_exports,{WeightedFilter:()=>WeightedFilter}),init_WeightedFilter=__esm({"src/core/navigators/filters/WeightedFilter.ts"(){"use strict";init_contentNavigationStrategy(),WeightedFilter=class{constructor(t,c=DEFAULT_LEARNABLE_WEIGHT,u=!1,d){_defineProperty$2(this,`name`,void 0),_defineProperty$2(this,`inner`,void 0),_defineProperty$2(this,`learnable`,void 0),_defineProperty$2(this,`staticWeight`,void 0),_defineProperty$2(this,`strategyId`,void 0),this.inner=t,this.name=t.name,this.learnable=c,this.staticWeight=u,this.strategyId=d}async transform(t,c){let u=this.learnable.weight,d;if(!this.staticWeight&&c.orchestration){let t=this.strategyId||this.inner.strategyId||this.name;u=c.orchestration.getEffectiveWeight(t,this.learnable),d=c.orchestration.getDeviation(t)}if(Math.abs(u-1)<.001)return this.inner.transform(t,c);let m=new Map;for(let c of t)m.set(c.cardId,c.score);return(await this.inner.transform(t,c)).map(t=>{let c=m.get(t.cardId);if(c===void 0||c===0||t.score===0)return t;let g=t.score/c;if(Math.abs(g-1)<1e-4)return t;let b=c*g**+u,S=t.provenance.length-1,C=t.provenance[S];if(C){let c=[...t.provenance];return c[S]={...C,score:b,effectiveWeight:u,deviation:d},{...t,score:b,provenance:c}}return{...t,score:b}})}}}}),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}),init_eloDistance=__esm({"src/core/navigators/filters/eloDistance.ts"(){"use strict";DEFAULT_HALF_LIFE=200,DEFAULT_MIN_MULTIPLIER=.3,DEFAULT_MAX_MULTIPLIER=1}}),hierarchyDefinition_exports={},__export(hierarchyDefinition_exports,{default:()=>HierarchyDefinitionNavigator}),init_hierarchyDefinition=__esm({"src/core/navigators/filters/hierarchyDefinition.ts"(){"use strict";init_navigators(),init_logger(),DEFAULT_MIN_COUNT2=3,HierarchyDefinitionNavigator=class extends ContentNavigator{constructor(t,c,u){super(t,c,u),_defineProperty$2(this,`config`,void 0),_defineProperty$2(this,`name`,void 0),this.config=this.parseConfig(u.serializedData),this.name=u.name||`Hierarchy Definition`}parseConfig(t){try{return{prerequisites:JSON.parse(t).prerequisites||{}}}catch{return{prerequisites:{}}}}isPrerequisiteMet(t,c,u){if(!c)return!1;let d=t.masteryThreshold?.minCount??DEFAULT_MIN_COUNT2;return c.count<d?!1:t.masteryThreshold?.minElo===void 0?t.masteryThreshold?.minCount===void 0?c.score>=u:!0:c.score>=t.masteryThreshold.minElo}async getMasteredTags(t){let c=new Set;try{let u=toCourseElo((await t.user.getCourseRegDoc(t.course.getCourseID())).elo);for(let t of Object.values(this.config.prerequisites))for(let d of t){let t=u.tags[d.tag];this.isPrerequisiteMet(d,t,u.global.score)&&c.add(d.tag)}}catch{}return c}getUnlockedTags(t){let c=new Set;for(let[u,d]of Object.entries(this.config.prerequisites))d.every(c=>t.has(c.tag))&&c.add(u);return c}hasPrerequisites(t){return t in this.config.prerequisites}async checkCardUnlock(t,c,u,d){try{let c=t.tags??[],m=c.filter(t=>this.hasPrerequisites(t)&&!u.has(t));return m.length===0?{isUnlocked:!0,reason:`Prerequisites met, tags: ${c.length>0?c.join(`, `):`none`}`}:{isUnlocked:!1,reason:`Blocked: missing prerequisites ${m.flatMap(t=>(this.config.prerequisites[t]||[]).filter(t=>!d.has(t.tag)).map(t=>t.tag)).join(`, `)} for tags ${m.join(`, `)}`}}catch{return{isUnlocked:!0,reason:`Prerequisites check skipped (tag lookup failed)`}}}getPreReqBoosts(t,c){let u=new Map;for(let[d,m]of Object.entries(this.config.prerequisites))if(!t.has(d))for(let t of m){if(!t.preReqBoost||t.preReqBoost<=1||c.has(t.tag))continue;let d=u.get(t.tag)??1;u.set(t.tag,Math.max(d,t.preReqBoost))}return u}getTargetBoosts(t){let c=new Map,u=Object.keys(this.config.prerequisites),d=[...t];logger.info(`[HierarchyDefinition:targetBoost:trace] ${this.name} | configKeys=${u.length}, unlocked=${d.length} (${d.slice(0,5).join(`, `)}${d.length>5?`...`:``})`);for(let[u,d]of Object.entries(this.config.prerequisites))if(t.has(u)){logger.info(`[HierarchyDefinition:targetBoost:trace] UNLOCKED ${u}: ${d.length} prereqs, raw=${JSON.stringify(d.map(t=>({tag:t.tag,tb:t.targetBoost})))}`);for(let t of d){if(!t.targetBoost||t.targetBoost<=1)continue;let d=c.get(u)??1;c.set(u,Math.max(d,t.targetBoost))}}return c.size>0?logger.info(`[HierarchyDefinition] targetBoosts active: ${[...c.entries()].map(([t,c])=>`${t}=\xD7${c}`).join(`, `)}`):logger.info(`[HierarchyDefinition:targetBoost:trace] no targetBoosts found despite ${d.length} unlocked tags`),c}async transform(t,c){let u=await this.getMasteredTags(c),d=this.getUnlockedTags(u),m=this.getPreReqBoosts(d,u),g=this.getTargetBoosts(d),b=[];for(let S of t){let{isUnlocked:t,reason:C}=await this.checkCardUnlock(S,c.course,d,u),w=t?S.score:S.score*.02,T=t?`passed`:`penalized`,E=C;if(t&&m.size>0){let t=S.tags??[],c=1,u=[];for(let d of t){let t=m.get(d);t&&t>c&&(c=t,u.push(d))}c>1&&(w*=c,T=`boosted`,E=`${C} | preReqBoost \xD7${c.toFixed(2)} for ${u.join(`, `)}`,logger.info(`[HierarchyDefinition] preReqBoost \xD7${c.toFixed(2)} applied to card ${S.cardId} via tags [${u.join(`, `)}] (score: ${S.score.toFixed(3)} \u2192 ${w.toFixed(3)})`))}if(t&&g.size>0){let t=S.tags??[],c=1,u=[];for(let d of t){let t=g.get(d);t&&t>c&&(c=t,u.push(d))}c>1&&(w*=c,T=`boosted`,E=`${E} | targetBoost \xD7${c.toFixed(2)} for ${u.join(`, `)}`,logger.info(`[HierarchyDefinition] targetBoost \xD7${c.toFixed(2)} applied to card ${S.cardId} via tags [${u.join(`, `)}] (score: ${S.score.toFixed(3)} \u2192 ${w.toFixed(3)})`))}b.push({...S,score:w,provenance:[...S.provenance,{strategy:`hierarchyDefinition`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-hierarchy`,action:T,score:w,reason:E}]})}return b}async getWeightedCards(t){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}),init_userTagPreference=__esm({"src/core/navigators/filters/userTagPreference.ts"(){"use strict";init_navigators(),UserTagPreferenceFilter=class extends ContentNavigator{constructor(t,c,u){super(t,c,u),_defineProperty$2(this,`_strategyData`,void 0),_defineProperty$2(this,`name`,void 0),this._strategyData=u,this.name=u.name||`User Tag Preferences`}computeMultiplier(t,c){let u=t.map(t=>c[t]).filter(t=>t!==void 0);return u.length===0?1:Math.max(...u)}buildReason(t,c,u){let d=t.filter(t=>c[t]===u);return u===0?`Excluded by user preference: ${d.join(`, `)} (${u}x)`:u<1?`Penalized by user preference: ${d.join(`, `)} (${u.toFixed(2)}x)`:u>1?`Boosted by user preference: ${d.join(`, `)} (${u.toFixed(2)}x)`:`No matching user preferences`}async transform(t,c){let u=await this.getStrategyState();return!u||Object.keys(u.boost).length===0?t.map(t=>({...t,provenance:[...t.provenance,{strategy:`userTagPreference`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||this._strategyData._id,action:`passed`,score:t.score,reason:`No user tag preferences configured`}]})):await Promise.all(t.map(async t=>{let c=t.tags??[],d=this.computeMultiplier(c,u.boost),m=Math.min(1,t.score*d),g;return g=d===0||d<1?`penalized`:d>1?`boosted`:`passed`,{...t,score:m,provenance:[...t.provenance,{strategy:`userTagPreference`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||this._strategyData._id,action:g,score:m,reason:this.buildReason(c,u.boost,d)}]}}))}async getWeightedCards(t){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}),init_filters=__esm({"src/core/navigators/filters/index.ts"(){"use strict";init_eloDistance(),init_userTagPreference()}}),inferredPreferenceStub_exports={},__export(inferredPreferenceStub_exports,{INFERRED_PREFERENCE_NAVIGATOR_STUB:()=>INFERRED_PREFERENCE_NAVIGATOR_STUB}),init_inferredPreferenceStub=__esm({"src/core/navigators/filters/inferredPreferenceStub.ts"(){"use strict";INFERRED_PREFERENCE_NAVIGATOR_STUB=!0}}),interferenceMitigator_exports={},__export(interferenceMitigator_exports,{default:()=>InterferenceMitigatorNavigator}),init_interferenceMitigator=__esm({"src/core/navigators/filters/interferenceMitigator.ts"(){"use strict";init_navigators(),DEFAULT_MIN_COUNT3=10,DEFAULT_MIN_ELAPSED_DAYS=3,DEFAULT_INTERFERENCE_DECAY=.8,InterferenceMitigatorNavigator=class extends ContentNavigator{constructor(t,c,u){super(t,c,u),_defineProperty$2(this,`config`,void 0),_defineProperty$2(this,`name`,void 0),_defineProperty$2(this,`interferenceMap`,void 0),this.config=this.parseConfig(u.serializedData),this.interferenceMap=this.buildInterferenceMap(),this.name=u.name||`Interference Mitigator`}parseConfig(t){try{let c=JSON.parse(t),u=c.interferenceSets||[];return u.length>0&&Array.isArray(u[0])&&(u=u.map(t=>({tags:t}))),{interferenceSets:u,maturityThreshold:{minCount:c.maturityThreshold?.minCount??DEFAULT_MIN_COUNT3,minElo:c.maturityThreshold?.minElo,minElapsedDays:c.maturityThreshold?.minElapsedDays??DEFAULT_MIN_ELAPSED_DAYS},defaultDecay:c.defaultDecay??DEFAULT_INTERFERENCE_DECAY}}catch{return{interferenceSets:[],maturityThreshold:{minCount:DEFAULT_MIN_COUNT3,minElapsedDays:DEFAULT_MIN_ELAPSED_DAYS},defaultDecay:DEFAULT_INTERFERENCE_DECAY}}}buildInterferenceMap(){let t=new Map;for(let c of this.config.interferenceSets){let u=c.decay??this.config.defaultDecay??DEFAULT_INTERFERENCE_DECAY;for(let d of c.tags){t.has(d)||t.set(d,[]);let m=t.get(d);for(let t of c.tags)if(t!==d){let c=m.find(c=>c.partner===t);c?c.decay=Math.max(c.decay,u):m.push({partner:t,decay:u})}}}return t}async getImmatureTags(t){let c=new Set;try{let u=toCourseElo((await t.user.getCourseRegDoc(t.course.getCourseID())).elo),d=this.config.maturityThreshold?.minCount??DEFAULT_MIN_COUNT3,m=this.config.maturityThreshold?.minElo,g=(this.config.maturityThreshold?.minElapsedDays??DEFAULT_MIN_ELAPSED_DAYS)*2;for(let[t,b]of Object.entries(u.tags)){if(b.count===0)continue;let u=b.count<d,S=m!==void 0&&b.score<m,C=b.count<g;(u||S||C)&&c.add(t)}}catch{}return c}getTagsToAvoid(t){let c=new Map;for(let u of t){let d=this.interferenceMap.get(u);if(d){for(let{partner:u,decay:m}of d)if(!t.has(u)){let t=c.get(u)??0;c.set(u,Math.max(t,m))}}}return c}computeInterferenceEffect(t,c,u){if(c.size===0)return{multiplier:1,interferingTags:[],reason:`No interference detected`};let d=1,m=[];for(let u of t){let t=c.get(u);t!==void 0&&(m.push(u),d*=1-t)}if(m.length===0)return{multiplier:1,interferingTags:[],reason:`No interference detected`};let g=new Set;for(let t of m)for(let c of u)this.interferenceMap.get(c)?.some(c=>c.partner===t)&&g.add(c);let b=`Interferes with immature tags ${Array.from(g).join(`, `)} (tags: ${m.join(`, `)}, multiplier: ${d.toFixed(2)})`;return{multiplier:d,interferingTags:m,reason:b}}async transform(t,c){let u=await this.getImmatureTags(c),d=this.getTagsToAvoid(u),m=[];for(let c of t){let t=c.tags??[],{multiplier:g,reason:b}=this.computeInterferenceEffect(t,d,u),S=c.score*g,C=g<1?`penalized`:g>1?`boosted`:`passed`;m.push({...c,score:S,provenance:[...c.provenance,{strategy:`interferenceMitigator`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-interference`,action:C,score:S,reason:b}]})}return m}async getWeightedCards(t){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}),init_relativePriority=__esm({"src/core/navigators/filters/relativePriority.ts"(){"use strict";init_navigators(),DEFAULT_PRIORITY=.5,DEFAULT_PRIORITY_INFLUENCE=.5,DEFAULT_COMBINE_MODE=`max`,RelativePriorityNavigator=class extends ContentNavigator{constructor(t,c,u){super(t,c,u),_defineProperty$2(this,`config`,void 0),_defineProperty$2(this,`name`,void 0),this.config=this.parseConfig(u.serializedData),this.name=u.name||`Relative Priority`}parseConfig(t){try{let c=JSON.parse(t);return{tagPriorities:c.tagPriorities||{},defaultPriority:c.defaultPriority??DEFAULT_PRIORITY,combineMode:c.combineMode??DEFAULT_COMBINE_MODE,priorityInfluence:c.priorityInfluence??DEFAULT_PRIORITY_INFLUENCE}}catch{return{tagPriorities:{},defaultPriority:DEFAULT_PRIORITY,combineMode:DEFAULT_COMBINE_MODE,priorityInfluence:DEFAULT_PRIORITY_INFLUENCE}}}getTagPriority(t){return this.config.tagPriorities[t]??this.config.defaultPriority??DEFAULT_PRIORITY}computeCardPriority(t){if(t.length===0)return this.config.defaultPriority??DEFAULT_PRIORITY;let c=t.map(t=>this.getTagPriority(t));switch(this.config.combineMode){case`max`:return Math.max(...c);case`min`:return Math.min(...c);case`average`:return c.reduce((t,c)=>t+c,0)/c.length;default:return Math.max(...c)}}computeBoostFactor(t){let c=this.config.priorityInfluence??DEFAULT_PRIORITY_INFLUENCE;return 1+(t-.5)*c}buildPriorityReason(t,c,u,d){if(t.length===0)return`No tags, neutral priority (${c.toFixed(2)})`;let m=t.slice(0,3).join(`, `),g=t.length>3?` (+${t.length-3} more)`:``;return u===1?`Neutral priority (${c.toFixed(2)}) for tags: ${m}${g}`:u>1?`High-priority tags: ${m}${g} (priority ${c.toFixed(2)} \u2192 boost ${u.toFixed(2)}x \u2192 ${d.toFixed(2)})`:`Low-priority tags: ${m}${g} (priority ${c.toFixed(2)} \u2192 reduce ${u.toFixed(2)}x \u2192 ${d.toFixed(2)})`}async transform(t,c){return await Promise.all(t.map(async t=>{let c=t.tags??[],u=this.computeCardPriority(c),d=this.computeBoostFactor(u),m=Math.max(0,t.score*d),g=d>1?`boosted`:d<1?`penalized`:`passed`,b=this.buildPriorityReason(c,u,d,m);return{...t,score:m,provenance:[...t.provenance,{strategy:`relativePriority`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-priority`,action:g,score:m,reason:b}]}}))}async getWeightedCards(t){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"(){"use strict";}}),userGoalStub_exports={},__export(userGoalStub_exports,{USER_GOAL_NAVIGATOR_STUB:()=>USER_GOAL_NAVIGATOR_STUB}),init_userGoalStub=__esm({"src/core/navigators/filters/userGoalStub.ts"(){"use strict";USER_GOAL_NAVIGATOR_STUB=!0}}),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))})}}),init_gradient=__esm({"src/core/orchestration/gradient.ts"(){"use strict";init_logger()}}),init_learning=__esm({"src/core/orchestration/learning.ts"(){"use strict";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}}),init_signal=__esm({"src/core/orchestration/signal.ts"(){"use strict";}}),init_recording=__esm({"src/core/orchestration/recording.ts"(){"use strict";init_signal(),init_types_legacy(),init_logger()}}),init_orchestration=__esm({"src/core/orchestration/index.ts"(){"use strict";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}),init_Pipeline=__esm({"src/core/navigators/Pipeline.ts"(){"use strict";init_navigators(),init_logger(),init_orchestration(),init_PipelineDebugger(),VERBOSE_RESULTS=!0,Pipeline=class extends ContentNavigator{constructor(t,c,u,d){super(),_defineProperty$2(this,`generator`,void 0),_defineProperty$2(this,`filters`,void 0),_defineProperty$2(this,`_cachedOrchestration`,null),_defineProperty$2(this,`_tagCache`,new Map),_defineProperty$2(this,`_ephemeralHints`,null),this.generator=t,this.filters=c,this.user=u,this.course=d,d.getCourseConfig().then(t=>{logger.debug(`[pipeline] Crated pipeline for ${t.name}`)}).catch(t=>{logger.error(`[pipeline] Failed to lookup courseCfg: ${t}`)}),logPipelineConfig(t,c),registerPipelineForDebug(this)}setEphemeralHints(t){this._ephemeralHints=t,logger.info(`[Pipeline] Ephemeral hints set: ${JSON.stringify(t)}`)}async getWeightedCards(t){let c=performance.now(),u=await this.buildContext(),d=performance.now(),m=500;logger.debug(`[Pipeline] Fetching 500 candidates from generator '${this.generator.name}'`);let g=await this.generator.getWeightedCards(500,u),b=g.cards,S=performance.now(),C=b.length;this._ephemeralHints=mergeHints2([this._ephemeralHints,g.hints])??null;let w;if(this.generator.generators){let t=new Map;for(let c of b){let u=c.provenance[0];if(u){let d=u.strategyName;t.has(d)||t.set(d,{cards:[]}),t.get(d).cards.push(c)}}w=Array.from(t.entries()).map(([t,c])=>{let u=c.cards.filter(t=>t.provenance[0]?.reason?.includes(`new card`)),d=c.cards.filter(t=>t.provenance[0]?.reason?.includes(`review`));return{name:t,cardCount:c.cards.length,newCount:u.length,reviewCount:d.length,topScore:Math.max(...c.cards.map(t=>t.score),0)}})}logger.debug(`[Pipeline] Generator returned ${C} candidates`),b=await this.hydrateTags(b);let T=performance.now(),E=[...b],D=this._ephemeralHints;if(D?.requireCards?.length){let t=new Set(E.map(t=>t.cardId)),c=D.requireCards.filter(c=>!c.includes(`*`)&&!t.has(c));if(c.length>0){let t=await this.course.getAppliedTagsBatch(c),u=this.course.getCourseID();for(let d of c)E.push({cardId:d,courseId:u,score:1,tags:t.get(d)??[],provenance:[]});logger.info(`[Pipeline] Pre-fetched ${c.length} required card(s) into pool: ${c.join(`, `)}`)}}let O=new Set(b.filter(t=>t.provenance.some(t=>t.strategy===`prescribed`)).map(t=>t.cardId)),Or=[];for(let t of this.filters){let c=b.length,d=new Map(b.map(t=>[t.cardId,t.score]));b=await t.transform(b,u);let m=0,g=0,S=0,C=c-b.length;for(let t of b){let c=d.get(t.cardId)??0;t.score>c?m++:t.score<c?g++:S++}if(Or.push({name:t.name,boosted:m,penalized:g,passed:S,removed:C}),O.size>0){let c=new Set(b.map(t=>t.cardId)),u=[...O].filter(t=>!c.has(t)),d=b.filter(t=>O.has(t.cardId)&&t.score===0).map(t=>t.cardId);(u.length>0||d.length>0)&&(logger.info(`[Pipeline] Filter '${t.name}' impact on prescribed cards: `+(u.length>0?`removed=[${u.join(`, `)}] `:``)+(d.length>0?`zeroed=[${d.join(`, `)}]`:``)),u.forEach(t=>O.delete(t)))}logger.debug(`[Pipeline] Filter '${t.name}': ${d.size} \u2192 ${b.length} cards (\u2191${m} \u2193${g} =${S})`)}b=b.filter(t=>t.score>0);let kr=this._ephemeralHints;kr&&(this._ephemeralHints=null,b=this.applyHints(b,kr,E)),b.sort((t,c)=>c.score-t.score);let Ar=performance.now(),jr=b.slice(0,t);logger.info(`[Pipeline:timing] total=${(Ar-c).toFixed(0)}ms (context=${(d-c).toFixed(0)} generate=${(S-d).toFixed(0)} hydrate=${(T-S).toFixed(0)} filter=${(Ar-T).toFixed(0)})`);let Mr=jr.slice(0,3).map(t=>t.score);logExecutionSummary(this.generator.name,C,this.filters.length,jr.length,Mr,Or),logResultCards(jr),logCardProvenance(jr,3);try{let t=await this.course?.getCourseConfig().then(t=>t.name).catch(()=>void 0);captureRun(buildRunReport(this.course?.getCourseID()||`unknown`,t,this.generator.name,w,C,Or,b,jr,u.userElo,kr??void 0))}catch(t){logger.debug(`[Pipeline] Failed to capture debug run: ${t}`)}return{cards:jr}}async hydrateTags(t){if(t.length===0)return t;let c=[];for(let u of t)this._tagCache.has(u.cardId)||c.push(u.cardId);if(c.length>0){let t=await this.course.getAppliedTagsBatch(c);for(let[c,u]of t)this._tagCache.set(c,u)}let u=new Map;for(let c of t)u.set(c.cardId,this._tagCache.get(c.cardId)??[]);return logTagHydration(t,u),t.map(t=>({...t,tags:this._tagCache.get(t.cardId)??[]}))}applyHints(t,c,u){let d=t.length;if(c.excludeCards?.length&&(t=t.filter(t=>!c.excludeCards.some(c=>globMatch(t.cardId,c)))),c.excludeTags?.length&&(t=t.filter(t=>!c.excludeTags.some(c=>cardMatchesTagPattern(t,c)))),c.boostTags)for(let[u,d]of Object.entries(c.boostTags))for(let m of t)cardMatchesTagPattern(m,u)&&(m.score*=d,m.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:c._label?`Replan Hint (${c._label})`:`Replan Hint`,action:`boosted`,score:m.score,reason:`boostTag ${u} \xD7${d}`}));if(c.boostCards)for(let[u,d]of Object.entries(c.boostCards))for(let m of t)globMatch(m.cardId,u)&&(m.score*=d,m.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:c._label?`Replan Hint (${c._label})`:`Replan Hint`,action:`boosted`,score:m.score,reason:`boostCard ${u} \xD7${d}`}));let m=new Set(t.map(t=>t.cardId)),g=new Map(t.map(t=>[t.cardId,t])),b=c._label?`Replan Hint (${c._label})`:`Replan Hint`,applyRequirement=(c,u)=>{let d=1/0,S=g.get(c.cardId);S?S.score<d&&(S.score=d,S.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:b,action:`boosted`,score:d,reason:`${u} (upgrade to mandatory score)`})):(t.push({...c,score:d,provenance:[...c.provenance,{strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:b,action:`boosted`,score:d,reason:u}]}),m.add(c.cardId),g.set(c.cardId,t[t.length-1]))};if(c.requireCards?.length)for(let t of c.requireCards){for(let c of m)globMatch(c,t)&&applyRequirement(g.get(c),`requireCard ${t}`);for(let c of u)globMatch(c.cardId,t)&&applyRequirement(c,`requireCard ${t}`)}if(c.requireTags?.length)for(let t of c.requireTags){for(let c of m){let u=g.get(c);cardMatchesTagPattern(u,t)&&applyRequirement(u,`requireTag ${t}`)}for(let c of u)cardMatchesTagPattern(c,t)&&applyRequirement(c,`requireTag ${t}`)}return logger.info(`[Pipeline] Hints applied: ${d} \u2192 ${t.length} cards`),t}async buildContext(){let t=1e3;try{t=toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo).global.score}catch(t){logger.debug(`[Pipeline] Could not get user ELO, using default: ${t}`)}this._cachedOrchestration||(this._cachedOrchestration=await createOrchestrationContext(this.user,this.course));let c=this._cachedOrchestration;return{user:this.user,course:this.course,userElo:t,orchestration:c}}getCourseID(){return this.course.getCourseID()}async getOrchestrationContext(){return createOrchestrationContext(this.user,this.course)}getStrategyIds(){let t=[],extractId=t=>t.strategyId?t.strategyId:null,c=extractId(this.generator);c&&t.push(c),this.generator.generators&&Array.isArray(this.generator.generators)&&this.generator.generators.forEach(c=>{let u=extractId(c);u&&t.push(u)});for(let c of this.filters){let u=extractId(c);u&&t.push(u)}return[...new Set(t)]}async getTagEloStatus(t){let c=toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo),u={};if(t){let d=Array.isArray(t)?t:[t];for(let t of d){let d=globToRegex(t);for(let[t,m]of Object.entries(c.tags))d.test(t)&&(u[t]={score:m.score,count:m.count})}}else for(let[t,d]of Object.entries(c.tags))u[t]={score:d.score,count:d.count};return u}async diagnoseCardSpace(t){let c=t?.threshold??.1,u=performance.now(),d=await this.course.getAllCardIds(),m=d.map(t=>({cardId:t,courseId:this.course.getCourseID(),score:1,provenance:[]}));m=await this.hydrateTags(m);let g=await this.buildContext(),b=[];for(let t of this.filters){m=await t.transform(m,g);let u=m.filter(t=>t.score>=c).length;b.push({name:t.name,wellIndicated:u})}let S=m.filter(t=>t.score>=c),C=new Set(S.map(t=>t.cardId)),w;try{let t=this.course.getCourseID(),c=await this.user.getSeenCards(t);w=new Set(c)}catch{w=new Set}let T=S.filter(t=>!w.has(t.cardId)),E=new Map;for(let t of m){let u=t.cardId.split(`-`)[1]||`unknown`;E.has(u)||E.set(u,{total:0,wellIndicated:0,new:0});let d=E.get(u);d.total++,t.score>=c&&(d.wellIndicated++,w.has(t.cardId)||d.new++)}let D=performance.now()-u,O={totalCards:d.length,threshold:c,wellIndicated:C.size,encountered:w.size,wellIndicatedNew:T.length,byType:Object.fromEntries(E),filterBreakdown:b,elapsedMs:Math.round(D)};logger.info(`[Pipeline:diagnose] Card space scan (${O.elapsedMs}ms):`),logger.info(`[Pipeline:diagnose] Total cards: ${O.totalCards}`),logger.info(`[Pipeline:diagnose] Well-indicated (score >= ${c}): ${O.wellIndicated}`),logger.info(`[Pipeline:diagnose] Encountered: ${O.encountered}`),logger.info(`[Pipeline:diagnose] Well-indicated & new: ${O.wellIndicatedNew}`),logger.info(`[Pipeline:diagnose] By type:`);for(let[t,c]of E)logger.info(`[Pipeline:diagnose] ${t}: ${c.wellIndicated}/${c.total} well-indicated, ${c.new} new`);logger.info(`[Pipeline:diagnose] After each filter:`);for(let t of b)logger.info(`[Pipeline:diagnose] ${t.name}: ${t.wellIndicated} well-indicated`);return O}}}}),defaults_exports={},__export(defaults_exports,{createDefaultEloStrategy:()=>createDefaultEloStrategy,createDefaultPipeline:()=>createDefaultPipeline,createDefaultSrsStrategy:()=>createDefaultSrsStrategy}),init_defaults=__esm({"src/core/navigators/defaults.ts"(){"use strict";init_navigators(),init_Pipeline(),init_CompositeGenerator(),init_elo(),init_srs(),init_eloDistance(),init_types_legacy()}}),PipelineAssembler_exports={},__export(PipelineAssembler_exports,{PipelineAssembler:()=>PipelineAssembler}),init_PipelineAssembler=__esm({"src/core/navigators/PipelineAssembler.ts"(){"use strict";init_navigators(),init_WeightedFilter(),init_Pipeline(),init_logger(),init_CompositeGenerator(),init_defaults(),PipelineAssembler=class{async assemble(t){let{strategies:c,user:u,course:d}=t,m=[];if(c.length===0)return{pipeline:null,generatorStrategies:[],filterStrategies:[],warnings:m};let g=[],b=[];for(let t of c)isGenerator(t.implementingClass)?g.push(t):isFilter(t.implementingClass)?b.push(t):m.push(`Unknown strategy type '${t.implementingClass}', skipping: ${t.name}`);let S=d.getCourseID(),C=g.some(t=>t.implementingClass===`elo`),w=g.some(t=>t.implementingClass===`srs`);if(C||(logger.debug(`[PipelineAssembler] No ELO generator configured, adding default`),g.push(createDefaultEloStrategy(S))),w||(logger.debug(`[PipelineAssembler] No SRS generator configured, adding default`),g.push(createDefaultSrsStrategy(S))),g.length===0)return m.push(`No generator strategy found`),{pipeline:null,generatorStrategies:[],filterStrategies:[],warnings:m};let T;g.length===1?(T=await ContentNavigator.create(u,d,g[0]),logger.debug(`[PipelineAssembler] Using single generator: ${g[0].name}`)):(logger.debug(`[PipelineAssembler] Using CompositeGenerator for ${g.length} generators: ${g.map(t=>t.name).join(`, `)}`),T=await CompositeGenerator.fromStrategies(u,d,g));let E=[],D=[...b].sort((t,c)=>t.name.localeCompare(c.name));for(let t of D)try{let c=await ContentNavigator.create(u,d,t);if(`transform`in c&&typeof c.transform==`function`){let u=c;t.learnable&&(u=new WeightedFilter(u,t.learnable,t.staticWeight,t._id)),E.push(u),logger.debug(`[PipelineAssembler] Added filter: ${t.name}`)}else m.push(`Filter '${t.name}' does not implement CardFilter.transform(), skipping`)}catch(c){m.push(`Failed to instantiate filter '${t.name}': ${c}`)}let O=new Pipeline(T,E,u,d);return logger.debug(`[PipelineAssembler] Assembled pipeline with ${g.length} generator(s) and ${E.length} filter(s)`),{pipeline:O,generatorStrategies:g,filterStrategies:D,warnings:m}}}}}),init_3=__esm({'import("./**/*") in src/core/navigators/index.ts'(){globImport=__glob({"./Pipeline.ts":()=>Promise.resolve().then(()=>(init_Pipeline(),Pipeline_exports)),"./PipelineAssembler.ts":()=>Promise.resolve().then(()=>(init_PipelineAssembler(),PipelineAssembler_exports)),"./PipelineDebugger.ts":()=>Promise.resolve().then(()=>(init_PipelineDebugger(),PipelineDebugger_exports)),"./defaults.ts":()=>Promise.resolve().then(()=>(init_defaults(),defaults_exports)),"./filters/WeightedFilter.ts":()=>Promise.resolve().then(()=>(init_WeightedFilter(),WeightedFilter_exports)),"./filters/eloDistance.ts":()=>Promise.resolve().then(()=>(init_eloDistance(),eloDistance_exports)),"./filters/hierarchyDefinition.ts":()=>Promise.resolve().then(()=>(init_hierarchyDefinition(),hierarchyDefinition_exports)),"./filters/index.ts":()=>Promise.resolve().then(()=>(init_filters(),filters_exports)),"./filters/inferredPreferenceStub.ts":()=>Promise.resolve().then(()=>(init_inferredPreferenceStub(),inferredPreferenceStub_exports)),"./filters/interferenceMitigator.ts":()=>Promise.resolve().then(()=>(init_interferenceMitigator(),interferenceMitigator_exports)),"./filters/relativePriority.ts":()=>Promise.resolve().then(()=>(init_relativePriority(),relativePriority_exports)),"./filters/types.ts":()=>Promise.resolve().then(()=>(init_types2(),types_exports2)),"./filters/userGoalStub.ts":()=>Promise.resolve().then(()=>(init_userGoalStub(),userGoalStub_exports)),"./filters/userTagPreference.ts":()=>Promise.resolve().then(()=>(init_userTagPreference(),userTagPreference_exports)),"./generators/CompositeGenerator.ts":()=>Promise.resolve().then(()=>(init_CompositeGenerator(),CompositeGenerator_exports)),"./generators/elo.ts":()=>Promise.resolve().then(()=>(init_elo(),elo_exports)),"./generators/index.ts":()=>Promise.resolve().then(()=>(init_generators(),generators_exports)),"./generators/prescribed.ts":()=>Promise.resolve().then(()=>(init_prescribed(),prescribed_exports)),"./generators/srs.ts":()=>Promise.resolve().then(()=>(init_srs(),srs_exports)),"./generators/types.ts":()=>Promise.resolve().then(()=>(init_types(),types_exports)),"./index.ts":()=>Promise.resolve().then(()=>(init_navigators(),navigators_exports))})}}),navigators_exports={},__export(navigators_exports,{ContentNavigator:()=>ContentNavigator,NavigatorRole:()=>NavigatorRole,NavigatorRoles:()=>NavigatorRoles,Navigators:()=>Navigators,getCardOrigin:()=>getCardOrigin,getRegisteredNavigator:()=>getRegisteredNavigator,getRegisteredNavigatorNames:()=>getRegisteredNavigatorNames,getRegisteredNavigatorRole:()=>getRegisteredNavigatorRole,hasRegisteredNavigator:()=>hasRegisteredNavigator,initializeNavigatorRegistry:()=>initializeNavigatorRegistry,isFilter:()=>isFilter,isGenerator:()=>isGenerator,mountPipelineDebugger:()=>mountPipelineDebugger,pipelineDebugAPI:()=>pipelineDebugAPI,registerNavigator:()=>registerNavigator}),init_navigators=__esm({"src/core/navigators/index.ts"(){"use strict";init_PipelineDebugger(),init_logger(),init_(),init_2(),init_3(),navigatorRegistry=new Map,Navigators=(t=>(t.ELO=`elo`,t.SRS=`srs`,t.PRESCRIBED=`prescribed`,t.HIERARCHY=`hierarchyDefinition`,t.INTERFERENCE=`interferenceMitigator`,t.RELATIVE_PRIORITY=`relativePriority`,t.USER_TAG_PREFERENCE=`userTagPreference`,t))(Navigators||{}),NavigatorRole=(t=>(t.GENERATOR=`generator`,t.FILTER=`filter`,t))(NavigatorRole||{}),NavigatorRoles={elo:`generator`,srs:`generator`,prescribed:`generator`,hierarchyDefinition:`filter`,interferenceMitigator:`filter`,relativePriority:`filter`,userTagPreference:`filter`},ContentNavigator=class{constructor(t,c,u){_defineProperty$2(this,`user`,void 0),_defineProperty$2(this,`course`,void 0),_defineProperty$2(this,`strategyName`,void 0),_defineProperty$2(this,`strategyId`,void 0),_defineProperty$2(this,`learnable`,void 0),_defineProperty$2(this,`staticWeight`,void 0),this.user=t,this.course=c,u&&(this.strategyName=u.name,this.strategyId=u._id,this.learnable=u.learnable,this.staticWeight=u.staticWeight)}get strategyKey(){return this.constructor.name}async getStrategyState(){if(!this.user||!this.course)throw Error(`Cannot get strategy state: navigator not properly initialized. Ensure user and course are provided to constructor.`);return this.user.getStrategyState(this.course.getCourseID(),this.strategyKey)}async putStrategyState(t){if(!this.user||!this.course)throw Error(`Cannot put strategy state: navigator not properly initialized. Ensure user and course are provided to constructor.`);return this.user.putStrategyState(this.course.getCourseID(),this.strategyKey,t)}static async create(t,c,u){let d=u.implementingClass,m=getRegisteredNavigator(d);if(m)return logger.debug(`[ContentNavigator.create] Using registered navigator: ${d}`),new m(t,c,u);logger.debug(`[ContentNavigator.create] Navigator not in registry, attempting dynamic import: ${d}`);let g;for(let t of[`.ts`,`.js`,``]){try{if(g=(await globImport_generators(`./generators/${d}${t}`)).default,g)break}catch(c){logger.debug(`Failed to load generator ${d}${t}:`,c)}try{if(g=(await globImport_filters(`./filters/${d}${t}`)).default,g)break}catch(c){logger.debug(`Failed to load filter ${d}${t}:`,c)}try{if(g=(await globImport(`./${d}${t}`)).default,g)break}catch(c){logger.debug(`Failed to load legacy ${d}${t}:`,c)}if(g)break}if(!g)throw Error(`Could not load navigator implementation for: ${d}`);return new g(t,c,u)}async getWeightedCards(t){throw Error(`${this.constructor.name} must implement getWeightedCards(). `)}setEphemeralHints(t){}}}}),init_courseDB=__esm({"src/impl/couch/courseDB.ts"(){"use strict";init_couch(),init_updateQueue(),init_types_legacy(),init_logger(),init_clientCache(),init_courseAPI(),init_courseLookupDB(),init_navigators(),init_PipelineAssembler(),init_defaults(),CoursesDB=class{constructor(t){_defineProperty$2(this,`_courseIDs`,void 0),t&&t.length>0?this._courseIDs=t:this._courseIDs=void 0}async getCourseList(){let t=await CourseLookup.allCourseWare();return logger.debug(`AllCourses: ${t.map(t=>t.name+`, `+t._id+`
349
+ `)}},mountPipelineDebugger()}}),CompositeGenerator_exports={},__export(CompositeGenerator_exports,{AggregationMode:()=>AggregationMode,default:()=>CompositeGenerator}),init_CompositeGenerator=__esm({"src/core/navigators/generators/CompositeGenerator.ts"(){"use strict";init_navigators(),init_logger(),AggregationMode=(t=>(t.MAX=`max`,t.AVERAGE=`average`,t.FREQUENCY_BOOST=`frequencyBoost`,t))(AggregationMode||{}),DEFAULT_AGGREGATION_MODE=`frequencyBoost`,FREQUENCY_BOOST_FACTOR=.1,CompositeGenerator=class _CompositeGenerator extends ContentNavigator{constructor(t,c=DEFAULT_AGGREGATION_MODE){if(super(),_defineProperty$2(this,`name`,`Composite Generator`),_defineProperty$2(this,`generators`,void 0),_defineProperty$2(this,`aggregationMode`,void 0),this.generators=t,this.aggregationMode=c,t.length===0)throw Error(`CompositeGenerator requires at least one generator`);logger.debug(`[CompositeGenerator] Created with ${t.length} generators, mode: ${c}`)}static async fromStrategies(t,c,u,d=DEFAULT_AGGREGATION_MODE){return new _CompositeGenerator(await Promise.all(u.map(u=>ContentNavigator.create(t,c,u))),d)}async getWeightedCards(t,c){if(!c)throw Error(`CompositeGenerator.getWeightedCards requires a GeneratorContext. It should be called via Pipeline, not directly.`);let u=await Promise.all(this.generators.map(u=>u.getWeightedCards(t,c))),d=[];u.forEach((t,c)=>{let u=t.cards,m=this.generators[c].name||`Generator ${c}`,g=u.filter(t=>t.provenance[0]?.reason?.includes(`new card`)),b=u.filter(t=>t.provenance[0]?.reason?.includes(`review`));if(u.length>0){let t=Math.max(...u.map(t=>t.score)).toFixed(2),c=[];g.length>0&&c.push(`${g.length} new`),b.length>0&&c.push(`${b.length} reviews`);let S=c.length>0?c.join(`, `):`${u.length} cards`;d.push(`${m}: ${S} (top: ${t})`)}else d.push(`${m}: 0 cards`)}),logger.info(`[Composite] Generator breakdown: ${d.join(` | `)}`);let m=new Map;u.forEach((t,u)=>{let d=t.cards,g=this.generators[u],b=g.learnable?.weight??1,S;if(g.learnable&&!g.staticWeight&&c.orchestration){let t=g.strategyId;t&&(b=c.orchestration.getEffectiveWeight(t,g.learnable),S=c.orchestration.getDeviation(t))}for(let t of d){t.provenance.length>0&&(t.provenance[0].effectiveWeight=b,t.provenance[0].deviation=S);let c=m.get(t.cardId)||[];c.push({card:t,weight:b}),m.set(t.cardId,c)}});let g=[];for(let[,t]of m){let c=t.map(t=>t.card),u=this.aggregateScores(t),d=Math.max(0,u),m=c.flatMap(t=>t.provenance),b=c[0].score,S=d>b?`boosted`:d<b?`penalized`:`passed`,C=this.buildAggregationReason(t,d);g.push({...c[0],score:d,provenance:[...m,{strategy:`composite`,strategyName:`Composite Generator`,strategyId:`COMPOSITE_GENERATOR`,action:S,score:d,reason:C}]})}return{cards:g.sort((t,c)=>c.score-t.score).slice(0,t),hints:mergeHints(u.map(t=>t.hints))}}buildAggregationReason(t,c){let u=t.map(t=>t.card),d=u.length,m=u.map(t=>t.score.toFixed(2)).join(`, `);if(d===1){let u=Math.abs(t[0].weight-1)>.001?` (w=${t[0].weight.toFixed(2)})`:``;return`Single generator, score ${c.toFixed(2)}${u}`}let g=u.map(t=>t.provenance[0]?.strategy||`unknown`).join(`, `);switch(this.aggregationMode){case`max`:return`Max of ${d} generators (${g}): scores [${m}] \u2192 ${c.toFixed(2)}`;case`average`:return`Weighted Avg of ${d} generators (${g}): scores [${m}] \u2192 ${c.toFixed(2)}`;case`frequencyBoost`:{let u=t.reduce((t,c)=>t+c.weight,0),m=t.reduce((t,c)=>t+c.card.score*c.weight,0),b=u>0?m/u:0,S=1+FREQUENCY_BOOST_FACTOR*(d-1);return`Frequency boost from ${d} generators (${g}): w-avg ${b.toFixed(2)} \xD7 ${S.toFixed(2)} \u2192 ${c.toFixed(2)}`}default:return`Aggregated from ${d} generators: ${c.toFixed(2)}`}}aggregateScores(t){let c=t.map(t=>t.card.score);switch(this.aggregationMode){case`max`:return Math.max(...c);case`average`:{let c=t.reduce((t,c)=>t+c.weight,0);return c===0?0:t.reduce((t,c)=>t+c.card.score*c.weight,0)/c}case`frequencyBoost`:{let c=t.reduce((t,c)=>t+c.weight,0),u=t.reduce((t,c)=>t+c.card.score*c.weight,0);return(c>0?u/c:0)*(1+FREQUENCY_BOOST_FACTOR*(t.length-1))}default:return c[0]}}}}}),elo_exports={},__export(elo_exports,{default:()=>ELONavigator}),init_elo=__esm({"src/core/navigators/generators/elo.ts"(){"use strict";init_navigators(),init_logger(),ELONavigator=class extends ContentNavigator{constructor(t,c,u){super(t,c,u),_defineProperty$2(this,`name`,void 0),this.name=u?.name||`ELO`}async getWeightedCards(t,c){let u;u=c?.userElo===void 0?toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo).global.score:c.userElo;let d=await this.user.getActiveCards(),m=(await this.course.getCardsCenteredAtELO({limit:t,elo:`user`},t=>!d.some(c=>t.cardID===c.cardID))).map(t=>({...t,status:`new`})).map(t=>{let c=t.elo??1e3,d=Math.abs(c-u),m=Math.max(0,1-d/500),g=m>0?Math.random()**(1/m):0;return{cardId:t.cardID,courseId:t.courseID,score:g,provenance:[{strategy:`elo`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-ELO-default`,action:`generated`,score:g,reason:`ELO distance ${Math.round(d)} (card: ${Math.round(c)}, user: ${Math.round(u)}), raw ${m.toFixed(3)}, key ${g.toFixed(3)}`}]}});m.sort((t,c)=>c.score-t.score);let g=m.slice(0,t);if(g.length>0){let t=g.slice(0,3).map(t=>t.score.toFixed(2)).join(`, `);logger.info(`[ELO] Course ${this.course.getCourseID()}: ${g.length} new cards (top scores: ${t})`)}else logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);return{cards:g}}}}}),generators_exports={},init_generators=__esm({"src/core/navigators/generators/index.ts"(){"use strict";}}),prescribed_exports={},__export(prescribed_exports,{default:()=>PrescribedCardsGenerator}),init_prescribed=__esm({"src/core/navigators/generators/prescribed.ts"(){"use strict";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(t,c,u){super(t,c,u),_defineProperty$2(this,`name`,void 0),_defineProperty$2(this,`config`,void 0),this.name=u.name||`Prescribed Cards`,this.config=this.parseConfig(u.serializedData),logger.debug(`[Prescribed] Initialized with ${this.config.groups.length} groups and ${this.config.groups.reduce((t,c)=>t+c.targetCardIds.length,0)} targets`)}get strategyKey(){return`PrescribedProgress`}async getWeightedCards(t,c){if(this.config.groups.length===0||t<=0)return{cards:[]};let u=this.course.getCourseID(),d=await this.user.getActiveCards(),m=new Set(d.map(t=>t.cardID)),g=await this.user.getSeenCards(u).catch(()=>[]),b=new Set(g),S=await this.getStrategyState()??{updatedAt:isoNow(),groups:{}},C=await this.loadHierarchyConfigs(),w=await this.user.getCourseRegDoc(u).catch(()=>null),T=typeof w?.elo==`number`?w.elo:w?.elo?.global?.score??c?.userElo??1e3,E=typeof w?.elo==`number`?{}:w?.elo?.tags??{},D=dedupe(this.config.groups.flatMap(t=>t.targetCardIds)),O=dedupe(this.config.groups.flatMap(t=>t.supportCardIds??[])),Or=dedupe([...D,...O]),kr=Or.length>0?await this.course.getAppliedTagsBatch(Or):new Map,Ar=await this.course.getCourseTagStubs().catch(()=>({rows:[],offset:0,total_rows:0})),jr=new Map;for(let t of Ar.rows??[]){let c=t.doc;c?.name&&Array.isArray(c.taggedCards)&&jr.set(c.name,[...c.taggedCards])}let Mr={updatedAt:isoNow(),groups:{}},Nr=[],Pr=new Set,Fr=[];for(let t of this.config.groups){let c=this.buildGroupRuntimeState({group:t,priorState:S.groups[t.id],activeIds:m,seenIds:b,tagsByCard:kr,cardsByTag:jr,hierarchyConfigs:C,userTagElo:E,userGlobalElo:T});Fr.push(c),logger.info(`[Prescribed] Group '${t.id}': ${t.targetCardIds.length} targets total, ${c.encounteredTargets.size} encountered, ${c.pendingTargets.length} pending (${c.surfaceableTargets.length} surfaceable, ${c.blockedTargets.length} blocked), ${c.supportCandidates.length} authored support candidates, ${c.discoveredSupportCandidates.length} discovered support candidates, pressure=${c.pressureMultiplier.toFixed(2)}`),c.blockedTargets.length>0&&(logger.info(`[Prescribed] Group '${t.id}' blocked targets: ${c.blockedTargets.join(`, `)}`),logger.info(`[Prescribed] Group '${t.id}' support tags needed: ${c.supportTags.join(`, `)||`(none)`}`),logger.info(`[Prescribed] Group '${t.id}' escalation mode: `+(c.supportCandidates.length>0?`direct-support`:c.discoveredSupportCandidates.length>0?`inserted-support-candidates`:`boost-only`)),c.discoveredSupportCandidates.length>0&&logger.info(`[Prescribed] Group '${t.id}' discovered support candidates: ${c.discoveredSupportCandidates.join(`, `)}`)),Mr.groups[t.id]=this.buildNextGroupState(c,S.groups[t.id]);let d=this.buildDirectTargetCards(c,u,Pr),g=this.buildSupportCards(c,u,Pr),w=this.buildDiscoveredSupportCards(c,u,Pr);Nr.push(...d,...g,...w)}let Ir=this.buildSupportHintSummary(Fr),Lr=Object.keys(Ir.boostTags).length>0?{boostTags:Ir.boostTags,_label:`prescribed-support (${Ir.supportTags.length} tags; blocked=${Ir.blockedTargetIds.length}; testversion=${PRESCRIBED_DEBUG_VERSION})`}:void 0;if(Lr){let t=Object.entries(Lr.boostTags??{});logger.info(`[Prescribed] Emitting ${t.length} boost hint(s): `+t.map(([t,c])=>`${t}\xD7${c.toFixed(1)}`).join(`, `))}else logger.info(`[Prescribed] No hints to emit (no blocked targets or no support tags)`);if(Nr.length===0)return logger.info(`[Prescribed] 0 cards emitted (all targets blocked, authored/discovered support candidates exhausted)`+(Lr?` — boost hints emitted but may not survive filters`:``)),await this.putStrategyState(Mr).catch(t=>{logger.debug(`[Prescribed] Failed to persist empty-state update: ${t}`)}),Lr?{cards:[],hints:Lr}:{cards:[]};let Rr=pickTopByScore(Nr,t),zr=new Map;for(let t of Rr){let c=t.provenance[0],u=c?.reason.match(/group=([^;]+)/)?.[1],d=c?.reason.includes(`mode=support`)?`supportIds`:`targetIds`;u&&(zr.has(u)||zr.set(u,{targetIds:[],supportIds:[]}),zr.get(u)[d].push(t.cardId))}for(let t of this.config.groups){let c=Mr.groups[t.id],u=zr.get(t.id);u&&(u.targetIds.length>0||u.supportIds.length>0)&&(c.lastSurfacedAt=isoNow(),c.sessionsSinceSurfaced=0,u.supportIds.length>0&&(c.lastSupportAt=isoNow()))}return await this.putStrategyState(Mr).catch(t=>{logger.debug(`[Prescribed] Failed to persist prescribed progress: ${t}`)}),logger.info(`[Prescribed] Emitting ${Rr.length} cards (${Rr.filter(t=>t.provenance[0]?.reason.includes(`mode=target`)).length} target, ${Rr.filter(t=>t.provenance[0]?.reason.includes(`mode=support`)).length} support, ${Rr.filter(t=>t.provenance[0]?.reason.includes(`mode=discovered-support`)).length} discovered support)`),Lr?{cards:Rr,hints:Lr}:{cards:Rr}}buildSupportHintSummary(t){let c={},u=new Set,d=new Set;for(let m of t)if(!(m.blockedTargets.length===0||m.supportTags.length===0)){m.blockedTargets.forEach(t=>u.add(t));for(let t of m.supportTags)d.add(t),c[t]=(c[t]??1)*m.supportMultiplier}return{boostTags:c,blockedTargetIds:[...u].sort(),supportTags:[...d].sort()}}parseConfig(t){try{let c=JSON.parse(t);return{groups:(Array.isArray(c.groups)?c.groups:[]).map((t,c)=>({id:typeof t.id==`string`&&t.id.trim().length>0?t.id:`group-${c+1}`,targetCardIds:dedupe(Array.isArray(t.targetCardIds)?t.targetCardIds.filter(t=>typeof t==`string`):[]),supportCardIds:dedupe(Array.isArray(t.supportCardIds)?t.supportCardIds.filter(t=>typeof t==`string`):[]),supportTagPatterns:dedupe(Array.isArray(t.supportTagPatterns)?t.supportTagPatterns.filter(t=>typeof t==`string`):[]),freshnessWindowSessions:typeof t.freshnessWindowSessions==`number`?t.freshnessWindowSessions:DEFAULT_FRESHNESS_WINDOW,maxDirectTargetsPerRun:typeof t.maxDirectTargetsPerRun==`number`?t.maxDirectTargetsPerRun:DEFAULT_MAX_DIRECT_PER_RUN,maxSupportCardsPerRun:typeof t.maxSupportCardsPerRun==`number`?t.maxSupportCardsPerRun:DEFAULT_MAX_SUPPORT_PER_RUN,hierarchyWalk:{enabled:t.hierarchyWalk?.enabled!==!1,maxDepth:typeof t.hierarchyWalk?.maxDepth==`number`?t.hierarchyWalk.maxDepth:DEFAULT_HIERARCHY_DEPTH},retireOnEncounter:t.retireOnEncounter!==!1})).filter(t=>t.targetCardIds.length>0)}}catch{return{groups:[]}}}async loadHierarchyConfigs(){try{return(await this.course.getAllNavigationStrategies()).filter(t=>t.implementingClass===`hierarchyDefinition`).map(t=>{try{return{prerequisites:JSON.parse(t.serializedData).prerequisites||{}}}catch{return{prerequisites:{}}}})}catch(t){return logger.debug(`[Prescribed] Failed to load hierarchy configs: ${t}`),[]}}buildGroupRuntimeState(t){let{group:c,priorState:u,activeIds:d,seenIds:m,tagsByCard:g,cardsByTag:b,hierarchyConfigs:S,userTagElo:C,userGlobalElo:w}=t,T=new Set;for(let t of c.targetCardIds)(d.has(t)||m.has(t))&&T.add(t);if(u?.encounteredCardIds?.length)for(let t of u.encounteredCardIds)T.add(t);let E=c.targetCardIds.filter(t=>!T.has(t)),D=new Map;for(let t of E)D.set(t,g.get(t)??[]);let O=[],Or=[],kr=new Set;for(let t of E){let u=D.get(t)??[],d=this.resolveBlockedSupportTags(u,S,C,w,c.hierarchyWalk?.enabled!==!1,c.hierarchyWalk?.maxDepth??DEFAULT_HIERARCHY_DEPTH),m=u.filter(t=>t.startsWith(`gpc:intro:`)),g=new Set(u.filter(t=>t.startsWith(`gpc:expose:`)));for(let t of m){let c=t.slice(10);c&&g.add(`gpc:expose:${c}`)}let b=[...g].filter(t=>{let c=C[t];return!c||c.count<DEFAULT_MIN_COUNT});b.length>0&&b.forEach(t=>kr.add(t)),d.blocked||b.length>0?(O.push(t),d.supportTags.forEach(t=>kr.add(t))):Or.push(t)}let Ar=dedupe([...c.supportCardIds??[],...this.findSupportCardsByTags(c,g,[...kr])]).filter(t=>!d.has(t)&&!m.has(t)),jr=O.length>0&&kr.size>0&&Ar.length===0?this.findDiscoveredSupportCards({supportTags:[...kr],cardsByTag:b,activeIds:d,seenIds:m,excludedIds:new Set([...c.targetCardIds,...c.supportCardIds??[]]),limit:c.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN}):[];O.length>0&&kr.size>0&&jr.length===0&&logger.info(`[Prescribed] Group '${c.id}' discovered 0 broader support candidates (blocked=${O.length}; authoredSupport=${Ar.length})`);let Mr=u?.sessionsSinceSurfaced??0,Nr=c.freshnessWindowSessions??DEFAULT_FRESHNESS_WINDOW,Pr=Math.max(0,Mr-Nr),Fr=E.length===0?1:clamp(1+Pr*.75+Math.min(2,E.length*.1),1,MAX_TARGET_MULTIPLIER),Ir=O.length===0?1:clamp(1+Pr*.5+Math.min(1.5,O.length*.15),1,MAX_SUPPORT_MULTIPLIER);return{group:c,encounteredTargets:T,pendingTargets:E,blockedTargets:O,surfaceableTargets:Or,targetTags:D,supportCandidates:Ar,discoveredSupportCandidates:jr,supportTags:[...kr],pressureMultiplier:Fr,supportMultiplier:Ir,debugVersion:PRESCRIBED_DEBUG_VERSION}}buildNextGroupState(t,c){let u=c?.sessionsSinceSurfaced??0,d=!1;return{encounteredCardIds:[...t.encounteredTargets].sort(),pendingTargetIds:[...t.pendingTargets].sort(),lastSurfacedAt:c?.lastSurfacedAt??null,sessionsSinceSurfaced:u+1,lastSupportAt:c?.lastSupportAt??null,blockedTargetIds:[...t.blockedTargets].sort(),lastResolvedSupportTags:[...t.supportTags].sort()}}buildDirectTargetCards(t,c,u){let d=t.group.maxDirectTargetsPerRun??DEFAULT_MAX_DIRECT_PER_RUN,m=t.surfaceableTargets.filter(t=>!u.has(t)).slice(0,d),g=[];for(let d of m)u.add(d),g.push({cardId:d,courseId:c,score:BASE_TARGET_SCORE*t.pressureMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:BASE_TARGET_SCORE*t.pressureMultiplier,reason:`mode=target;group=${t.group.id};pending=${t.pendingTargets.length};surfaceable=${t.surfaceableTargets.length};blocked=${t.blockedTargets.length};blockedTargets=${t.blockedTargets.join(`|`)||`none`};supportTags=${t.supportTags.join(`|`)||`none`};multiplier=${t.pressureMultiplier.toFixed(2)};testversion=${t.debugVersion}`}]});return g}buildSupportCards(t,c,u){if(t.blockedTargets.length===0||t.supportCandidates.length===0)return[];let d=t.group.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN,m=t.supportCandidates.filter(t=>!u.has(t)).slice(0,d),g=[];for(let d of m)u.add(d),g.push({cardId:d,courseId:c,score:BASE_SUPPORT_SCORE*t.supportMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:BASE_SUPPORT_SCORE*t.supportMultiplier,reason:`mode=support;group=${t.group.id};pending=${t.pendingTargets.length};blocked=${t.blockedTargets.length};blockedTargets=${t.blockedTargets.join(`|`)||`none`};supportCard=${d};supportTags=${t.supportTags.join(`|`)||`none`};multiplier=${t.supportMultiplier.toFixed(2)};testversion=${t.debugVersion}`}]});return g}buildDiscoveredSupportCards(t,c,u){if(t.blockedTargets.length===0||t.discoveredSupportCandidates.length===0)return[];let d=t.group.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN,m=t.discoveredSupportCandidates.filter(t=>!u.has(t)).slice(0,d),g=[];for(let d of m)u.add(d),g.push({cardId:d,courseId:c,score:DISCOVERED_SUPPORT_SCORE*t.supportMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:DISCOVERED_SUPPORT_SCORE*t.supportMultiplier,reason:`mode=discovered-support;group=${t.group.id};pending=${t.pendingTargets.length};blocked=${t.blockedTargets.length};blockedTargets=${t.blockedTargets.join(`|`)||`none`};supportCard=${d};supportTags=${t.supportTags.join(`|`)||`none`};multiplier=${t.supportMultiplier.toFixed(2)};testversion=${t.debugVersion}`}]});return g}findSupportCardsByTags(t,c,u){if(u.length===0)return[];let d=t.supportCardIds??[],m=t.supportTagPatterns??[];if(d.length===0&&m.length===0)return[];let g=new Set;for(let t of d){let d=c.get(t)??[],b=u.some(t=>d.includes(t)),S=m.some(t=>d.some(c=>matchesTagPattern(c,t)));(b||S)&&g.add(t)}return[...g]}findDiscoveredSupportCards(t){let{supportTags:c,cardsByTag:u,activeIds:d,seenIds:m,excludedIds:g,limit:b}=t,S=new Map;for(let t of c){let c=u.get(t)??[];for(let t of c){if(d.has(t)||m.has(t)||g.has(t))continue;let c=S.get(t);c?c.matches+=1:S.set(t,{cardId:t,matches:1})}}let C=[...S.values()].sort((t,c)=>c.matches-t.matches||t.cardId.localeCompare(c.cardId)),w=new Set,T=[],E=[];for(let t of C){let c=extractWordStem(t.cardId);w.has(c)?E.push(t):(w.add(c),T.push(t))}return shuffleInPlace(T),shuffleInPlace(E),[...T,...E].slice(0,b).map(t=>t.cardId)}resolveBlockedSupportTags(t,c,u,d,m,g){let b=new Set,S=!1;for(let C of t){let t=c.map(t=>t.prerequisites[C]).filter(t=>Array.isArray(t)&&t.length>0);if(t.length!==0&&t.some(t=>t.some(t=>!this.isPrerequisiteMet(t,u[t.tag],d)))){if(S=!0,!m){for(let c of t)for(let t of c)this.isPrerequisiteMet(t,u[t.tag],d)||b.add(t.tag);continue}for(let m of t)for(let t of m)this.isPrerequisiteMet(t,u[t.tag],d)||this.collectSupportTagsRecursive(t.tag,c,u,d,g,new Set,b)}}return{blocked:S,supportTags:[...b]}}collectSupportTagsRecursive(t,c,u,d,m,g,b){if(m<0||g.has(t))return;g.add(t);let S=!1;for(let C of c){let w=C.prerequisites[t];if(!w||w.length===0)continue;let T=w.filter(t=>!this.isPrerequisiteMet(t,u[t.tag],d));if(T.length>0&&m>0){S=!0;for(let t of T)this.collectSupportTagsRecursive(t.tag,c,u,d,m-1,g,b)}}S||b.add(t)}isPrerequisiteMet(t,c,u){if(!c)return!1;let d=t.masteryThreshold?.minCount??DEFAULT_MIN_COUNT;return c.count<d?!1:t.masteryThreshold?.minElo===void 0?t.masteryThreshold?.minCount===void 0?c.score>=u:!0:c.score>=t.masteryThreshold.minElo}}}}),srs_exports={},__export(srs_exports,{default:()=>SRSNavigator}),init_srs=__esm({"src/core/navigators/generators/srs.ts"(){"use strict";init_navigators(),init_logger(),DEFAULT_HEALTHY_BACKLOG=20,MAX_BACKLOG_PRESSURE=.5,SRSNavigator=class extends ContentNavigator{constructor(t,c,u){super(t,c,u),_defineProperty$2(this,`name`,void 0),_defineProperty$2(this,`healthyBacklog`,void 0),this.name=u?.name||`SRS`,this.healthyBacklog=this.parseConfig(u?.serializedData).healthyBacklog??DEFAULT_HEALTHY_BACKLOG}parseConfig(t){if(!t)return{};try{return JSON.parse(t)}catch{return logger.warn(`[SRS] Failed to parse strategy config, using defaults`),{}}}async getWeightedCards(t,c){if(!this.user||!this.course)throw Error(`SRSNavigator requires user and course to be set`);let u=this.course.getCourseID(),d=await this.user.getPendingReviews(u),m=hooks.utc(),g=d.filter(t=>m.isAfter(hooks.utc(t.reviewTime)));if(g.length>0){let t=[...new Set(g.map(t=>t.cardId))],c=await this.course.getAppliedTagsBatch(t),u=[];if(g=g.filter(t=>(c.get(t.cardId)??[]).includes(`srs:skip`)?(u.push(t._id),!1):!0),u.length>0){logger.info(`[SRS] Removing ${u.length} scheduled reviews for srs:skip cards`);for(let t of u)this.user.removeScheduledCardReview(t)}}let b=this.computeBacklogPressure(g.length);if(g.length>0){let t=b>0?` [backlog pressure: +${b.toFixed(2)}]`:` [healthy backlog]`;logger.info(`[SRS] Course ${u}: ${g.length} reviews due now (of ${d.length} scheduled)${t}`)}else if(d.length>0){let t=[...d].sort((t,c)=>hooks.utc(t.reviewTime).diff(hooks.utc(c.reviewTime)))[0],c=hooks.utc(t.reviewTime),g=hooks.duration(c.diff(m)),b=g.asHours()<1?`${Math.round(g.asMinutes())}m`:g.asHours()<24?`${Math.round(g.asHours())}h`:`${Math.round(g.asDays())}d`;logger.info(`[SRS] Course ${u}: 0 reviews due now (${d.length} scheduled, next in ${b})`)}else logger.info(`[SRS] Course ${u}: No reviews scheduled`);return{cards:g.map(t=>{let{score:c,reason:u}=this.computeUrgencyScore(t,m,b);return{cardId:t.cardId,courseId:t.courseId,score:c,reviewID:t._id,provenance:[{strategy:`srs`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-SRS-default`,action:`generated`,score:c,reason:u}]}}).sort((t,c)=>c.score-t.score).slice(0,t)}}computeBacklogPressure(t){if(t<=this.healthyBacklog)return 0;let c=(t-this.healthyBacklog)/this.healthyBacklog*(MAX_BACKLOG_PRESSURE/2);return Math.min(MAX_BACKLOG_PRESSURE,c)}computeUrgencyScore(t,c,u){let d=hooks.utc(t.scheduledAt),m=hooks.utc(t.reviewTime),g=Math.max(1,m.diff(d,`hours`)),b=c.diff(m,`hours`),S=b/g,C=.3+.7*Math.exp(-g/720),w=.5+(Math.min(1,Math.max(0,S))*.5+C*.5)*.45,T=Math.min(1,w+u),E=[`${Math.round(b)}h overdue`,`interval: ${Math.round(g)}h`,`relative: ${S.toFixed(2)}`,`recency: ${C.toFixed(2)}`];return u>0&&E.push(`backlog: +${u.toFixed(2)}`),E.push(`review`),{score:T,reason:E.join(`, `)}}}}}),types_exports={},init_types=__esm({"src/core/navigators/generators/types.ts"(){"use strict";}}),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))})}}),init_contentNavigationStrategy=__esm({"src/core/types/contentNavigationStrategy.ts"(){"use strict";DEFAULT_LEARNABLE_WEIGHT={weight:1,confidence:.1,sampleSize:0}}}),WeightedFilter_exports={},__export(WeightedFilter_exports,{WeightedFilter:()=>WeightedFilter}),init_WeightedFilter=__esm({"src/core/navigators/filters/WeightedFilter.ts"(){"use strict";init_contentNavigationStrategy(),WeightedFilter=class{constructor(t,c=DEFAULT_LEARNABLE_WEIGHT,u=!1,d){_defineProperty$2(this,`name`,void 0),_defineProperty$2(this,`inner`,void 0),_defineProperty$2(this,`learnable`,void 0),_defineProperty$2(this,`staticWeight`,void 0),_defineProperty$2(this,`strategyId`,void 0),this.inner=t,this.name=t.name,this.learnable=c,this.staticWeight=u,this.strategyId=d}async transform(t,c){let u=this.learnable.weight,d;if(!this.staticWeight&&c.orchestration){let t=this.strategyId||this.inner.strategyId||this.name;u=c.orchestration.getEffectiveWeight(t,this.learnable),d=c.orchestration.getDeviation(t)}if(Math.abs(u-1)<.001)return this.inner.transform(t,c);let m=new Map;for(let c of t)m.set(c.cardId,c.score);return(await this.inner.transform(t,c)).map(t=>{let c=m.get(t.cardId);if(c===void 0||c===0||t.score===0)return t;let g=t.score/c;if(Math.abs(g-1)<1e-4)return t;let b=c*g**+u,S=t.provenance.length-1,C=t.provenance[S];if(C){let c=[...t.provenance];return c[S]={...C,score:b,effectiveWeight:u,deviation:d},{...t,score:b,provenance:c}}return{...t,score:b}})}}}}),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}),init_eloDistance=__esm({"src/core/navigators/filters/eloDistance.ts"(){"use strict";DEFAULT_HALF_LIFE=200,DEFAULT_MIN_MULTIPLIER=.3,DEFAULT_MAX_MULTIPLIER=1}}),hierarchyDefinition_exports={},__export(hierarchyDefinition_exports,{default:()=>HierarchyDefinitionNavigator}),init_hierarchyDefinition=__esm({"src/core/navigators/filters/hierarchyDefinition.ts"(){"use strict";init_navigators(),init_logger(),DEFAULT_MIN_COUNT2=3,HierarchyDefinitionNavigator=class extends ContentNavigator{constructor(t,c,u){super(t,c,u),_defineProperty$2(this,`config`,void 0),_defineProperty$2(this,`name`,void 0),this.config=this.parseConfig(u.serializedData),this.name=u.name||`Hierarchy Definition`}parseConfig(t){try{return{prerequisites:JSON.parse(t).prerequisites||{}}}catch{return{prerequisites:{}}}}isPrerequisiteMet(t,c,u){if(!c)return!1;let d=t.masteryThreshold?.minCount??DEFAULT_MIN_COUNT2;return c.count<d?!1:t.masteryThreshold?.minElo===void 0?t.masteryThreshold?.minCount===void 0?c.score>=u:!0:c.score>=t.masteryThreshold.minElo}async getMasteredTags(t){let c=new Set;try{let u=toCourseElo((await t.user.getCourseRegDoc(t.course.getCourseID())).elo);for(let t of Object.values(this.config.prerequisites))for(let d of t){let t=u.tags[d.tag];this.isPrerequisiteMet(d,t,u.global.score)&&c.add(d.tag)}}catch{}return c}getUnlockedTags(t){let c=new Set;for(let[u,d]of Object.entries(this.config.prerequisites))d.every(c=>t.has(c.tag))&&c.add(u);return c}hasPrerequisites(t){return t in this.config.prerequisites}async checkCardUnlock(t,c,u,d){try{let c=t.tags??[],m=c.filter(t=>this.hasPrerequisites(t)&&!u.has(t));return m.length===0?{isUnlocked:!0,reason:`Prerequisites met, tags: ${c.length>0?c.join(`, `):`none`}`}:{isUnlocked:!1,reason:`Blocked: missing prerequisites ${m.flatMap(t=>(this.config.prerequisites[t]||[]).filter(t=>!d.has(t.tag)).map(t=>t.tag)).join(`, `)} for tags ${m.join(`, `)}`}}catch{return{isUnlocked:!0,reason:`Prerequisites check skipped (tag lookup failed)`}}}getPreReqBoosts(t,c){let u=new Map;for(let[d,m]of Object.entries(this.config.prerequisites))if(!t.has(d))for(let t of m){if(!t.preReqBoost||t.preReqBoost<=1||c.has(t.tag))continue;let d=u.get(t.tag)??1;u.set(t.tag,Math.max(d,t.preReqBoost))}return u}getTargetBoosts(t){let c=new Map,u=Object.keys(this.config.prerequisites),d=[...t];logger.info(`[HierarchyDefinition:targetBoost:trace] ${this.name} | configKeys=${u.length}, unlocked=${d.length} (${d.slice(0,5).join(`, `)}${d.length>5?`...`:``})`);for(let[u,d]of Object.entries(this.config.prerequisites))if(t.has(u)){logger.info(`[HierarchyDefinition:targetBoost:trace] UNLOCKED ${u}: ${d.length} prereqs, raw=${JSON.stringify(d.map(t=>({tag:t.tag,tb:t.targetBoost})))}`);for(let t of d){if(!t.targetBoost||t.targetBoost<=1)continue;let d=c.get(u)??1;c.set(u,Math.max(d,t.targetBoost))}}return c.size>0?logger.info(`[HierarchyDefinition] targetBoosts active: ${[...c.entries()].map(([t,c])=>`${t}=\xD7${c}`).join(`, `)}`):logger.info(`[HierarchyDefinition:targetBoost:trace] no targetBoosts found despite ${d.length} unlocked tags`),c}async transform(t,c){let u=await this.getMasteredTags(c),d=this.getUnlockedTags(u),m=this.getPreReqBoosts(d,u),g=this.getTargetBoosts(d),b=[];for(let S of t){let{isUnlocked:t,reason:C}=await this.checkCardUnlock(S,c.course,d,u),w=t?S.score:S.score*.02,T=t?`passed`:`penalized`,E=C;if(t&&m.size>0){let t=S.tags??[],c=1,u=[];for(let d of t){let t=m.get(d);t&&t>c&&(c=t,u.push(d))}c>1&&(w*=c,T=`boosted`,E=`${C} | preReqBoost \xD7${c.toFixed(2)} for ${u.join(`, `)}`,logger.info(`[HierarchyDefinition] preReqBoost \xD7${c.toFixed(2)} applied to card ${S.cardId} via tags [${u.join(`, `)}] (score: ${S.score.toFixed(3)} \u2192 ${w.toFixed(3)})`))}if(t&&g.size>0){let t=S.tags??[],c=1,u=[];for(let d of t){let t=g.get(d);t&&t>c&&(c=t,u.push(d))}c>1&&(w*=c,T=`boosted`,E=`${E} | targetBoost \xD7${c.toFixed(2)} for ${u.join(`, `)}`,logger.info(`[HierarchyDefinition] targetBoost \xD7${c.toFixed(2)} applied to card ${S.cardId} via tags [${u.join(`, `)}] (score: ${S.score.toFixed(3)} \u2192 ${w.toFixed(3)})`))}b.push({...S,score:w,provenance:[...S.provenance,{strategy:`hierarchyDefinition`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-hierarchy`,action:T,score:w,reason:E}]})}return b}async getWeightedCards(t){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}),init_userTagPreference=__esm({"src/core/navigators/filters/userTagPreference.ts"(){"use strict";init_navigators(),UserTagPreferenceFilter=class extends ContentNavigator{constructor(t,c,u){super(t,c,u),_defineProperty$2(this,`_strategyData`,void 0),_defineProperty$2(this,`name`,void 0),this._strategyData=u,this.name=u.name||`User Tag Preferences`}computeMultiplier(t,c){let u=t.map(t=>c[t]).filter(t=>t!==void 0);return u.length===0?1:Math.max(...u)}buildReason(t,c,u){let d=t.filter(t=>c[t]===u);return u===0?`Excluded by user preference: ${d.join(`, `)} (${u}x)`:u<1?`Penalized by user preference: ${d.join(`, `)} (${u.toFixed(2)}x)`:u>1?`Boosted by user preference: ${d.join(`, `)} (${u.toFixed(2)}x)`:`No matching user preferences`}async transform(t,c){let u=await this.getStrategyState();return!u||Object.keys(u.boost).length===0?t.map(t=>({...t,provenance:[...t.provenance,{strategy:`userTagPreference`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||this._strategyData._id,action:`passed`,score:t.score,reason:`No user tag preferences configured`}]})):await Promise.all(t.map(async t=>{let c=t.tags??[],d=this.computeMultiplier(c,u.boost),m=Math.min(1,t.score*d),g;return g=d===0||d<1?`penalized`:d>1?`boosted`:`passed`,{...t,score:m,provenance:[...t.provenance,{strategy:`userTagPreference`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||this._strategyData._id,action:g,score:m,reason:this.buildReason(c,u.boost,d)}]}}))}async getWeightedCards(t){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}),init_filters=__esm({"src/core/navigators/filters/index.ts"(){"use strict";init_eloDistance(),init_userTagPreference()}}),inferredPreferenceStub_exports={},__export(inferredPreferenceStub_exports,{INFERRED_PREFERENCE_NAVIGATOR_STUB:()=>INFERRED_PREFERENCE_NAVIGATOR_STUB}),init_inferredPreferenceStub=__esm({"src/core/navigators/filters/inferredPreferenceStub.ts"(){"use strict";INFERRED_PREFERENCE_NAVIGATOR_STUB=!0}}),interferenceMitigator_exports={},__export(interferenceMitigator_exports,{default:()=>InterferenceMitigatorNavigator}),init_interferenceMitigator=__esm({"src/core/navigators/filters/interferenceMitigator.ts"(){"use strict";init_navigators(),DEFAULT_MIN_COUNT3=10,DEFAULT_MIN_ELAPSED_DAYS=3,DEFAULT_INTERFERENCE_DECAY=.8,InterferenceMitigatorNavigator=class extends ContentNavigator{constructor(t,c,u){super(t,c,u),_defineProperty$2(this,`config`,void 0),_defineProperty$2(this,`name`,void 0),_defineProperty$2(this,`interferenceMap`,void 0),this.config=this.parseConfig(u.serializedData),this.interferenceMap=this.buildInterferenceMap(),this.name=u.name||`Interference Mitigator`}parseConfig(t){try{let c=JSON.parse(t),u=c.interferenceSets||[];return u.length>0&&Array.isArray(u[0])&&(u=u.map(t=>({tags:t}))),{interferenceSets:u,maturityThreshold:{minCount:c.maturityThreshold?.minCount??DEFAULT_MIN_COUNT3,minElo:c.maturityThreshold?.minElo,minElapsedDays:c.maturityThreshold?.minElapsedDays??DEFAULT_MIN_ELAPSED_DAYS},defaultDecay:c.defaultDecay??DEFAULT_INTERFERENCE_DECAY}}catch{return{interferenceSets:[],maturityThreshold:{minCount:DEFAULT_MIN_COUNT3,minElapsedDays:DEFAULT_MIN_ELAPSED_DAYS},defaultDecay:DEFAULT_INTERFERENCE_DECAY}}}buildInterferenceMap(){let t=new Map;for(let c of this.config.interferenceSets){let u=c.decay??this.config.defaultDecay??DEFAULT_INTERFERENCE_DECAY;for(let d of c.tags){t.has(d)||t.set(d,[]);let m=t.get(d);for(let t of c.tags)if(t!==d){let c=m.find(c=>c.partner===t);c?c.decay=Math.max(c.decay,u):m.push({partner:t,decay:u})}}}return t}async getImmatureTags(t){let c=new Set;try{let u=toCourseElo((await t.user.getCourseRegDoc(t.course.getCourseID())).elo),d=this.config.maturityThreshold?.minCount??DEFAULT_MIN_COUNT3,m=this.config.maturityThreshold?.minElo,g=(this.config.maturityThreshold?.minElapsedDays??DEFAULT_MIN_ELAPSED_DAYS)*2;for(let[t,b]of Object.entries(u.tags)){if(b.count===0)continue;let u=b.count<d,S=m!==void 0&&b.score<m,C=b.count<g;(u||S||C)&&c.add(t)}}catch{}return c}getTagsToAvoid(t){let c=new Map;for(let u of t){let d=this.interferenceMap.get(u);if(d){for(let{partner:u,decay:m}of d)if(!t.has(u)){let t=c.get(u)??0;c.set(u,Math.max(t,m))}}}return c}computeInterferenceEffect(t,c,u){if(c.size===0)return{multiplier:1,interferingTags:[],reason:`No interference detected`};let d=1,m=[];for(let u of t){let t=c.get(u);t!==void 0&&(m.push(u),d*=1-t)}if(m.length===0)return{multiplier:1,interferingTags:[],reason:`No interference detected`};let g=new Set;for(let t of m)for(let c of u)this.interferenceMap.get(c)?.some(c=>c.partner===t)&&g.add(c);let b=`Interferes with immature tags ${Array.from(g).join(`, `)} (tags: ${m.join(`, `)}, multiplier: ${d.toFixed(2)})`;return{multiplier:d,interferingTags:m,reason:b}}async transform(t,c){let u=await this.getImmatureTags(c),d=this.getTagsToAvoid(u),m=[];for(let c of t){let t=c.tags??[],{multiplier:g,reason:b}=this.computeInterferenceEffect(t,d,u),S=c.score*g,C=g<1?`penalized`:g>1?`boosted`:`passed`;m.push({...c,score:S,provenance:[...c.provenance,{strategy:`interferenceMitigator`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-interference`,action:C,score:S,reason:b}]})}return m}async getWeightedCards(t){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}),init_relativePriority=__esm({"src/core/navigators/filters/relativePriority.ts"(){"use strict";init_navigators(),DEFAULT_PRIORITY=.5,DEFAULT_PRIORITY_INFLUENCE=.5,DEFAULT_COMBINE_MODE=`max`,RelativePriorityNavigator=class extends ContentNavigator{constructor(t,c,u){super(t,c,u),_defineProperty$2(this,`config`,void 0),_defineProperty$2(this,`name`,void 0),this.config=this.parseConfig(u.serializedData),this.name=u.name||`Relative Priority`}parseConfig(t){try{let c=JSON.parse(t);return{tagPriorities:c.tagPriorities||{},defaultPriority:c.defaultPriority??DEFAULT_PRIORITY,combineMode:c.combineMode??DEFAULT_COMBINE_MODE,priorityInfluence:c.priorityInfluence??DEFAULT_PRIORITY_INFLUENCE}}catch{return{tagPriorities:{},defaultPriority:DEFAULT_PRIORITY,combineMode:DEFAULT_COMBINE_MODE,priorityInfluence:DEFAULT_PRIORITY_INFLUENCE}}}getTagPriority(t){return this.config.tagPriorities[t]??this.config.defaultPriority??DEFAULT_PRIORITY}computeCardPriority(t){if(t.length===0)return this.config.defaultPriority??DEFAULT_PRIORITY;let c=t.map(t=>this.getTagPriority(t));switch(this.config.combineMode){case`max`:return Math.max(...c);case`min`:return Math.min(...c);case`average`:return c.reduce((t,c)=>t+c,0)/c.length;default:return Math.max(...c)}}computeBoostFactor(t){let c=this.config.priorityInfluence??DEFAULT_PRIORITY_INFLUENCE;return 1+(t-.5)*c}buildPriorityReason(t,c,u,d){if(t.length===0)return`No tags, neutral priority (${c.toFixed(2)})`;let m=t.slice(0,3).join(`, `),g=t.length>3?` (+${t.length-3} more)`:``;return u===1?`Neutral priority (${c.toFixed(2)}) for tags: ${m}${g}`:u>1?`High-priority tags: ${m}${g} (priority ${c.toFixed(2)} \u2192 boost ${u.toFixed(2)}x \u2192 ${d.toFixed(2)})`:`Low-priority tags: ${m}${g} (priority ${c.toFixed(2)} \u2192 reduce ${u.toFixed(2)}x \u2192 ${d.toFixed(2)})`}async transform(t,c){return await Promise.all(t.map(async t=>{let c=t.tags??[],u=this.computeCardPriority(c),d=this.computeBoostFactor(u),m=Math.max(0,t.score*d),g=d>1?`boosted`:d<1?`penalized`:`passed`,b=this.buildPriorityReason(c,u,d,m);return{...t,score:m,provenance:[...t.provenance,{strategy:`relativePriority`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-priority`,action:g,score:m,reason:b}]}}))}async getWeightedCards(t){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"(){"use strict";}}),userGoalStub_exports={},__export(userGoalStub_exports,{USER_GOAL_NAVIGATOR_STUB:()=>USER_GOAL_NAVIGATOR_STUB}),init_userGoalStub=__esm({"src/core/navigators/filters/userGoalStub.ts"(){"use strict";USER_GOAL_NAVIGATOR_STUB=!0}}),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))})}}),init_gradient=__esm({"src/core/orchestration/gradient.ts"(){"use strict";init_logger()}}),init_learning=__esm({"src/core/orchestration/learning.ts"(){"use strict";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}}),init_signal=__esm({"src/core/orchestration/signal.ts"(){"use strict";}}),init_recording=__esm({"src/core/orchestration/recording.ts"(){"use strict";init_signal(),init_types_legacy(),init_logger()}}),init_orchestration=__esm({"src/core/orchestration/index.ts"(){"use strict";init_logger(),init_gradient(),init_learning(),init_signal(),init_recording(),MIN_SPREAD=.1,MAX_SPREAD=.5,MIN_WEIGHT=.1,MAX_WEIGHT=3}}),Pipeline_exports={},__export(Pipeline_exports,{Pipeline:()=>Pipeline,mergeHints:()=>mergeHints2}),init_Pipeline=__esm({"src/core/navigators/Pipeline.ts"(){"use strict";init_navigators(),init_logger(),init_orchestration(),init_PipelineDebugger(),VERBOSE_RESULTS=!0,Pipeline=class extends ContentNavigator{constructor(t,c,u,d){super(),_defineProperty$2(this,`generator`,void 0),_defineProperty$2(this,`filters`,void 0),_defineProperty$2(this,`_cachedOrchestration`,null),_defineProperty$2(this,`_tagCache`,new Map),_defineProperty$2(this,`_ephemeralHints`,null),this.generator=t,this.filters=c,this.user=u,this.course=d,d.getCourseConfig().then(t=>{logger.debug(`[pipeline] Crated pipeline for ${t.name}`)}).catch(t=>{logger.error(`[pipeline] Failed to lookup courseCfg: ${t}`)}),logPipelineConfig(t,c),registerPipelineForDebug(this)}setEphemeralHints(t){this._ephemeralHints=t,logger.info(`[Pipeline] Ephemeral hints set: ${JSON.stringify(t)}`)}async getWeightedCards(t){let c=performance.now(),u=await this.buildContext(),d=performance.now(),m=500;logger.debug(`[Pipeline] Fetching 500 candidates from generator '${this.generator.name}'`);let g=await this.generator.getWeightedCards(500,u),b=g.cards,S=performance.now(),C=b.length;this._ephemeralHints=mergeHints2([this._ephemeralHints,g.hints])??null;let w;if(this.generator.generators){let t=new Map;for(let c of b){let u=c.provenance[0];if(u){let d=u.strategyName;t.has(d)||t.set(d,{cards:[]}),t.get(d).cards.push(c)}}w=Array.from(t.entries()).map(([t,c])=>{let u=c.cards.filter(t=>t.provenance[0]?.reason?.includes(`new card`)),d=c.cards.filter(t=>t.provenance[0]?.reason?.includes(`review`));return{name:t,cardCount:c.cards.length,newCount:u.length,reviewCount:d.length,topScore:Math.max(...c.cards.map(t=>t.score),0)}})}logger.debug(`[Pipeline] Generator returned ${C} candidates`),b=await this.hydrateTags(b);let T=performance.now(),E=[...b],D=this._ephemeralHints;if(D?.requireCards?.length){let t=new Set(E.map(t=>t.cardId)),c=D.requireCards.filter(c=>!c.includes(`*`)&&!t.has(c));if(c.length>0){let t=await this.course.getAppliedTagsBatch(c),u=this.course.getCourseID();for(let d of c)E.push({cardId:d,courseId:u,score:1,tags:t.get(d)??[],provenance:[]});logger.info(`[Pipeline] Pre-fetched ${c.length} required card(s) into pool: ${c.join(`, `)}`)}}let O=new Set(b.filter(t=>t.provenance.some(t=>t.strategy===`prescribed`)).map(t=>t.cardId)),Or=[];for(let t of this.filters){let c=b.length,d=new Map(b.map(t=>[t.cardId,t.score]));b=await t.transform(b,u);let m=0,g=0,S=0,C=c-b.length;for(let t of b){let c=d.get(t.cardId)??0;t.score>c?m++:t.score<c?g++:S++}if(Or.push({name:t.name,boosted:m,penalized:g,passed:S,removed:C}),O.size>0){let c=new Set(b.map(t=>t.cardId)),u=[...O].filter(t=>!c.has(t)),d=b.filter(t=>O.has(t.cardId)&&t.score===0).map(t=>t.cardId);(u.length>0||d.length>0)&&(logger.info(`[Pipeline] Filter '${t.name}' impact on prescribed cards: `+(u.length>0?`removed=[${u.join(`, `)}] `:``)+(d.length>0?`zeroed=[${d.join(`, `)}]`:``)),u.forEach(t=>O.delete(t)))}logger.debug(`[Pipeline] Filter '${t.name}': ${d.size} \u2192 ${b.length} cards (\u2191${m} \u2193${g} =${S})`)}b=b.filter(t=>t.score>0);let kr=this._ephemeralHints;kr&&(this._ephemeralHints=null,b=this.applyHints(b,kr,E)),b.sort((t,c)=>c.score-t.score);let Ar=performance.now(),jr=b.slice(0,t);logger.info(`[Pipeline:timing] total=${(Ar-c).toFixed(0)}ms (context=${(d-c).toFixed(0)} generate=${(S-d).toFixed(0)} hydrate=${(T-S).toFixed(0)} filter=${(Ar-T).toFixed(0)})`);let Mr=jr.slice(0,3).map(t=>t.score);logExecutionSummary(this.generator.name,C,this.filters.length,jr.length,Mr,Or),logResultCards(jr),logCardProvenance(jr,3);try{let t=await this.course?.getCourseConfig().then(t=>t.name).catch(()=>void 0);captureRun(buildRunReport(this.course?.getCourseID()||`unknown`,t,this.generator.name,w,C,Or,b,jr,u.userElo,kr??void 0))}catch(t){logger.debug(`[Pipeline] Failed to capture debug run: ${t}`)}return{cards:jr}}async hydrateTags(t){if(t.length===0)return t;let c=[];for(let u of t)this._tagCache.has(u.cardId)||c.push(u.cardId);if(c.length>0){let t=await this.course.getAppliedTagsBatch(c);for(let[c,u]of t)this._tagCache.set(c,u)}let u=new Map;for(let c of t)u.set(c.cardId,this._tagCache.get(c.cardId)??[]);return logTagHydration(t,u),t.map(t=>({...t,tags:this._tagCache.get(t.cardId)??[]}))}applyHints(t,c,u){let d=t.length;if(c.excludeCards?.length&&(t=t.filter(t=>!c.excludeCards.some(c=>globMatch(t.cardId,c)))),c.excludeTags?.length&&(t=t.filter(t=>!c.excludeTags.some(c=>cardMatchesTagPattern(t,c)))),c.boostTags)for(let[u,d]of Object.entries(c.boostTags))for(let m of t)cardMatchesTagPattern(m,u)&&(m.score*=d,m.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:c._label?`Replan Hint (${c._label})`:`Replan Hint`,action:`boosted`,score:m.score,reason:`boostTag ${u} \xD7${d}`}));if(c.boostCards)for(let[u,d]of Object.entries(c.boostCards))for(let m of t)globMatch(m.cardId,u)&&(m.score*=d,m.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:c._label?`Replan Hint (${c._label})`:`Replan Hint`,action:`boosted`,score:m.score,reason:`boostCard ${u} \xD7${d}`}));let m=new Set(t.map(t=>t.cardId)),g=new Map(t.map(t=>[t.cardId,t])),b=c._label?`Replan Hint (${c._label})`:`Replan Hint`,applyRequirement=(c,u)=>{let d=1/0,S=g.get(c.cardId);S?S.score<d&&(S.score=d,S.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:b,action:`boosted`,score:d,reason:`${u} (upgrade to mandatory score)`})):(t.push({...c,score:d,provenance:[...c.provenance,{strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:b,action:`boosted`,score:d,reason:u}]}),m.add(c.cardId),g.set(c.cardId,t[t.length-1]))};if(c.requireCards?.length)for(let t of c.requireCards){for(let c of m)globMatch(c,t)&&applyRequirement(g.get(c),`requireCard ${t}`);for(let c of u)globMatch(c.cardId,t)&&applyRequirement(c,`requireCard ${t}`)}if(c.requireTags?.length)for(let t of c.requireTags){for(let c of m){let u=g.get(c);cardMatchesTagPattern(u,t)&&applyRequirement(u,`requireTag ${t}`)}for(let c of u)cardMatchesTagPattern(c,t)&&applyRequirement(c,`requireTag ${t}`)}return logger.info(`[Pipeline] Hints applied: ${d} \u2192 ${t.length} cards`),t}async buildContext(){let t=1e3;try{t=toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo).global.score}catch(t){logger.debug(`[Pipeline] Could not get user ELO, using default: ${t}`)}this._cachedOrchestration||(this._cachedOrchestration=await createOrchestrationContext(this.user,this.course));let c=this._cachedOrchestration;return{user:this.user,course:this.course,userElo:t,orchestration:c}}getCourseID(){return this.course.getCourseID()}async getOrchestrationContext(){return createOrchestrationContext(this.user,this.course)}getStrategyIds(){let t=[],extractId=t=>t.strategyId?t.strategyId:null,c=extractId(this.generator);c&&t.push(c),this.generator.generators&&Array.isArray(this.generator.generators)&&this.generator.generators.forEach(c=>{let u=extractId(c);u&&t.push(u)});for(let c of this.filters){let u=extractId(c);u&&t.push(u)}return[...new Set(t)]}async getTagEloStatus(t){let c=toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo),u={};if(t){let d=Array.isArray(t)?t:[t];for(let t of d){let d=globToRegex(t);for(let[t,m]of Object.entries(c.tags))d.test(t)&&(u[t]={score:m.score,count:m.count})}}else for(let[t,d]of Object.entries(c.tags))u[t]={score:d.score,count:d.count};return u}async diagnoseCardSpace(t){let c=t?.threshold??.1,u=performance.now(),d=await this.course.getAllCardIds(),m=d.map(t=>({cardId:t,courseId:this.course.getCourseID(),score:1,provenance:[]}));m=await this.hydrateTags(m);let g=await this.buildContext(),b=[];for(let t of this.filters){m=await t.transform(m,g);let u=m.filter(t=>t.score>=c).length;b.push({name:t.name,wellIndicated:u})}let S=m.filter(t=>t.score>=c),C=new Set(S.map(t=>t.cardId)),w;try{let t=this.course.getCourseID(),c=await this.user.getSeenCards(t);w=new Set(c)}catch{w=new Set}let T=S.filter(t=>!w.has(t.cardId)),E=new Map;for(let t of m){let u=t.cardId.split(`-`)[1]||`unknown`;E.has(u)||E.set(u,{total:0,wellIndicated:0,new:0});let d=E.get(u);d.total++,t.score>=c&&(d.wellIndicated++,w.has(t.cardId)||d.new++)}let D=performance.now()-u,O={totalCards:d.length,threshold:c,wellIndicated:C.size,encountered:w.size,wellIndicatedNew:T.length,byType:Object.fromEntries(E),filterBreakdown:b,elapsedMs:Math.round(D)};logger.info(`[Pipeline:diagnose] Card space scan (${O.elapsedMs}ms):`),logger.info(`[Pipeline:diagnose] Total cards: ${O.totalCards}`),logger.info(`[Pipeline:diagnose] Well-indicated (score >= ${c}): ${O.wellIndicated}`),logger.info(`[Pipeline:diagnose] Encountered: ${O.encountered}`),logger.info(`[Pipeline:diagnose] Well-indicated & new: ${O.wellIndicatedNew}`),logger.info(`[Pipeline:diagnose] By type:`);for(let[t,c]of E)logger.info(`[Pipeline:diagnose] ${t}: ${c.wellIndicated}/${c.total} well-indicated, ${c.new} new`);logger.info(`[Pipeline:diagnose] After each filter:`);for(let t of b)logger.info(`[Pipeline:diagnose] ${t.name}: ${t.wellIndicated} well-indicated`);return O}}}}),defaults_exports={},__export(defaults_exports,{createDefaultEloStrategy:()=>createDefaultEloStrategy,createDefaultPipeline:()=>createDefaultPipeline,createDefaultSrsStrategy:()=>createDefaultSrsStrategy}),init_defaults=__esm({"src/core/navigators/defaults.ts"(){"use strict";init_navigators(),init_Pipeline(),init_CompositeGenerator(),init_elo(),init_srs(),init_eloDistance(),init_types_legacy()}}),PipelineAssembler_exports={},__export(PipelineAssembler_exports,{PipelineAssembler:()=>PipelineAssembler}),init_PipelineAssembler=__esm({"src/core/navigators/PipelineAssembler.ts"(){"use strict";init_navigators(),init_WeightedFilter(),init_Pipeline(),init_logger(),init_CompositeGenerator(),init_defaults(),PipelineAssembler=class{async assemble(t){let{strategies:c,user:u,course:d}=t,m=[];if(c.length===0)return{pipeline:null,generatorStrategies:[],filterStrategies:[],warnings:m};let g=[],b=[];for(let t of c)isGenerator(t.implementingClass)?g.push(t):isFilter(t.implementingClass)?b.push(t):m.push(`Unknown strategy type '${t.implementingClass}', skipping: ${t.name}`);let S=d.getCourseID(),C=g.some(t=>t.implementingClass===`elo`),w=g.some(t=>t.implementingClass===`srs`);if(C||(logger.debug(`[PipelineAssembler] No ELO generator configured, adding default`),g.push(createDefaultEloStrategy(S))),w||(logger.debug(`[PipelineAssembler] No SRS generator configured, adding default`),g.push(createDefaultSrsStrategy(S))),g.length===0)return m.push(`No generator strategy found`),{pipeline:null,generatorStrategies:[],filterStrategies:[],warnings:m};let T;g.length===1?(T=await ContentNavigator.create(u,d,g[0]),logger.debug(`[PipelineAssembler] Using single generator: ${g[0].name}`)):(logger.debug(`[PipelineAssembler] Using CompositeGenerator for ${g.length} generators: ${g.map(t=>t.name).join(`, `)}`),T=await CompositeGenerator.fromStrategies(u,d,g));let E=[],D=[...b].sort((t,c)=>t.name.localeCompare(c.name));for(let t of D)try{let c=await ContentNavigator.create(u,d,t);if(`transform`in c&&typeof c.transform==`function`){let u=c;t.learnable&&(u=new WeightedFilter(u,t.learnable,t.staticWeight,t._id)),E.push(u),logger.debug(`[PipelineAssembler] Added filter: ${t.name}`)}else m.push(`Filter '${t.name}' does not implement CardFilter.transform(), skipping`)}catch(c){m.push(`Failed to instantiate filter '${t.name}': ${c}`)}let O=new Pipeline(T,E,u,d);return logger.debug(`[PipelineAssembler] Assembled pipeline with ${g.length} generator(s) and ${E.length} filter(s)`),{pipeline:O,generatorStrategies:g,filterStrategies:D,warnings:m}}}}}),init_3=__esm({'import("./**/*") in src/core/navigators/index.ts'(){globImport=__glob({"./Pipeline.ts":()=>Promise.resolve().then(()=>(init_Pipeline(),Pipeline_exports)),"./PipelineAssembler.ts":()=>Promise.resolve().then(()=>(init_PipelineAssembler(),PipelineAssembler_exports)),"./PipelineDebugger.ts":()=>Promise.resolve().then(()=>(init_PipelineDebugger(),PipelineDebugger_exports)),"./defaults.ts":()=>Promise.resolve().then(()=>(init_defaults(),defaults_exports)),"./filters/WeightedFilter.ts":()=>Promise.resolve().then(()=>(init_WeightedFilter(),WeightedFilter_exports)),"./filters/eloDistance.ts":()=>Promise.resolve().then(()=>(init_eloDistance(),eloDistance_exports)),"./filters/hierarchyDefinition.ts":()=>Promise.resolve().then(()=>(init_hierarchyDefinition(),hierarchyDefinition_exports)),"./filters/index.ts":()=>Promise.resolve().then(()=>(init_filters(),filters_exports)),"./filters/inferredPreferenceStub.ts":()=>Promise.resolve().then(()=>(init_inferredPreferenceStub(),inferredPreferenceStub_exports)),"./filters/interferenceMitigator.ts":()=>Promise.resolve().then(()=>(init_interferenceMitigator(),interferenceMitigator_exports)),"./filters/relativePriority.ts":()=>Promise.resolve().then(()=>(init_relativePriority(),relativePriority_exports)),"./filters/types.ts":()=>Promise.resolve().then(()=>(init_types2(),types_exports2)),"./filters/userGoalStub.ts":()=>Promise.resolve().then(()=>(init_userGoalStub(),userGoalStub_exports)),"./filters/userTagPreference.ts":()=>Promise.resolve().then(()=>(init_userTagPreference(),userTagPreference_exports)),"./generators/CompositeGenerator.ts":()=>Promise.resolve().then(()=>(init_CompositeGenerator(),CompositeGenerator_exports)),"./generators/elo.ts":()=>Promise.resolve().then(()=>(init_elo(),elo_exports)),"./generators/index.ts":()=>Promise.resolve().then(()=>(init_generators(),generators_exports)),"./generators/prescribed.ts":()=>Promise.resolve().then(()=>(init_prescribed(),prescribed_exports)),"./generators/srs.ts":()=>Promise.resolve().then(()=>(init_srs(),srs_exports)),"./generators/types.ts":()=>Promise.resolve().then(()=>(init_types(),types_exports)),"./index.ts":()=>Promise.resolve().then(()=>(init_navigators(),navigators_exports))})}}),navigators_exports={},__export(navigators_exports,{ContentNavigator:()=>ContentNavigator,NavigatorRole:()=>NavigatorRole,NavigatorRoles:()=>NavigatorRoles,Navigators:()=>Navigators,getCardOrigin:()=>getCardOrigin,getRegisteredNavigator:()=>getRegisteredNavigator,getRegisteredNavigatorNames:()=>getRegisteredNavigatorNames,getRegisteredNavigatorRole:()=>getRegisteredNavigatorRole,hasRegisteredNavigator:()=>hasRegisteredNavigator,initializeNavigatorRegistry:()=>initializeNavigatorRegistry,isFilter:()=>isFilter,isGenerator:()=>isGenerator,mountPipelineDebugger:()=>mountPipelineDebugger,pipelineDebugAPI:()=>pipelineDebugAPI,registerNavigator:()=>registerNavigator}),init_navigators=__esm({"src/core/navigators/index.ts"(){"use strict";init_PipelineDebugger(),init_logger(),init_(),init_2(),init_3(),navigatorRegistry=new Map,Navigators=(t=>(t.ELO=`elo`,t.SRS=`srs`,t.PRESCRIBED=`prescribed`,t.HIERARCHY=`hierarchyDefinition`,t.INTERFERENCE=`interferenceMitigator`,t.RELATIVE_PRIORITY=`relativePriority`,t.USER_TAG_PREFERENCE=`userTagPreference`,t))(Navigators||{}),NavigatorRole=(t=>(t.GENERATOR=`generator`,t.FILTER=`filter`,t))(NavigatorRole||{}),NavigatorRoles={elo:`generator`,srs:`generator`,prescribed:`generator`,hierarchyDefinition:`filter`,interferenceMitigator:`filter`,relativePriority:`filter`,userTagPreference:`filter`},ContentNavigator=class{constructor(t,c,u){_defineProperty$2(this,`user`,void 0),_defineProperty$2(this,`course`,void 0),_defineProperty$2(this,`strategyName`,void 0),_defineProperty$2(this,`strategyId`,void 0),_defineProperty$2(this,`learnable`,void 0),_defineProperty$2(this,`staticWeight`,void 0),this.user=t,this.course=c,u&&(this.strategyName=u.name,this.strategyId=u._id,this.learnable=u.learnable,this.staticWeight=u.staticWeight)}get strategyKey(){return this.constructor.name}async getStrategyState(){if(!this.user||!this.course)throw Error(`Cannot get strategy state: navigator not properly initialized. Ensure user and course are provided to constructor.`);return this.user.getStrategyState(this.course.getCourseID(),this.strategyKey)}async putStrategyState(t){if(!this.user||!this.course)throw Error(`Cannot put strategy state: navigator not properly initialized. Ensure user and course are provided to constructor.`);return this.user.putStrategyState(this.course.getCourseID(),this.strategyKey,t)}static async create(t,c,u){let d=u.implementingClass,m=getRegisteredNavigator(d);if(m)return logger.debug(`[ContentNavigator.create] Using registered navigator: ${d}`),new m(t,c,u);logger.debug(`[ContentNavigator.create] Navigator not in registry, attempting dynamic import: ${d}`);let g;for(let t of[`.ts`,`.js`,``]){try{if(g=(await globImport_generators(`./generators/${d}${t}`)).default,g)break}catch(c){logger.debug(`Failed to load generator ${d}${t}:`,c)}try{if(g=(await globImport_filters(`./filters/${d}${t}`)).default,g)break}catch(c){logger.debug(`Failed to load filter ${d}${t}:`,c)}try{if(g=(await globImport(`./${d}${t}`)).default,g)break}catch(c){logger.debug(`Failed to load legacy ${d}${t}:`,c)}if(g)break}if(!g)throw Error(`Could not load navigator implementation for: ${d}`);return new g(t,c,u)}async getWeightedCards(t){throw Error(`${this.constructor.name} must implement getWeightedCards(). `)}setEphemeralHints(t){}}}}),init_courseDB=__esm({"src/impl/couch/courseDB.ts"(){"use strict";init_couch(),init_updateQueue(),init_types_legacy(),init_logger(),init_clientCache(),init_courseAPI(),init_courseLookupDB(),init_navigators(),init_PipelineAssembler(),init_defaults(),CoursesDB=class{constructor(t){_defineProperty$2(this,`_courseIDs`,void 0),t&&t.length>0?this._courseIDs=t:this._courseIDs=void 0}async getCourseList(){let t=await CourseLookup.allCourseWare();return logger.debug(`AllCourses: ${t.map(t=>t.name+`, `+t._id+`
350
350
  `)}`),this._courseIDs&&(t=t.filter(t=>this._courseIDs.includes(t._id))),logger.debug(`AllCourses.filtered: ${t.map(t=>t.name+`, `+t._id+`
351
- `)}`),(await Promise.all(t.map(async t=>{try{let c=await getCredentialledCourseConfig(t._id);return logger.debug(`Found cfg: ${JSON.stringify(c)}`),c}catch(c){logger.warn(`Error fetching cfg for course ${t.name}, ${t._id}: ${c}`);return}}))).filter(t=>!!t)}async getCourseConfig(t){if(this._courseIDs&&this._courseIDs.length&&!this._courseIDs.includes(t))throw Error(`Course ${t} not in course list`);let c=await getCredentialledCourseConfig(t);if(c===void 0)throw Error(`Error fetching cfg for course ${t}`);return c}async disambiguateCourse(t,c){await CourseLookup.updateDisambiguator(t,c)}},CourseDB=class{constructor(t,c,u){_defineProperty$2(this,`db`,void 0),_defineProperty$2(this,`remoteDB`,void 0),_defineProperty$2(this,`id`,void 0),_defineProperty$2(this,`_getCurrentUser`,void 0),_defineProperty$2(this,`updateQueue`,void 0),_defineProperty$2(this,`_pendingHints`,null),this.id=t;let d=getCourseDB2(this.id);this.remoteDB=d,this.db=u??d,this._getCurrentUser=c,this.updateQueue=new UpdateQueue(this.remoteDB,this.remoteDB)}getCourseID(){return this.id}async getCourseInfo(){return{cardCount:(await this.db.find({selector:{docType:`CARD`},limit:1e3})).docs.length,registeredUsers:0}}async getInexperiencedCards(t=2){return(await this.db.query(`cardsByInexperience`,{limit:t})).rows.map(t=>({courseId:this.id,cardId:t.id,count:t.key,elo:t.value}))}async getCardsByEloLimits(t={low:0,high:-(2**53-1),limit:25,page:0}){return(await this.db.query(`elo`,{startkey:t.low,endkey:t.high,limit:t.limit,skip:t.limit*t.page})).rows.map(t=>`${this.id}-${t.id}-${t.key}`)}async getCardEloData(t){let c=await this.db.allDocs({keys:t,include_docs:!0}),u=[];return c.rows.forEach(t=>{isSuccessRow(t)?t.doc&&t.doc.elo?u.push(toCourseElo(t.doc.elo)):(logger.warn(`no elo data for card: `+t.id),u.push(blankCourseElo())):(logger.warn(`no elo data for card: `+JSON.stringify(t)),u.push(blankCourseElo()))}),u}async getELOBounds(){let[t,c]=await Promise.all([(await this.db.query(`elo`,{startkey:0,limit:1,include_docs:!1})).rows[0].key,(await this.db.query(`elo`,{limit:1,descending:!0,startkey:1e5})).rows[0].key]);return{low:t,high:c}}async removeCard(t){let c=await this.remoteDB.get(t);if(!c.docType||c.docType!==`CARD`)throw Error(`failed to remove ${t} from course ${this.id}. id does not point to a card`);try{let c=await this.getAppliedTags(t);(await Promise.allSettled(c.rows.map(async c=>{let u=c.id;await this.removeTagFromCard(t,u)}))).forEach((u,d)=>{if(u.status===`rejected`){let m=c.rows[d].id;logger.error(`Failed to remove card ${t} from tag ${m}: ${u.reason}`)}})}catch(c){logger.error(`Error removing card ${t} from tags: ${c}`)}return this.remoteDB.remove(c)}async getCardDisplayableDataIDs(t){logger.debug(t.join(`, `));let c=await this.db.allDocs({keys:t,include_docs:!0}),u={};return c.rows.forEach(t=>{isSuccessRow(t)&&(u[t.id]=t.doc.id_displayable_data)}),u}async getCardsByELO(t,c){t=parseInt(t);let u=c||25,d=await this.db.query(`elo`,{limit:Math.ceil(u/2),startkey:t,descending:!0}),m=u-d.rows.length,g=await this.db.query(`elo`,{limit:m,startkey:t+1}),b=d.rows;b=b.concat(g.rows);let S=b.sort((c,u)=>{let d=Math.abs(c.key-t)-Math.abs(u.key-t);return d===0?Math.random()-.5:d}).map(t=>({courseID:this.id,cardID:t.id,elo:t.key})),C=`below:
351
+ `)}`),(await Promise.all(t.map(async t=>{try{let c=await getCredentialledCourseConfig(t._id);return logger.debug(`Found cfg: ${JSON.stringify(c)}`),c}catch(c){logger.warn(`Error fetching cfg for course ${t.name}, ${t._id}: ${c}`);return}}))).filter(t=>!!t)}async getCourseConfig(t){if(this._courseIDs&&this._courseIDs.length&&!this._courseIDs.includes(t))throw Error(`Course ${t} not in course list`);let c=await getCredentialledCourseConfig(t);if(c===void 0)throw Error(`Error fetching cfg for course ${t}`);return c}async disambiguateCourse(t,c){await CourseLookup.updateDisambiguator(t,c)}},CourseDB=class{constructor(t,c,u){_defineProperty$2(this,`db`,void 0),_defineProperty$2(this,`remoteDB`,void 0),_defineProperty$2(this,`id`,void 0),_defineProperty$2(this,`_getCurrentUser`,void 0),_defineProperty$2(this,`updateQueue`,void 0),_defineProperty$2(this,`_pendingHints`,null),_defineProperty$2(this,`_eloPoolCache`,null),_defineProperty$2(this,`_eloPoolTtlMs`,300*1e3),_defineProperty$2(this,`_cachedNavigator`,null),_defineProperty$2(this,`_navigatorTtlMs`,300*1e3),this.id=t;let d=getCourseDB2(this.id);this.remoteDB=d,this.db=u??d,this._getCurrentUser=c,this.updateQueue=new UpdateQueue(this.remoteDB,this.remoteDB)}getCourseID(){return this.id}async getCourseInfo(){return{cardCount:(await this.db.find({selector:{docType:`CARD`},limit:1e3})).docs.length,registeredUsers:0}}async getInexperiencedCards(t=2){return(await this.db.query(`cardsByInexperience`,{limit:t})).rows.map(t=>({courseId:this.id,cardId:t.id,count:t.key,elo:t.value}))}async getCardsByEloLimits(t={low:0,high:-(2**53-1),limit:25,page:0}){return(await this.db.query(`elo`,{startkey:t.low,endkey:t.high,limit:t.limit,skip:t.limit*t.page})).rows.map(t=>`${this.id}-${t.id}-${t.key}`)}async getCardEloData(t){let c=await this.db.allDocs({keys:t,include_docs:!0}),u=[];return c.rows.forEach(t=>{isSuccessRow(t)?t.doc&&t.doc.elo?u.push(toCourseElo(t.doc.elo)):(logger.warn(`no elo data for card: `+t.id),u.push(blankCourseElo())):(logger.warn(`no elo data for card: `+JSON.stringify(t)),u.push(blankCourseElo()))}),u}async getELOBounds(){let[t,c]=await Promise.all([(await this.db.query(`elo`,{startkey:0,limit:1,include_docs:!1})).rows[0].key,(await this.db.query(`elo`,{limit:1,descending:!0,startkey:1e5})).rows[0].key]);return{low:t,high:c}}async removeCard(t){let c=await this.remoteDB.get(t);if(!c.docType||c.docType!==`CARD`)throw Error(`failed to remove ${t} from course ${this.id}. id does not point to a card`);try{let c=await this.getAppliedTags(t);(await Promise.allSettled(c.rows.map(async c=>{let u=c.id;await this.removeTagFromCard(t,u)}))).forEach((u,d)=>{if(u.status===`rejected`){let m=c.rows[d].id;logger.error(`Failed to remove card ${t} from tag ${m}: ${u.reason}`)}})}catch(c){logger.error(`Error removing card ${t} from tags: ${c}`)}return this.remoteDB.remove(c)}async getCardDisplayableDataIDs(t){logger.debug(t.join(`, `));let c=await this.db.allDocs({keys:t,include_docs:!0}),u={};return c.rows.forEach(t=>{isSuccessRow(t)&&(u[t.id]=t.doc.id_displayable_data)}),u}async getCardsByELO(t,c){t=parseInt(t);let u=c||25,d=await this.db.query(`elo`,{limit:Math.ceil(u/2),startkey:t,descending:!0}),m=u-d.rows.length,g=await this.db.query(`elo`,{limit:m,startkey:t+1}),b=d.rows;b=b.concat(g.rows);let S=b.sort((c,u)=>{let d=Math.abs(c.key-t)-Math.abs(u.key-t);return d===0?Math.random()-.5:d}).map(t=>({courseID:this.id,cardID:t.id,elo:t.key})),C=`below:
352
352
  ${d.rows.map(t=>` ${t.id}-${t.key}
353
353
  `)}
354
354
 
@@ -358,8 +358,8 @@ ${g.rows.map(t=>` ${t.id}-${t.key}
358
358
 
359
359
  `+C),S}async getCourseConfig(){let t=await getCredentialledCourseConfig(this.id);if(t)return t;throw Error(`Course config not found for course ID: ${this.id}`)}async updateCourseConfig(t){logger.debug(`Updating: ${JSON.stringify(t)}`);try{return await updateCredentialledCourseConfig(this.id,t)}catch(t){throw logger.error(`Error updating course config in course DB: ${t}`),t}}async updateCardElo(t,c){if(!c)throw Error(`Cannot update card elo with null or undefined value for card ID: ${t}`);try{return{ok:!0,id:t,rev:(await this.updateQueue.update(t,t=>(logger.debug(`Replacing ${JSON.stringify(t.elo)} with ${JSON.stringify(c)}`),t.elo=c,t)))._rev}}catch(c){throw logger.error(`Failed to update card elo for card ID: ${t}`,c),Error(`Failed to update card elo for card ID: ${t}`)}}async getAppliedTags(t){let c=await getAppliedTags(this.id,t);if(c)return c;throw Error(`Failed to find tags for card ${this.id}-${t}`)}async getAppliedTagsBatch(t){if(t.length===0)return new Map;let c=await this.db.query(`getTags`,{keys:t,include_docs:!1}),u=new Map;for(let c of t)u.set(c,[]);for(let t of c.rows){let c=t.key,d=t.value?.name;d&&u.has(c)&&u.get(c).push(d)}return u}async getAllCardIds(){return(await this.db.allDocs({startkey:`CARD-`,endkey:`CARD-￰`,include_docs:!1})).rows.map(t=>t.id)}async addTagToCard(t,c,u){return await addTagToCard(this.id,t,c,(await this._getCurrentUser()).getUsername(),u)}async removeTagFromCard(t,c){return await removeTagFromCard(this.id,t,c)}async createTag(t,c){return await createTag(this.id,t,c)}async getTag(t){return await getTag(this.id,t)}async updateTag(t){if(t.course!==this.id)throw Error(`Tag ${JSON.stringify(t)} does not belong to course ${this.id}`);return await updateTag(t)}async getCourseTagStubs(){return getCourseTagStubs(this.id)}async addNote(t,c,u,d,m,g,b=blankCourseElo()){try{let S=await addNote55(this.id,t,c,u,d,m,g,b);return S.ok?S.cardCreationFailed?(logger.warn(`[courseDB.addNote] Note added but card creation failed: ${S.cardCreationError}`),{status:Status.error,message:`Note was added but no cards were created: ${S.cardCreationError}`,id:S.id}):{status:Status.ok,message:``,id:S.id}:{status:Status.error,message:`Unexpected error adding note`}}catch(t){let c=t;return logger.error(`[addNote] error ${c.name}
360
360
  reason: ${c.reason}
361
- message: ${c.message}`),{status:Status.error,message:`Error adding note to course. ${t.reason||c.message}`}}}async getCourseDoc(t,c){return await this.db.get(t,c??{})}async getCourseDocs(t,c={}){return await this.db.allDocs({...c,keys:t})}getNavigationStrategy(t){if(logger.debug(`[courseDB] Getting navigation strategy: ${t}`),t==``){let t={_id:`NAVIGATION_STRATEGY-ELO`,docType:`NAVIGATION_STRATEGY`,name:`ELO`,description:`ELO-based navigation strategy for ordering content by difficulty`,implementingClass:`elo`,course:this.id,serializedData:``};return Promise.resolve(t)}else return this.db.get(t)}async getAllNavigationStrategies(){let t=DocTypePrefixes.NAVIGATION_STRATEGY;return(await this.db.allDocs({startkey:t,endkey:`${t}\uFFF0`,include_docs:!0})).rows.map(t=>t.doc)}async addNavigationStrategy(t){return logger.debug(`[courseDB] Adding navigation strategy: ${t._id}`),this.remoteDB.put(t).then(()=>{})}updateNavigationStrategy(t,c){return logger.debug(`[courseDB] Updating navigation strategy: ${t}`),logger.debug(JSON.stringify(c)),Promise.resolve()}async createNavigator(t){try{let c=await this.getAllNavigationStrategies();if(c.length===0)return logger.debug(`[courseDB] No strategy documents found, using default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])`),createDefaultPipeline(t,this);let{pipeline:u,generatorStrategies:d,filterStrategies:m,warnings:g}=await new PipelineAssembler().assemble({strategies:c,user:t,course:this});for(let t of g)logger.warn(`[PipelineAssembler] ${t}`);return u?(logger.debug(`[courseDB] Using assembled pipeline with ${d.length} generator(s) and ${m.length} filter(s)`),u):(logger.debug(`[courseDB] Pipeline assembly failed, using default pipeline`),createDefaultPipeline(t,this))}catch(t){let c=t instanceof Error?`${t.message}
362
- ${t.stack}`:JSON.stringify(t);throw logger.error(`[courseDB] Error creating navigator: ${c}`),t}}setEphemeralHints(t){this._pendingHints=t}async getWeightedCards(t){let c=await this._getCurrentUser();try{let u=await this.createNavigator(c);return this._pendingHints&&(u.setEphemeralHints(this._pendingHints),this._pendingHints=null),u.getWeightedCards(t)}catch(t){throw logger.error(`[courseDB] Error getting weighted cards: ${t}`),t}}async getCardsCenteredAtELO(t={limit:99,elo:`user`},c){let u;if(t.elo===`user`){let t=await this._getCurrentUser();u=-1;try{u=EloToNumber((await t.getCourseRegistrationsDoc()).courses.find(t=>t.courseID===this.id).elo)}catch{u=1e3}}else if(t.elo===`random`){let t=await GET_CACHED(`elo-bounds-${this.id}`,()=>this.getELOBounds());u=Math.round(t.low+Math.random()*(t.high-t.low))}else u=t.elo;let d=[],m=4,g=-1,b=0;for(;d.length<t.limit&&b!==g;)d=await this.getCardsByELO(u,m*t.limit),g=b,b=d.length,logger.debug(`Found ${d.length} elo neighbor cards...`),c&&(d=d.filter(c),logger.debug(`Filtered to ${d.length} cards...`)),m*=2;let S=[];for(;S.length<t.limit&&d.length>0;){let t=randIntWeightedTowardZero(d.length),c=d.splice(t,1)[0];S.push(c)}return S.map(t=>({courseID:this.id,cardID:t.cardID,contentSourceType:`course`,contentSourceID:this.id,elo:t.elo,status:`new`}))}async searchCards(t){logger.log(`[CourseDB ${this.id}] Searching for: "${t}"`);let c;try{c=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`,"data.0.data":{$regex:`.*${t}.*`}}}),logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`)}catch(u){logger.log(`[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,u);let d=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`}});logger.log(`[CourseDB ${this.id}] Retrieved ${d.docs.length} documents for manual filtering`),c={docs:d.docs.filter(c=>{let u=JSON.stringify(c).toLowerCase().includes(t.toLowerCase());return u&&logger.log(`[CourseDB ${this.id}] Manual match found in document: ${c._id}`),u})}}if(logger.log(`[CourseDB ${this.id}] Found ${c.docs.length} displayable data documents`),c.docs.length===0){let t=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`},limit:5});logger.log(`[CourseDB ${this.id}] Sample displayable data:`,t.docs.map(t=>({id:t._id,docType:t.docType,dataStructure:t.data?Object.keys(t.data):`no data field`,dataContent:t.data,fullDoc:t})))}let u=[];for(let t of c.docs){let c=await this.db.find({selector:{docType:`CARD`,id_displayable_data:{$in:[t._id]}}});logger.log(`[CourseDB ${this.id}] Displayable data ${t._id} linked to ${c.docs.length} cards`),u.push(...c.docs)}return logger.log(`[CourseDB ${this.id}] Total cards found: ${u.length}`),u}async find(t){return this.db.find(t)}}}}),init_classroomDB2=__esm({"src/impl/couch/classroomDB.ts"(){"use strict";init_factory(),init_logger(),init_pouchdb_setup(),init_couch(),init_courseDB(),classroomLookupDBTitle=`classdb-lookup`,CLASSROOM_CONFIG=`ClassroomConfig`,ClassroomDBBase=class{constructor(){_defineProperty$2(this,`_id`,void 0),_defineProperty$2(this,`_db`,void 0),_defineProperty$2(this,`_cfg`,void 0),_defineProperty$2(this,`_initComplete`,!1),_defineProperty$2(this,`_content_prefix`,`content`)}get _content_searchkeys(){return getStartAndEndKeys2(this._content_prefix)}async getAssignedContent(){return logger.info(`Getting assigned content...`),(await this._db.allDocs({startkey:this._content_prefix,endkey:this._content_prefix+`￰`,include_docs:!0})).rows.map(t=>t.doc)}getContentId(t){return t.type===`tag`?`${this._content_prefix}-${t.courseID}-${t.tagID}`:`${this._content_prefix}-${t.courseID}`}get ready(){return this._initComplete}getConfig(){return this._cfg}},StudentClassroomDB=class _StudentClassroomDB extends ClassroomDBBase{constructor(t,c){super(),_defineProperty$2(this,`userMessages`,void 0),_defineProperty$2(this,`_user`,void 0),this._id=t,this._user=c}async init(){let t=`classdb-student-${this._id}`;this._db=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+t,createPouchDBConfig());try{this._cfg=await this._db.get(CLASSROOM_CONFIG),this.userMessages=this._db.changes({since:`now`,live:!0,include_docs:!0}),this._initComplete=!0;return}catch(t){throw Error(`Error in StudentClassroomDB constructor: ${JSON.stringify(t)}`)}}static async factory(t,c){let u=new _StudentClassroomDB(t,c);return await u.init(),u}setChangeFcn(t){this.userMessages.on(`change`,t)}async getWeightedCards(t){let c=[],u=(await this._user.getPendingReviews()).filter(t=>t.scheduledFor===`classroom`&&t.schedulingAgentId===this._id);for(let t of u)c.push({cardId:t.cardId,courseId:t.courseId,score:1,reviewID:t._id,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom scheduled review`}]});let d=await this._user.getActiveCards(),m=new Set(d.map(t=>t.cardID)),g=hooks.utc(),b=(await this.getAssignedContent()).filter(t=>g.isAfter(hooks.utc(t.activeOn,REVIEW_TIME_FORMAT2)));logger.info(`[StudentClassroomDB] Due content: ${JSON.stringify(b)}`);for(let u of b)if(u.type===`course`){let{cards:d}=await new CourseDB(u.courseID,async()=>this._user).getWeightedCards(t);for(let t of d)m.has(t.cardId)||c.push({...t,provenance:[...t.provenance,{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`passed`,score:t.score,reason:`Assigned via classroom from course ${u.courseID}`}]})}else if(u.type===`tag`){let t=await getTag(u.courseID,u.tagID);for(let d of t.taggedCards)m.has(d)||c.push({cardId:d,courseId:u.courseID,score:1,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom assigned tag: ${u.tagID}, new card`}]})}else u.type===`card`&&(m.has(u.cardID)||c.push({cardId:u.cardID,courseId:u.courseID,score:1,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom assigned card, new card`}]}));return logger.info(`[StudentClassroomDB] New cards from classroom ${this._cfg.name}: ${c.length} total (reviews + new)`),{cards:c.sort((t,c)=>c.score-t.score).slice(0,t)}}},TeacherClassroomDB=class _TeacherClassroomDB extends ClassroomDBBase{constructor(t){super(),_defineProperty$2(this,`_stuDb`,void 0),this._id=t}async init(){let t=`classdb-teacher-${this._id}`,c=`classdb-student-${this._id}`;this._db=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+t,createPouchDBConfig()),this._stuDb=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+c,createPouchDBConfig());try{return this._db.get(CLASSROOM_CONFIG).then(t=>{this._cfg=t,this._initComplete=!0}).then(()=>{})}catch(t){throw Error(`Error in TeacherClassroomDB constructor: ${JSON.stringify(t)}`)}}static async factory(t){let c=new _TeacherClassroomDB(t);return await c.init(),c}async removeContent(t){let c=this.getContentId(t);try{let t=await this._db.get(c);await this._db.remove(t),this._db.replicate.to(this._stuDb,{doc_ids:[c]})}catch(t){logger.error(`Failed to remove content:`,c,t)}}async assignContent(t){let c,u=this.getContentId(t);return c=t.type===`tag`?await this._db.put({courseID:t.courseID,tagID:t.tagID,type:`tag`,_id:u,assignedBy:t.assignedBy,assignedOn:hooks.utc(),activeOn:t.activeOn||hooks.utc()}):await this._db.put({courseID:t.courseID,type:`course`,_id:u,assignedBy:t.assignedBy,assignedOn:hooks.utc(),activeOn:t.activeOn||hooks.utc()}),c.ok?(this._db.replicate.to(this._stuDb,{doc_ids:[u]}),!0):!1}},ClassroomLookupDB=()=>new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+classroomLookupDBTitle,{skip_setup:!0})}}),init_adminDB2=__esm({"src/impl/couch/adminDB.ts"(){"use strict";init_pouchdb_setup(),init_factory(),init_couch(),init_classroomDB2(),init_courseLookupDB(),init_logger(),AdminDB=class{constructor(){_defineProperty$2(this,`usersDB`,void 0),this.usersDB=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+`_users`,createPouchDBConfig())}async getUsers(){return(await this.usersDB.allDocs({include_docs:!0,...getStartAndEndKeys2(`org.couchdb.user:`)})).rows.map(t=>t.doc)}async getCourses(){let t=await CourseLookup.allCourseWare();return await Promise.all(t.map(t=>getCredentialledCourseConfig(t._id)))}async removeCourse(t){let c=await CourseLookup.delete(t),u=await getCredentialledCourseConfig(t);u.deleted=!0;let d=await updateCredentialledCourseConfig(t,u);return{ok:c.ok&&d.ok,id:c.id,rev:c.rev}}async getClassrooms(){let t=(await ClassroomLookupDB().allDocs({include_docs:!0})).rows.map(t=>t.doc.uuid);logger.debug(t.join(`, `));let c=[];for(let u=0;u<t.length;u++)try{let d=await TeacherClassroomDB.factory(t[u]);c.push(d)}catch(c){let d=c;d.error&&d.error===`not_found`&&logger.warn(`db ${t[u]} not found`)}return c.map(t=>({...t.getConfig(),_id:t._id}))}}}}),CourseSyncService_exports={},__export(CourseSyncService_exports,{CourseSyncService:()=>CourseSyncService}),init_CourseSyncService=__esm({"src/impl/couch/CourseSyncService.ts"(){"use strict";var t;init_pouchdb_setup(),init_couch(),init_logger(),DEFAULT_REPLICATION={batchSize:1e3,batchesLimit:5},CourseSyncService=(t=class _CourseSyncService{constructor(){_defineProperty$2(this,`entries`,new Map),_defineProperty$2(this,`replicationOptions`,DEFAULT_REPLICATION)}static getInstance(){return _CourseSyncService.instance||(_CourseSyncService.instance=new _CourseSyncService),_CourseSyncService.instance}configure(t){t.replication&&(this.replicationOptions={...DEFAULT_REPLICATION,...t.replication},logger.info(`[CourseSyncService] Replication configured: batch_size=${this.replicationOptions.batchSize}, batches_limit=${this.replicationOptions.batchesLimit}`))}static resetInstance(){if(_CourseSyncService.instance){for(let[,t]of _CourseSyncService.instance.entries)t.localDB&&t.localDB.close().catch(()=>{});_CourseSyncService.instance.entries.clear()}_CourseSyncService.instance=null}async ensureSynced(t,c){let u=this.entries.get(t);if(u?.status.state===`ready`&&u.localDB){if(!await this.isLocalEpochStale(t,u.localDB))return;logger.info(`[CourseSyncService] Remote DB epoch changed for course ${t} \u2014 destroying stale local replica`);try{await u.localDB.destroy()}catch{}u.localDB=null,u.readyPromise=null}if(u?.status.state===`disabled`)return;if(u?.readyPromise)return u.readyPromise;let d={localDB:null,status:{state:`not-started`},readyPromise:null};return this.entries.set(t,d),d.readyPromise=this.performSync(t,d,c),d.readyPromise}getLocalDB(t){let c=this.entries.get(t);return c?.status.state===`ready`&&c.localDB?c.localDB:null}isReady(t){return this.entries.get(t)?.status.state===`ready`}getStatus(t){return this.entries.get(t)?.status??{state:`not-started`}}async performSync(t,c,u){try{if(!u&&(c.status={state:`checking-config`},!await this.checkLocalSyncEnabled(t))){c.status={state:`disabled`},c.readyPromise=null,logger.debug(`[CourseSyncService] Local sync disabled for course ${t}`);return}c.status={state:`syncing`};let d=this.localDBName(t),m=new pouchdb_setup_default(d);await this.isLocalEpochStale(t,m)&&(logger.info(`[CourseSyncService] Stale local DB detected for course ${t} \u2014 destroying before sync`),await m.destroy(),m=new pouchdb_setup_default(d)),c.localDB=m;let g=this.getRemoteDB(t),b=Date.now();logger.info(`[CourseSyncService] Starting one-shot replication for course ${t} (batch_size=${this.replicationOptions.batchSize}, batches_limit=${this.replicationOptions.batchesLimit})`);let S=await this.replicate(g,m),C=Date.now()-b;logger.info(`[CourseSyncService] Replication complete for course ${t}: ${S.docs_written} docs in ${C}ms`),c.status={state:`warming-views`};let w=Date.now();await this.warmViewIndices(m);let T=Date.now()-w;logger.info(`[CourseSyncService] View indices warmed for course ${t} in ${T}ms`),c.status={state:`ready`,docsReplicated:S.docs_written,syncTimeMs:C,viewWarmTimeMs:T}}catch(u){let d=u instanceof Error?u.message:String(u);if(logger.error(`[CourseSyncService] Sync failed for course ${t}: ${d}`),c.status={state:`error`,error:d},c.readyPromise=null,c.localDB){try{await c.localDB.destroy()}catch{}c.localDB=null}}}async checkLocalSyncEnabled(t){try{return(await this.getRemoteDB(t).get(`CourseConfig`)).localSync?.enabled===!0}catch(c){return logger.warn(`[CourseSyncService] Could not read CourseConfig for ${t}, assuming local sync disabled: ${c}`),!1}}replicate(t,c){return new Promise((u,d)=>{pouchdb_setup_default.replicate(t,c,{batch_size:this.replicationOptions.batchSize,batches_limit:this.replicationOptions.batchesLimit}).on(`complete`,t=>{u(t)}).on(`error`,t=>{d(t)})})}async warmViewIndices(t){for(let c of[`elo`,`getTags`])try{await t.query(c,{limit:1}),logger.debug(`[CourseSyncService] Warmed view index: ${c}`)}catch(t){logger.debug(`[CourseSyncService] Could not warm view ${c}: ${t}`)}}async isLocalEpochStale(t,c){try{let u=await this.getRemoteDB(t).get(`db-epoch`),d=null;try{d=await c.get(`db-epoch`)}catch{return!0}return u.epoch!==d.epoch}catch{return!1}}getRemoteDB(t){return getCourseDB2(t)}localDBName(t){return`coursedb-local-${t}`}},_defineProperty$2(t,`instance`,null),t)}}),init_auth=__esm({"src/impl/couch/auth.ts"(){"use strict";init_factory(),init_logger()}}),CouchDBSyncStrategy_exports={},__export(CouchDBSyncStrategy_exports,{CouchDBSyncStrategy:()=>CouchDBSyncStrategy}),init_CouchDBSyncStrategy=__esm({"src/impl/couch/CouchDBSyncStrategy.ts"(){"use strict";init_factory(),init_types_legacy(),init_logger(),init_common(),init_pouchdb_setup(),init_couch(),init_auth(),log3=t=>{logger.info(t)},CouchDBSyncStrategy=class{constructor(){_defineProperty$2(this,`syncHandle`,void 0)}setupRemoteDB(t){return t===GuestUsername||t.startsWith(GuestUsername)?getLocalUserDB(t):this.getUserDB(t)}getWriteDB(t){return t===GuestUsername||t.startsWith(GuestUsername)?getLocalUserDB(t):this.getUserDB(t)}startSync(t,c){t!==c&&(this.syncHandle=pouchdb_setup_default.sync(t,c,{live:!0,retry:!0}))}stopSync(){this.syncHandle&&(this.syncHandle.cancel(),this.syncHandle=void 0)}canCreateAccount(){return!0}canAuthenticate(){return!0}async createAccount(t,c){let u=await this.getCurrentUsername(),d=u.startsWith(GuestUsername);d&&logger.info(`Creating account for funnel user ${u} -> ${t}`);try{let m=await this.getRemoteCouchRootDB().signUp(t,c);if(m.ok){log3(`CREATEACCOUNT: Successfully created account for ${t}`);try{let t=await this.getRemoteCouchRootDB().logOut();log3(`CREATEACCOUNT: logged out: ${t.ok}`)}catch{}let m=await this.getRemoteCouchRootDB().logIn(t,c);if(log3(`CREATEACCOUNT: logged in as new user: ${m.ok}`),m.ok){if(d){logger.info(`Migrating data from funnel account ${u} to ${t}`);let c=await this.migrateFunnelData(u,t);c.success||logger.warn(`Migration failed: ${c.error}`)}return{status:Status.ok,error:void 0}}else return{status:Status.error,error:`Failed to log in after account creation`}}else return logger.warn(`Signup not OK: ${JSON.stringify(m)}`),{status:Status.error,error:`Account creation failed`}}catch(t){return t.reason===`Document update conflict.`?{status:Status.error,error:`This username is taken!`}:(logger.error(`Error on signup: ${JSON.stringify(t)}`),{status:Status.error,error:t.message||`Unknown error during account creation`})}}async authenticate(t,c){try{return(await this.getRemoteCouchRootDB().logIn(t,c)).ok?(log3(`Successfully logged in as ${t}`),{ok:!0}):(log3(`Login failed for ${t}`),{ok:!1,error:`Invalid username or password`})}catch(c){return logger.error(`Authentication error for ${t}:`,c),{ok:!1,error:c.message||`Authentication failed`}}}async logout(){try{let t=await this.getRemoteCouchRootDB().logOut();return{ok:t.ok,error:t.ok?void 0:`Logout failed`}}catch(t){return logger.error(`Logout error:`,t),{ok:!1,error:t.message||`Logout failed`}}}async getCurrentUsername(){logger.log(`[funnel] CouchDBSyncStrategy.getCurrentUsername() called`);try{let t=await getLoggedInUsername();return logger.log(`[funnel] getLoggedInUsername() returned:`,t),t}catch(t){logger.log(`[funnel] getLoggedInUsername() failed, calling accomodateGuest()`),logger.log(`[funnel] Error was:`,t);let c=accomodateGuest();return logger.log(`[funnel] accomodateGuest() returned:`,c),c.username}}async migrateFunnelData(t,c){try{logger.info(`Starting data migration from ${t} to ${c}`);let u=getLocalUserDB(t),d=getLocalUserDB(c),m=await u.allDocs({include_docs:!0});logger.info(`Found ${m.rows.length} documents in funnel account`);let g=m.rows.filter(t=>!t.id.startsWith(`_design/`)).map(t=>({...t.doc,_rev:void 0}));return g.length>0?(await d.bulkDocs(g),logger.info(`Successfully migrated ${g.length} documents from ${t} to ${c}`)):logger.info(`No documents to migrate from funnel account`),{success:!0}}catch(t){return logger.error(`Migration failed:`,t),{success:!1,error:t instanceof Error?t.message:`Unknown error`}}}getRemoteCouchRootDB(){let t=ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+`skuilder`;try{return new pouchdb_setup_default(t,{skip_setup:!0})}catch(t){throw logger.error(`Failed to initialize remote CouchDB connection:`,t),Error(`Failed to initialize CouchDB: ${JSON.stringify(t)}`)}}getUserDB(t){let c=!1,u=`userdb-${hexEncode(t)}`;return log3(`Fetching user database: ${u} (${t})`),new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+u,createPouchDBConfig())}}}}),init_couch=__esm({"src/impl/couch/index.ts"(){"use strict";init_factory(),init_types_legacy(),init_logger(),init_pouchdb_setup(),init_contentSource(),init_adminDB2(),init_classroomDB2(),init_courseAPI(),init_courseDB(),init_CourseSyncService(),init_CouchDBSyncStrategy(),isBrowser=typeof window<`u`,isBrowser&&(window.process=import_browser.default),GUEST_LOCAL_DB=`userdb-${GuestUsername}`,new pouchdb_setup_default(GUEST_LOCAL_DB),pouchDBincludeCredentialsConfig={fetch(t,c){return c.credentials=`include`,pouchdb_setup_default.fetch(t,c)}},REVIEW_TIME_FORMAT2=`YYYY-MM-DD--kk:mm:ss-SSS`}}),init_BaseUserDB=__esm({"src/impl/common/BaseUserDB.ts"(){"use strict";var t;init_core(),init_util(),init_types_legacy(),init_logger(),init_userDBHelpers(),init_updateQueue(),init_user_course_relDB(),init_couch(),log4=t=>{logger.info(t)},BaseUser=(t=class _BaseUser{static Dummy(t){return new _BaseUser(`Me`,t)}getUsername(){return this._username}isLoggedIn(){return!this._username.startsWith(GuestUsername)}remote(){return this.remoteDB}async createAccount(t,c){if(!this.syncStrategy.canCreateAccount())throw Error(`Account creation not supported by current sync strategy`);if(!this._username.startsWith(GuestUsername))throw Error(`Cannot create a new account while logged in:
361
+ message: ${c.message}`),{status:Status.error,message:`Error adding note to course. ${t.reason||c.message}`}}}async getCourseDoc(t,c){return await this.db.get(t,c??{})}async getCourseDocs(t,c={}){return await this.db.allDocs({...c,keys:t})}getNavigationStrategy(t){if(logger.debug(`[courseDB] Getting navigation strategy: ${t}`),t==``){let t={_id:`NAVIGATION_STRATEGY-ELO`,docType:`NAVIGATION_STRATEGY`,name:`ELO`,description:`ELO-based navigation strategy for ordering content by difficulty`,implementingClass:`elo`,course:this.id,serializedData:``};return Promise.resolve(t)}else return this.db.get(t)}async getAllNavigationStrategies(){let t=DocTypePrefixes.NAVIGATION_STRATEGY;return(await this.db.allDocs({startkey:t,endkey:`${t}\uFFF0`,include_docs:!0})).rows.map(t=>t.doc)}async addNavigationStrategy(t){return logger.debug(`[courseDB] Adding navigation strategy: ${t._id}`),this.invalidateNavigatorCache(),this.remoteDB.put(t).then(()=>{})}updateNavigationStrategy(t,c){return logger.debug(`[courseDB] Updating navigation strategy: ${t}`),logger.debug(JSON.stringify(c)),Promise.resolve()}async createNavigator(t){try{let c=await this.getAllNavigationStrategies();if(c.length===0)return logger.debug(`[courseDB] No strategy documents found, using default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])`),createDefaultPipeline(t,this);let{pipeline:u,generatorStrategies:d,filterStrategies:m,warnings:g}=await new PipelineAssembler().assemble({strategies:c,user:t,course:this});for(let t of g)logger.warn(`[PipelineAssembler] ${t}`);return u?(logger.debug(`[courseDB] Using assembled pipeline with ${d.length} generator(s) and ${m.length} filter(s)`),u):(logger.debug(`[courseDB] Pipeline assembly failed, using default pipeline`),createDefaultPipeline(t,this))}catch(t){let c=t instanceof Error?`${t.message}
362
+ ${t.stack}`:JSON.stringify(t);throw logger.error(`[courseDB] Error creating navigator: ${c}`),t}}setEphemeralHints(t){this._pendingHints=t}async getWeightedCards(t){let c=await this._getCurrentUser();try{let{navigator:u}=await this._getCachedNavigator(c);return this._pendingHints&&(u.setEphemeralHints(this._pendingHints),this._pendingHints=null),await u.getWeightedCards(t)}catch(t){throw logger.error(`[courseDB] Error getting weighted cards: ${t}`),t}}async _getCachedNavigator(t){let c=t.getUsername(),u=Date.now();if(this._cachedNavigator&&this._cachedNavigator.userId===c&&u-this._cachedNavigator.builtAt<this._navigatorTtlMs)return{navigator:this._cachedNavigator.navigator,cacheStatus:`hit`};let d=await this.createNavigator(t);return this._cachedNavigator={navigator:d,userId:c,builtAt:u},{navigator:d,cacheStatus:`miss`}}invalidateNavigatorCache(){this._cachedNavigator=null}async getCardsCenteredAtELO(t={limit:99,elo:`user`},c){let u;if(t.elo===`user`){let t=await this._getCurrentUser();u=-1;try{u=EloToNumber((await t.getCourseRegistrationsDoc()).courses.find(t=>t.courseID===this.id).elo)}catch{u=1e3}}else if(t.elo===`random`){let t=await GET_CACHED(`elo-bounds-${this.id}`,()=>this.getELOBounds());u=Math.round(t.low+Math.random()*(t.high-t.low))}else u=t.elo;let d=Math.max(2e3,t.limit*4),m=Date.now(),g=`hit`;if(!this._eloPoolCache||m-this._eloPoolCache.fetchedAt>this._eloPoolTtlMs){let t=await this.getCardsByELO(u,d);t.length>0&&(this._eloPoolCache={rows:t,fetchedAt:m}),g=`miss`}let rankAgainstCurrentElo=()=>{let t=this._eloPoolCache?.rows??[];return(c?t.filter(t=>c(t)):t).map(t=>({...t})).sort((t,c)=>Math.abs((t.elo??u)-u)-Math.abs((c.elo??u)-u))},b=rankAgainstCurrentElo();if(b.length<t.limit&&(g===`hit`||!this._eloPoolCache)){let t=await this.getCardsByELO(u,d);t.length>0&&(this._eloPoolCache={rows:t,fetchedAt:m}),b=rankAgainstCurrentElo(),g=`refresh`}let S=[];for(;S.length<t.limit&&b.length>0;){let t=randIntWeightedTowardZero(b.length),c=b.splice(t,1)[0];S.push(c)}return S.map(t=>({courseID:this.id,cardID:t.cardID,contentSourceType:`course`,contentSourceID:this.id,elo:t.elo,status:`new`}))}async searchCards(t){logger.log(`[CourseDB ${this.id}] Searching for: "${t}"`);let c;try{c=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`,"data.0.data":{$regex:`.*${t}.*`}}}),logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`)}catch(u){logger.log(`[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,u);let d=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`}});logger.log(`[CourseDB ${this.id}] Retrieved ${d.docs.length} documents for manual filtering`),c={docs:d.docs.filter(c=>{let u=JSON.stringify(c).toLowerCase().includes(t.toLowerCase());return u&&logger.log(`[CourseDB ${this.id}] Manual match found in document: ${c._id}`),u})}}if(logger.log(`[CourseDB ${this.id}] Found ${c.docs.length} displayable data documents`),c.docs.length===0){let t=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`},limit:5});logger.log(`[CourseDB ${this.id}] Sample displayable data:`,t.docs.map(t=>({id:t._id,docType:t.docType,dataStructure:t.data?Object.keys(t.data):`no data field`,dataContent:t.data,fullDoc:t})))}let u=[];for(let t of c.docs){let c=await this.db.find({selector:{docType:`CARD`,id_displayable_data:{$in:[t._id]}}});logger.log(`[CourseDB ${this.id}] Displayable data ${t._id} linked to ${c.docs.length} cards`),u.push(...c.docs)}return logger.log(`[CourseDB ${this.id}] Total cards found: ${u.length}`),u}async find(t){return this.db.find(t)}}}}),init_classroomDB2=__esm({"src/impl/couch/classroomDB.ts"(){"use strict";init_factory(),init_logger(),init_pouchdb_setup(),init_couch(),init_courseDB(),classroomLookupDBTitle=`classdb-lookup`,CLASSROOM_CONFIG=`ClassroomConfig`,ClassroomDBBase=class{constructor(){_defineProperty$2(this,`_id`,void 0),_defineProperty$2(this,`_db`,void 0),_defineProperty$2(this,`_cfg`,void 0),_defineProperty$2(this,`_initComplete`,!1),_defineProperty$2(this,`_content_prefix`,`content`)}get _content_searchkeys(){return getStartAndEndKeys2(this._content_prefix)}async getAssignedContent(){return logger.info(`Getting assigned content...`),(await this._db.allDocs({startkey:this._content_prefix,endkey:this._content_prefix+`￰`,include_docs:!0})).rows.map(t=>t.doc)}getContentId(t){return t.type===`tag`?`${this._content_prefix}-${t.courseID}-${t.tagID}`:`${this._content_prefix}-${t.courseID}`}get ready(){return this._initComplete}getConfig(){return this._cfg}},StudentClassroomDB=class _StudentClassroomDB extends ClassroomDBBase{constructor(t,c){super(),_defineProperty$2(this,`userMessages`,void 0),_defineProperty$2(this,`_user`,void 0),this._id=t,this._user=c}async init(){let t=`classdb-student-${this._id}`;this._db=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+t,createPouchDBConfig());try{this._cfg=await this._db.get(CLASSROOM_CONFIG),this.userMessages=this._db.changes({since:`now`,live:!0,include_docs:!0}),this._initComplete=!0;return}catch(t){throw Error(`Error in StudentClassroomDB constructor: ${JSON.stringify(t)}`)}}static async factory(t,c){let u=new _StudentClassroomDB(t,c);return await u.init(),u}setChangeFcn(t){this.userMessages.on(`change`,t)}async getWeightedCards(t){let c=[],u=(await this._user.getPendingReviews()).filter(t=>t.scheduledFor===`classroom`&&t.schedulingAgentId===this._id);for(let t of u)c.push({cardId:t.cardId,courseId:t.courseId,score:1,reviewID:t._id,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom scheduled review`}]});let d=await this._user.getActiveCards(),m=new Set(d.map(t=>t.cardID)),g=hooks.utc(),b=(await this.getAssignedContent()).filter(t=>g.isAfter(hooks.utc(t.activeOn,REVIEW_TIME_FORMAT2)));logger.info(`[StudentClassroomDB] Due content: ${JSON.stringify(b)}`);for(let u of b)if(u.type===`course`){let{cards:d}=await new CourseDB(u.courseID,async()=>this._user).getWeightedCards(t);for(let t of d)m.has(t.cardId)||c.push({...t,provenance:[...t.provenance,{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`passed`,score:t.score,reason:`Assigned via classroom from course ${u.courseID}`}]})}else if(u.type===`tag`){let t=await getTag(u.courseID,u.tagID);for(let d of t.taggedCards)m.has(d)||c.push({cardId:d,courseId:u.courseID,score:1,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom assigned tag: ${u.tagID}, new card`}]})}else u.type===`card`&&(m.has(u.cardID)||c.push({cardId:u.cardID,courseId:u.courseID,score:1,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom assigned card, new card`}]}));return logger.info(`[StudentClassroomDB] New cards from classroom ${this._cfg.name}: ${c.length} total (reviews + new)`),{cards:c.sort((t,c)=>c.score-t.score).slice(0,t)}}},TeacherClassroomDB=class _TeacherClassroomDB extends ClassroomDBBase{constructor(t){super(),_defineProperty$2(this,`_stuDb`,void 0),this._id=t}async init(){let t=`classdb-teacher-${this._id}`,c=`classdb-student-${this._id}`;this._db=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+t,createPouchDBConfig()),this._stuDb=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+c,createPouchDBConfig());try{return this._db.get(CLASSROOM_CONFIG).then(t=>{this._cfg=t,this._initComplete=!0}).then(()=>{})}catch(t){throw Error(`Error in TeacherClassroomDB constructor: ${JSON.stringify(t)}`)}}static async factory(t){let c=new _TeacherClassroomDB(t);return await c.init(),c}async removeContent(t){let c=this.getContentId(t);try{let t=await this._db.get(c);await this._db.remove(t),this._db.replicate.to(this._stuDb,{doc_ids:[c]})}catch(t){logger.error(`Failed to remove content:`,c,t)}}async assignContent(t){let c,u=this.getContentId(t);return c=t.type===`tag`?await this._db.put({courseID:t.courseID,tagID:t.tagID,type:`tag`,_id:u,assignedBy:t.assignedBy,assignedOn:hooks.utc(),activeOn:t.activeOn||hooks.utc()}):await this._db.put({courseID:t.courseID,type:`course`,_id:u,assignedBy:t.assignedBy,assignedOn:hooks.utc(),activeOn:t.activeOn||hooks.utc()}),c.ok?(this._db.replicate.to(this._stuDb,{doc_ids:[u]}),!0):!1}},ClassroomLookupDB=()=>new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+classroomLookupDBTitle,{skip_setup:!0})}}),init_adminDB2=__esm({"src/impl/couch/adminDB.ts"(){"use strict";init_pouchdb_setup(),init_factory(),init_couch(),init_classroomDB2(),init_courseLookupDB(),init_logger(),AdminDB=class{constructor(){_defineProperty$2(this,`usersDB`,void 0),this.usersDB=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+`_users`,createPouchDBConfig())}async getUsers(){return(await this.usersDB.allDocs({include_docs:!0,...getStartAndEndKeys2(`org.couchdb.user:`)})).rows.map(t=>t.doc)}async getCourses(){let t=await CourseLookup.allCourseWare();return await Promise.all(t.map(t=>getCredentialledCourseConfig(t._id)))}async removeCourse(t){let c=await CourseLookup.delete(t),u=await getCredentialledCourseConfig(t);u.deleted=!0;let d=await updateCredentialledCourseConfig(t,u);return{ok:c.ok&&d.ok,id:c.id,rev:c.rev}}async getClassrooms(){let t=(await ClassroomLookupDB().allDocs({include_docs:!0})).rows.map(t=>t.doc.uuid);logger.debug(t.join(`, `));let c=[];for(let u=0;u<t.length;u++)try{let d=await TeacherClassroomDB.factory(t[u]);c.push(d)}catch(c){let d=c;d.error&&d.error===`not_found`&&logger.warn(`db ${t[u]} not found`)}return c.map(t=>({...t.getConfig(),_id:t._id}))}}}}),CourseSyncService_exports={},__export(CourseSyncService_exports,{CourseSyncService:()=>CourseSyncService}),init_CourseSyncService=__esm({"src/impl/couch/CourseSyncService.ts"(){"use strict";var t;init_pouchdb_setup(),init_couch(),init_logger(),DEFAULT_REPLICATION={batchSize:1e3,batchesLimit:5},CourseSyncService=(t=class _CourseSyncService{constructor(){_defineProperty$2(this,`entries`,new Map),_defineProperty$2(this,`replicationOptions`,DEFAULT_REPLICATION)}static getInstance(){return _CourseSyncService.instance||(_CourseSyncService.instance=new _CourseSyncService),_CourseSyncService.instance}configure(t){t.replication&&(this.replicationOptions={...DEFAULT_REPLICATION,...t.replication},logger.info(`[CourseSyncService] Replication configured: batch_size=${this.replicationOptions.batchSize}, batches_limit=${this.replicationOptions.batchesLimit}`))}static resetInstance(){if(_CourseSyncService.instance){for(let[,t]of _CourseSyncService.instance.entries)t.localDB&&t.localDB.close().catch(()=>{});_CourseSyncService.instance.entries.clear()}_CourseSyncService.instance=null}async ensureSynced(t,c){let u=this.entries.get(t);if(u?.status.state===`ready`&&u.localDB){if(!await this.isLocalEpochStale(t,u.localDB))return;logger.info(`[CourseSyncService] Remote DB epoch changed for course ${t} \u2014 destroying stale local replica`);try{await u.localDB.destroy()}catch{}u.localDB=null,u.readyPromise=null}if(u?.status.state===`disabled`)return;if(u?.readyPromise)return u.readyPromise;let d={localDB:null,status:{state:`not-started`},readyPromise:null};return this.entries.set(t,d),d.readyPromise=this.performSync(t,d,c),d.readyPromise}getLocalDB(t){let c=this.entries.get(t);return c?.status.state===`ready`&&c.localDB?c.localDB:null}isReady(t){return this.entries.get(t)?.status.state===`ready`}getStatus(t){return this.entries.get(t)?.status??{state:`not-started`}}async performSync(t,c,u){try{if(!u&&(c.status={state:`checking-config`},!await this.checkLocalSyncEnabled(t))){c.status={state:`disabled`},c.readyPromise=null,logger.debug(`[CourseSyncService] Local sync disabled for course ${t}`);return}c.status={state:`syncing`};let d=this.localDBName(t),m=new pouchdb_setup_default(d);await this.isLocalEpochStale(t,m)&&(logger.info(`[CourseSyncService] Stale local DB detected for course ${t} \u2014 destroying before sync`),await m.destroy(),m=new pouchdb_setup_default(d)),c.localDB=m;let g=this.getRemoteDB(t),b=Date.now();logger.info(`[CourseSyncService] Starting one-shot replication for course ${t} (batch_size=${this.replicationOptions.batchSize}, batches_limit=${this.replicationOptions.batchesLimit})`);let S=await this.replicate(g,m),C=Date.now()-b;logger.info(`[CourseSyncService] Replication complete for course ${t}: ${S.docs_written} docs in ${C}ms`),c.status={state:`warming-views`};let w=Date.now();await this.warmViewIndices(m);let T=Date.now()-w;logger.info(`[CourseSyncService] View indices warmed for course ${t} in ${T}ms`),c.status={state:`ready`,docsReplicated:S.docs_written,syncTimeMs:C,viewWarmTimeMs:T}}catch(u){let d=u instanceof Error?u.message:String(u);if(logger.error(`[CourseSyncService] Sync failed for course ${t}: ${d}`),c.status={state:`error`,error:d},c.readyPromise=null,c.localDB){try{await c.localDB.destroy()}catch{}c.localDB=null}}}async checkLocalSyncEnabled(t){try{return(await this.getRemoteDB(t).get(`CourseConfig`)).localSync?.enabled===!0}catch(c){return logger.warn(`[CourseSyncService] Could not read CourseConfig for ${t}, assuming local sync disabled: ${c}`),!1}}replicate(t,c){return new Promise((u,d)=>{pouchdb_setup_default.replicate(t,c,{batch_size:this.replicationOptions.batchSize,batches_limit:this.replicationOptions.batchesLimit}).on(`complete`,t=>{u(t)}).on(`error`,t=>{d(t)})})}async warmViewIndices(t){for(let c of[`elo`,`getTags`])try{await t.query(c,{limit:1}),logger.debug(`[CourseSyncService] Warmed view index: ${c}`)}catch(t){logger.debug(`[CourseSyncService] Could not warm view ${c}: ${t}`)}}async isLocalEpochStale(t,c){try{let u=await this.getRemoteDB(t).get(`db-epoch`),d=null;try{d=await c.get(`db-epoch`)}catch{return!0}return u.epoch!==d.epoch}catch{return!1}}getRemoteDB(t){return getCourseDB2(t)}localDBName(t){return`coursedb-local-${t}`}},_defineProperty$2(t,`instance`,null),t)}}),init_auth=__esm({"src/impl/couch/auth.ts"(){"use strict";init_factory(),init_logger()}}),CouchDBSyncStrategy_exports={},__export(CouchDBSyncStrategy_exports,{CouchDBSyncStrategy:()=>CouchDBSyncStrategy}),init_CouchDBSyncStrategy=__esm({"src/impl/couch/CouchDBSyncStrategy.ts"(){"use strict";init_factory(),init_types_legacy(),init_logger(),init_common(),init_pouchdb_setup(),init_couch(),init_auth(),log3=t=>{logger.info(t)},CouchDBSyncStrategy=class{constructor(){_defineProperty$2(this,`syncHandle`,void 0)}setupRemoteDB(t){return t===GuestUsername||t.startsWith(GuestUsername)?getLocalUserDB(t):this.getUserDB(t)}getWriteDB(t){return t===GuestUsername||t.startsWith(GuestUsername)?getLocalUserDB(t):this.getUserDB(t)}startSync(t,c){t!==c&&(this.syncHandle=pouchdb_setup_default.sync(t,c,{live:!0,retry:!0}))}stopSync(){this.syncHandle&&(this.syncHandle.cancel(),this.syncHandle=void 0)}canCreateAccount(){return!0}canAuthenticate(){return!0}async createAccount(t,c){let u=await this.getCurrentUsername(),d=u.startsWith(GuestUsername);d&&logger.info(`Creating account for funnel user ${u} -> ${t}`);try{let m=await this.getRemoteCouchRootDB().signUp(t,c);if(m.ok){log3(`CREATEACCOUNT: Successfully created account for ${t}`);try{let t=await this.getRemoteCouchRootDB().logOut();log3(`CREATEACCOUNT: logged out: ${t.ok}`)}catch{}let m=await this.getRemoteCouchRootDB().logIn(t,c);if(log3(`CREATEACCOUNT: logged in as new user: ${m.ok}`),m.ok){if(d){logger.info(`Migrating data from funnel account ${u} to ${t}`);let c=await this.migrateFunnelData(u,t);c.success||logger.warn(`Migration failed: ${c.error}`)}return{status:Status.ok,error:void 0}}else return{status:Status.error,error:`Failed to log in after account creation`}}else return logger.warn(`Signup not OK: ${JSON.stringify(m)}`),{status:Status.error,error:`Account creation failed`}}catch(t){return t.reason===`Document update conflict.`?{status:Status.error,error:`This username is taken!`}:(logger.error(`Error on signup: ${JSON.stringify(t)}`),{status:Status.error,error:t.message||`Unknown error during account creation`})}}async authenticate(t,c){try{return(await this.getRemoteCouchRootDB().logIn(t,c)).ok?(log3(`Successfully logged in as ${t}`),{ok:!0}):(log3(`Login failed for ${t}`),{ok:!1,error:`Invalid username or password`})}catch(c){return logger.error(`Authentication error for ${t}:`,c),{ok:!1,error:c.message||`Authentication failed`}}}async logout(){try{let t=await this.getRemoteCouchRootDB().logOut();return{ok:t.ok,error:t.ok?void 0:`Logout failed`}}catch(t){return logger.error(`Logout error:`,t),{ok:!1,error:t.message||`Logout failed`}}}async getCurrentUsername(){logger.log(`[funnel] CouchDBSyncStrategy.getCurrentUsername() called`);try{let t=await getLoggedInUsername();return logger.log(`[funnel] getLoggedInUsername() returned:`,t),t}catch(t){logger.log(`[funnel] getLoggedInUsername() failed, calling accomodateGuest()`),logger.log(`[funnel] Error was:`,t);let c=accomodateGuest();return logger.log(`[funnel] accomodateGuest() returned:`,c),c.username}}async migrateFunnelData(t,c){try{logger.info(`Starting data migration from ${t} to ${c}`);let u=getLocalUserDB(t),d=getLocalUserDB(c),m=await u.allDocs({include_docs:!0});logger.info(`Found ${m.rows.length} documents in funnel account`);let g=m.rows.filter(t=>!t.id.startsWith(`_design/`)).map(t=>({...t.doc,_rev:void 0}));return g.length>0?(await d.bulkDocs(g),logger.info(`Successfully migrated ${g.length} documents from ${t} to ${c}`)):logger.info(`No documents to migrate from funnel account`),{success:!0}}catch(t){return logger.error(`Migration failed:`,t),{success:!1,error:t instanceof Error?t.message:`Unknown error`}}}getRemoteCouchRootDB(){let t=ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+`skuilder`;try{return new pouchdb_setup_default(t,{skip_setup:!0})}catch(t){throw logger.error(`Failed to initialize remote CouchDB connection:`,t),Error(`Failed to initialize CouchDB: ${JSON.stringify(t)}`)}}getUserDB(t){let c=!1,u=`userdb-${hexEncode(t)}`;return log3(`Fetching user database: ${u} (${t})`),new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+u,createPouchDBConfig())}}}}),init_couch=__esm({"src/impl/couch/index.ts"(){"use strict";init_factory(),init_types_legacy(),init_logger(),init_pouchdb_setup(),init_contentSource(),init_adminDB2(),init_classroomDB2(),init_courseAPI(),init_courseDB(),init_CourseSyncService(),init_CouchDBSyncStrategy(),isBrowser=typeof window<`u`,isBrowser&&(window.process=import_browser.default),GUEST_LOCAL_DB=`userdb-${GuestUsername}`,new pouchdb_setup_default(GUEST_LOCAL_DB),pouchDBincludeCredentialsConfig={fetch(t,c){return c.credentials=`include`,pouchdb_setup_default.fetch(t,c)}},REVIEW_TIME_FORMAT2=`YYYY-MM-DD--kk:mm:ss-SSS`}}),init_BaseUserDB=__esm({"src/impl/common/BaseUserDB.ts"(){"use strict";var t;init_core(),init_util(),init_types_legacy(),init_logger(),init_userDBHelpers(),init_updateQueue(),init_user_course_relDB(),init_couch(),log4=t=>{logger.info(t)},BaseUser=(t=class _BaseUser{static Dummy(t){return new _BaseUser(`Me`,t)}getUsername(){return this._username}isLoggedIn(){return!this._username.startsWith(GuestUsername)}remote(){return this.remoteDB}async createAccount(t,c){if(!this.syncStrategy.canCreateAccount())throw Error(`Account creation not supported by current sync strategy`);if(!this._username.startsWith(GuestUsername))throw Error(`Cannot create a new account while logged in:
363
363
  Currently logged-in as ${this._username}.`);let u=await this.syncStrategy.createAccount(t,c);if(u.status===Status.ok){log4(`Account created successfully, updating username to ${t}`),this._username=t;try{localStorage.removeItem(`sk-guest-uuid`)}catch(t){logger.warn(`localStorage not available (Node.js environment):`,t)}await this.init()}return{status:u.status,error:u.error||``}}async login(t,c){if(!this.syncStrategy.canAuthenticate())throw Error(`Authentication not supported by current sync strategy`);if(!this._username.startsWith(GuestUsername)&&this._username!=t){if(this._username!=t)throw Error(`Cannot change accounts while logged in.
364
364
  Log out of account ${this.getUsername()} before logging in as ${t}.`);logger.warn(`User ${this._username} is already logged in, but executing login again.`)}let u=await this.syncStrategy.authenticate(t,c);if(u.ok){log4(`Logged in as ${t}`),this._username=t;try{localStorage.removeItem(`sk-guest-uuid`)}catch(t){logger.warn(`localStorage not available (Node.js environment):`,t)}await this.init()}return u}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 t=getLocalUserDB(this._username),c=(await t.allDocs({include_docs:!1})).rows.filter(t=>{let c=t.id;return c.startsWith(DocTypePrefixes.CARDRECORD)||c.startsWith(DocTypePrefixes.SCHEDULED_CARD)||c.startsWith(DocTypePrefixes.STRATEGY_STATE)||c.startsWith(DocTypePrefixes.USER_OUTCOME)||c.startsWith(DocTypePrefixes.STRATEGY_LEARNING_STATE)||c===_BaseUser.DOC_IDS.COURSE_REGISTRATIONS||c===_BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS||c===_BaseUser.DOC_IDS.CONFIG}).map(t=>({_id:t.id,_rev:t.value.rev,_deleted:!0}));return c.length>0&&await t.bulkDocs(c),await this.init(),{status:Status.ok}}catch(t){return logger.error(`Failed to reset user data:`,t),{status:Status.error,error:t instanceof Error?t.message:`Unknown error during reset`}}}async logout(){if(!this.syncStrategy.canAuthenticate())return this._username=await this.syncStrategy.getCurrentUsername(),await this.init(),{ok:!0};let t=await this.syncStrategy.logout();return this._username=await this.syncStrategy.getCurrentUsername(),await this.init(),t}async get(t){return this.localDB.get(t)}update(t,c){return this.updateQueue.update(t,c)}async getCourseRegistrationsDoc(){logger.debug(`Fetching courseRegistrations for ${this.getUsername()}`);let t;try{return await this.localDB.get(_BaseUser.DOC_IDS.COURSE_REGISTRATIONS)}catch(c){if(c.status===404)await this.localDB.put({_id:_BaseUser.DOC_IDS.COURSE_REGISTRATIONS,courses:[],studyWeight:{}}),t=await this.getCourseRegistrationsDoc();else throw Error(`Unexpected error ${JSON.stringify(c)} in getOrCreateCourseRegistrationDoc...`)}return t}async getActiveCourses(){return(await this.getCourseRegistrationsDoc()).courses.filter(t=>t.status===void 0||t.status===`active`)}async getActiveCards(){let t=getStartAndEndKeys(DocTypePrefixes.SCHEDULED_CARD);return(await this.remoteDB.allDocs({startkey:t.startkey,endkey:t.endkey,include_docs:!0})).rows.map(t=>({courseID:t.doc.courseId,cardID:t.doc.cardId}))}async getActivityRecords(){try{let t=await this.getHistory(),c=[];if(!Array.isArray(t))return logger.error(`getHistory did not return an array:`,t),c;let u=0;for(let d=0;d<t.length;d++)try{t[d]&&Array.isArray(t[d].records)&&t[d].records.forEach(t=>{try{if(!t.timeStamp)return;let d;if(typeof t.timeStamp==`object`)if(typeof t.timeStamp.toDate==`function`)d=t.timeStamp.toISOString();else if(t.timeStamp instanceof Date)d=t.timeStamp.toISOString();else{u<3&&(logger.warn(`Unknown timestamp object type:`,t.timeStamp),u++);return}else if(typeof t.timeStamp==`string`){let c=new Date(t.timeStamp);if(isNaN(c.getTime()))return;d=t.timeStamp}else if(typeof t.timeStamp==`number`)d=new Date(t.timeStamp).toISOString();else return;c.push({timeStamp:d,courseID:t.courseID||`unknown`,cardID:t.cardID||`unknown`,timeSpent:t.timeSpent||0,type:`card_view`})}catch{}})}catch(t){logger.error(`Error processing history item:`,t)}return logger.debug(`Found ${c.length} activity records`),c}catch(t){return logger.error(`Error in getActivityRecords:`,t),[]}}async getReviewstoDate(t,c){let u=getStartAndEndKeys(DocTypePrefixes.SCHEDULED_CARD),d=await this.remoteDB.allDocs({startkey:u.startkey,endkey:u.endkey,include_docs:!0});return log4(`Fetching ${this._username}'s scheduled reviews${c?` for course ${c}`:``}.`),d.rows.filter(u=>{if(u.id.startsWith(DocTypePrefixes.SCHEDULED_CARD)){let d=hooks.utc(u.id.substr(DocTypePrefixes.SCHEDULED_CARD.length),REVIEW_TIME_FORMAT);if(t.isAfter(d)&&(c===void 0||u.doc.courseId===c))return!0}}).map(t=>t.doc)}async getReviewsForcast(t){let c=hooks.utc().add(t,`days`);return this.getReviewstoDate(c)}async getPendingReviews(t){let c=hooks.utc();return this.getReviewstoDate(c,t)}async getScheduledReviewCount(t){return(await this.getPendingReviews(t)).length}async getRegisteredCourses(){return(await this.getCourseRegistrationsDoc()).courses.filter(t=>!t.status||t.status===`active`||t.status===`maintenance-mode`)}async getCourseRegDoc(t){let c=(await this.getCourseRegistrationsDoc()).courses.find(c=>c.courseID===t);if(c)return c;throw Error(`Course registration not found for course ID: ${t}`)}async registerForCourse(t,c=!1){return this.getCourseRegistrationsDoc().then(u=>{let d=c?`preview`:`active`;logger.debug(`Registering for ${t} with status: ${d}`);let m={status:d,courseID:t,user:!0,admin:!1,moderator:!1,elo:{global:{score:1e3,count:0},tags:{},misc:{}}};return u.courses.filter(t=>t.courseID===m.courseID).length===0?(log4(`It's a new course registration!`),u.courses.push(m),u.studyWeight[t]=1):u.courses.forEach(c=>{log4(`Found the previously registered course!`),c.courseID===t&&(c.status=d)}),this.localDB.put(u)}).catch(t=>{throw log4(`Registration failed because of: ${JSON.stringify(t)}`),t})}async dropCourse(t,c=`dropped`){return this.getCourseRegistrationsDoc().then(u=>{let d=-1;for(let c=0;c<u.courses.length;c++)u.courses[c].courseID===t&&(d=c);if(d!==-1)delete u.studyWeight[t],u.courses[d].status=c;else throw Error(`User ${this.getUsername()} is not currently registered for course ${t}`);return this.localDB.put(u)})}async getCourseInterface(t){return new UsrCrsData(this,t)}async getUserEditableCourses(){let t=[],c=await this.getCourseRegistrationsDoc();return t=t.concat(c.courses.map(t=>t.courseID)),await Promise.all(t.map(async t=>await getCredentialledCourseConfig(t)))}async getConfig(){let t={_id:_BaseUser.DOC_IDS.CONFIG,darkMode:!1,likesConfetti:!1,sessionTimeLimit:5};try{let t=await this.localDB.get(_BaseUser.DOC_IDS.CONFIG);return logger.debug(`Raw config from DB:`,t),t}catch(c){let u=c;if(u.name&&u.name===`not_found`)return await this.localDB.put(t),this.getConfig();throw logger.error(`Error setting user default config:`,c),Error(`Error returning the user's configuration: ${JSON.stringify(c)}`)}}async setConfig(t){logger.debug(`Setting Config items ${JSON.stringify(t)}`);let c=await this.getConfig(),u=await this.localDB.put({...c,...t});u.ok?logger.debug(`Config items set: ${JSON.stringify(t)}`):logger.error(`Error setting config items: ${JSON.stringify(u)}`)}static async instance(t,c){return c?(_BaseUser._instance=new _BaseUser(c,t),await _BaseUser._instance.init(),_BaseUser._instance):_BaseUser._instance&&_BaseUser._initialized?_BaseUser._instance:_BaseUser._instance?new Promise(t=>{(function waitForUser(){if(_BaseUser._initialized)return t(_BaseUser._instance);setTimeout(waitForUser,50)})()}):(_BaseUser._instance=new _BaseUser(await t.getCurrentUsername(),t),await _BaseUser._instance.init(),_BaseUser._instance)}constructor(t,c){_defineProperty$2(this,`_username`,void 0),_defineProperty$2(this,`syncStrategy`,void 0),_defineProperty$2(this,`localDB`,void 0),_defineProperty$2(this,`remoteDB`,void 0),_defineProperty$2(this,`writeDB`,void 0),_defineProperty$2(this,`updateQueue`,void 0),_BaseUser._initialized=!1,this._username=t,this.syncStrategy=c,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(t=>{log4(`Error in applyDesignDocs background task: ${t}`),t&&typeof t==`object`&&log4(`Full error details in applyDesignDocs: ${JSON.stringify(t)}`)}),this.deduplicateReviews().catch(t=>{log4(`Error in deduplicateReviews background task: ${t}`),t&&typeof t==`object`&&log4(`Full error details in background task: ${JSON.stringify(t)}`)}),_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 t of _BaseUser.designDocs){log4(`Applying design doc: ${t._id}`);try{try{let c=await this.remoteDB.get(t._id);await this.remoteDB.put({...t,_rev:c._rev})}catch(c){if(c?.name===`not_found`)await this.remoteDB.put(t);else throw c}}catch(c){if(c.name&&c.name===`conflict`)logger.warn(`Design doc ${t._id} update conflict - will retry`),await new Promise(t=>setTimeout(t,1e3)),await this.applyDesignDoc(t);else throw logger.error(`Failed to apply design doc ${t._id}:`,c),c}}}async applyDesignDoc(t,c=3){try{let c=await this.remoteDB.get(t._id);await this.remoteDB.put({...t,_rev:c._rev})}catch(u){if(u?.name===`conflict`&&c>0)return await new Promise(t=>setTimeout(t,1e3)),this.applyDesignDoc(t,c-1);throw u}}async putCardRecord(t){let c=getCardHistoryID(t.courseID,t.cardID);t.timeStamp=hooks.utc(t.timeStamp).toString();try{let u=await this.update(c,function(c){return c.records.push(t),c.bestInterval=c.bestInterval||0,c.lapses=c.lapses||0,c.streak=c.streak||0,c});return u.records=u.records.map(t=>{let c={...t};return c.timeStamp=hooks.utc(t.timeStamp),c}),u}catch(u){let d=u;if(d.status===404)try{let u={_id:c,cardID:t.cardID,courseID:t.courseID,records:[t],lapses:0,streak:0,bestInterval:0},d=await this.writeDB.put(u);return{...u,_rev:d.rev}}catch(t){throw Error(`Failed to create CardHistory for ${c}. Reason: ${t}`)}else throw Error(`putCardRecord failed because of:
365
365
  name:${d.name}
@@ -404,7 +404,7 @@ Examples:
404
404
  User ELO update: ${S?`SUCCESS`:`FAILED`}
405
405
  Card ELO update: ${C?`SUCCESS`:`FAILED`}`),!S&&m[0].status===`rejected`&&logger.error(`[EloService] User ELO update error:`,m[0].reason),!C&&m[1].status===`rejected`&&logger.error(`[EloService] Card ELO update error:`,m[1].reason)}}},init_core(),init_logger(),ResponseProcessor=class{constructor(t,c){_defineProperty$2(this,`srsService`,void 0),_defineProperty$2(this,`eloService`,void 0),this.srsService=t,this.eloService=c}parsePerformance(t){return typeof t==`number`?{globalScore:t,taggedPerformance:null}:isTaggedPerformance(t)?{globalScore:t._global,taggedPerformance:t}:(logger.warn(`[ResponseProcessor] Unexpected performance structure, using neutral score`,{performance:t}),{globalScore:.5,taggedPerformance:null})}async processResponse(t,c,u,d,m,g,b,S,C,w){if(!isQuestionRecord(t))return{nextCardAction:`dismiss-success`,shouldLoadNextCard:!0,isCorrect:!0,shouldClearFeedbackShadow:!0};try{let T=await c,E;return E=t.isCorrect?this.processCorrectResponse(t,T,u,d,m,g,b):this.processIncorrectResponse(t,T,d,m,g,b,S,C,w),t.deferAdvance&&E.shouldLoadNextCard&&(logger.info(`[ResponseProcessor] deferAdvance requested — suppressing navigation, action stashed:`,{nextCardAction:E.nextCardAction}),E={...E,shouldLoadNextCard:!1,deferred:!0}),E}catch(t){throw logger.error(`[ResponseProcessor] Failed to load card history`,{e:t,cardId:b}),t}}processCorrectResponse(t,c,u,d,m,g,b){if(t.priorAttemps===0){m.card.tags.includes(`srs:skip`)||this.srsService.scheduleReview(c,u);let{globalScore:S,taggedPerformance:C}=this.parsePerformance(t.performance);if(C){let t=Object.keys(C).filter(t=>t!==`_global`),c=t.filter(t=>C[t]===null),u=t.filter(t=>C[t]!==null);logger.info(`[ResponseProcessor] per-tag ELO update for ${b}: scored=[${u.join(`, `)}] count-only=[${c.join(`, `)}]`),this.eloService.updateUserAndCardEloPerTag(C,g,b,d,m)}else{let t=.5+S/2;if(c.records.length===1)this.eloService.updateUserAndCardElo(t,g,b,d,m);else{let u=Math.ceil(32/c.records.length);this.eloService.updateUserAndCardElo(t,g,b,d,m,u)}logger.info(`[ResponseProcessor] Processed correct response with SRS scheduling and ELO update`)}return{nextCardAction:`dismiss-success`,shouldLoadNextCard:!0,isCorrect:!0,performanceScore:S,shouldClearFeedbackShadow:!0}}else{logger.info(`[ResponseProcessor] Processed correct response (retry attempt - no scheduling/ELO)`);let{globalScore:c}=this.parsePerformance(t.performance);return{nextCardAction:`marked-failed`,shouldLoadNextCard:!0,isCorrect:!0,performanceScore:c,shouldClearFeedbackShadow:!0}}}processIncorrectResponse(t,c,u,d,m,g,b,S,C){let{taggedPerformance:w}=this.parsePerformance(t.performance);return c.records.length!==1&&t.priorAttemps===0?w?(this.eloService.updateUserAndCardEloPerTag(w,m,g,u,d),logger.info(`[ResponseProcessor] Processed incorrect response with per-tag ELO update (${Object.keys(w).length-1} tags)`)):(this.eloService.updateUserAndCardElo(0,m,g,u,d),logger.info(`[ResponseProcessor] Processed incorrect response with ELO update`)):logger.info(`[ResponseProcessor] Processed incorrect response (no ELO update needed)`),d.records.length>=b?C>=S?(w?this.eloService.updateUserAndCardEloPerTag(w,m,g,u,d):this.eloService.updateUserAndCardElo(0,m,g,u,d),{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(),CardHydrationService=class{constructor(t,c,u){_defineProperty$2(this,`hydratedCards`,new Map),_defineProperty$2(this,`hydrationInFlight`,new Set),_defineProperty$2(this,`hydrationInProgress`,!1),this.getViewComponent=t,this.getCourseDB=c,this.getItemsToHydrate=u}getHydratedCard(t){return this.hydratedCards.get(t)??null}hasHydratedCard(t){return this.hydratedCards.has(t)}removeCard(t){this.hydratedCards.delete(t)}async ensureHydratedCards(){this.fillHydratedCards()}async waitForCard(t){if(this.hydratedCards.has(t))return this.hydratedCards.get(t);this.hydrationInProgress||this.fillHydratedCards();let c=1e4,u=25,d=0;for(;d<1e4;){if(this.hydratedCards.has(t))return this.hydratedCards.get(t);if(!this.hydrationInFlight.has(t)&&!this.hydrationInProgress)break;await new Promise(t=>setTimeout(t,25)),d+=25}return this.hydratedCards.get(t)??null}get hydratedCount(){return this.hydratedCards.size}getHydratedCardIds(){return Array.from(this.hydratedCards.keys())}async fillHydratedCards(){if(!this.hydrationInProgress){this.hydrationInProgress=!0;try{let t=this.getItemsToHydrate();for(let c of t)if(!(this.hydratedCards.has(c.cardID)||this.hydrationInFlight.has(c.cardID)))try{await this.hydrateCard(c)}catch(t){logger.error(`[CardHydrationService] Error hydrating card ${c.cardID}:`,t)}}finally{this.hydrationInProgress=!1}}}async hydrateCard(t){if(!(this.hydratedCards.has(t.cardID)||this.hydrationInFlight.has(t.cardID))){this.hydrationInFlight.add(t.cardID);try{let c=this.getCourseDB(t.courseID),[u,d]=await Promise.all([c.getCourseDoc(t.cardID),c.getAppliedTagsBatch([t.cardID])]);isCourseElo(u.elo)||(u.elo=toCourseElo(u.elo));let m=this.getViewComponent(u.id_view),g=await Promise.all(u.id_displayable_data.map(t=>c.getCourseDoc(t,{attachments:!0,binary:!0}))),b=[];g.forEach(t=>{t.data.forEach(t=>{b.push(...parseAudioURIs(t.data))})});let S=[...new Set(b)];S.length>0&&(logger.debug(`[CardHydrationService] Prefetching ${S.length} audio files for card ${t.cardID}`),await Promise.allSettled(S.map(prefetchAudio)));let C=g.map(displayableDataToViewData).reverse();this.hydratedCards.set(t.cardID,{item:t,view:m,data:C,tags:d.get(t.cardID)??[]}),logger.debug(`[CardHydrationService] Hydrated card ${t.cardID}`)}finally{this.hydrationInFlight.delete(t.cardID)}}}},ItemQueue=class{constructor(){_defineProperty$2(this,`q`,[]),_defineProperty$2(this,`seenCardIds`,[]),_defineProperty$2(this,`_dequeueCount`,0)}get dequeueCount(){return this._dequeueCount}add(t,c){this.seenCardIds.find(t=>t===c)||(this.seenCardIds.push(c),this.q.push(t))}addAll(t,c){t.forEach(t=>this.add(t,c(t)))}get length(){return this.q.length}peek(t){return this.q[t]}dequeue(t){if(this.q.length!==0){this._dequeueCount++;let c=this.q.splice(0,1)[0];if(t){let u=t(c),d=this.seenCardIds.indexOf(u);d>-1&&this.seenCardIds.splice(d,1)}return c}else return null}replaceAll(t,c){this.q=[],this.seenCardIds=[];for(let u of t){let t=c(u);this.seenCardIds.includes(t)||(this.seenCardIds.push(t),this.q.push(u))}}mergeToFront(t,c){let u=0,d=[];for(let m of t){let t=c(m);this.seenCardIds.includes(t)||(this.seenCardIds.push(t),d.push(m),u++)}return this.q.unshift(...d),u}get toString(){return`${typeof this.q[0]}:
406
406
  `+this.q.map(t=>` ${t.courseID}+${t.cardID}: ${t.status}`).join(`
407
- `)}},init_couch(),init_recording(),init_Loggable(),init_types_legacy(),init_logger(),CouchDBToStaticPacker=class{constructor(t={}){_defineProperty$2(this,`config`,void 0),_defineProperty$2(this,`sourceDB`,null),this.config={chunkSize:1e3,includeAttachments:!0,...t}}async packCourse(t,c){logger.info(`Starting static pack for course: ${c}`),this.sourceDB=t;let u={version:`1.0.0`,courseId:c,courseName:``,courseConfig:null,lastUpdated:new Date().toISOString(),documentCount:0,chunks:[],indices:[],designDocs:[]},d=await this.extractCourseConfig(t);u.courseName=d.name,u.courseConfig=d,u.designDocs=await this.extractDesignDocs(t);let m=await this.extractDocumentsByType(t),g=new Map;this.config.includeAttachments&&await this.extractAllAttachments(m,g);let b=new Map;for(let[t,c]of Object.entries(m)){let d=this.createChunks(c,t);u.chunks.push(...d),u.documentCount+=c.length,this.prepareChunkData(d,c,b)}let S=new Map;return u.indices=await this.buildIndices(m,u.designDocs,S),{manifest:u,chunks:b,indices:S,attachments:g}}async packCourseToFiles(t,c,u,d){logger.info(`Packing course ${c} to files in ${u}`);let m=await this.packCourse(t,c),g=await this.writePackedDataToFiles(m,u,d);return{manifest:m.manifest,filesWritten:g,attachmentsFound:m.attachments?m.attachments.size:0}}async writePackedDataToFiles(t,c,u){let d=0;await u.ensureDir(c);let m=u.joinPath(c,`manifest.json`);await u.writeJson(m,t.manifest,{spaces:2}),d++,logger.info(`Wrote manifest: ${m}`);let g=u.joinPath(c,`chunks`),b=u.joinPath(c,`indices`);await u.ensureDir(g),await u.ensureDir(b);for(let[c,m]of t.chunks){let t=u.joinPath(g,`${c}.json`);await u.writeJson(t,m),d++}logger.info(`Wrote ${t.chunks.size} chunk files`);for(let[c,m]of t.indices){let t=u.joinPath(b,`${c}.json`);await u.writeJson(t,m,{spaces:2}),d++}if(logger.info(`Wrote ${t.indices.size} index files`),t.attachments&&t.attachments.size>0){for(let[m,g]of t.attachments){let t=u.joinPath(c,m),b=u.dirname(t);await u.ensureDir(b),await u.writeFile(t,g.buffer),d++}logger.info(`Wrote ${t.attachments.size} attachment files`)}return d}async extractCourseConfig(t){try{return await t.get(`CourseConfig`)}catch(t){throw logger.error(`Failed to extract course config:`,t),Error(`Course config not found`)}}async extractDesignDocs(t){return(await t.allDocs({startkey:`_design/`,endkey:`_design/￰`,include_docs:!0})).rows.map(t=>({_id:t.id,views:t.doc.views||{}}))}async extractDocumentsByType(t){let c=await t.allDocs({include_docs:!0}),u={};for(let t of c.rows){if(t.id.startsWith(`_`))continue;let c=t.doc;c.docType&&(u[c.docType]||(u[c.docType]=[]),u[c.docType].push(c))}return u}createChunks(t,c){let u=[],d=t.sort((t,c)=>t._id.localeCompare(c._id));for(let t=0;t<d.length;t+=this.config.chunkSize){let m=d.slice(t,t+this.config.chunkSize),g=`${c}-${String(Math.floor(t/this.config.chunkSize)).padStart(4,`0`)}`;u.push({id:g,docType:c,startKey:m[0]._id,endKey:m[m.length-1]._id,documentCount:m.length,path:`chunks/${g}.json`})}return u}prepareChunkData(t,c,u){let d=c.sort((t,c)=>t._id.localeCompare(c._id));for(let c of t){let t=d.filter(t=>t._id>=c.startKey&&t._id<=c.endKey).map(t=>{let c={...t};return delete c._rev,this.config.includeAttachments&&c._attachments?c._attachments=this.transformAttachmentStubs(c._attachments,c._id):this.config.includeAttachments||delete c._attachments,c});u.set(c.id,t)}}async buildIndices(t,c,u){let d=[];if(t.CARD){let c=await this.buildEloIndex(t.CARD,u);d.push(c)}if(t.TAG){let c=await this.buildTagIndex(t.TAG,u);d.push(c)}for(let t of c)for(let[c,m]of Object.entries(t.views))if(m.map){logger.info(`Processing view: ${t._id}/${c}`);let m=await this.buildViewIndex(c,t,u);m?(d.push(m),logger.info(`Successfully built index: ${m.name}`)):logger.warn(`Skipped view index: ${t._id}/${c}`)}return d}async buildEloIndex(t,c){let u=[];for(let c of t)c.elo?.global?.score&&u.push({elo:c.elo.global.score,cardId:c._id});u.sort((t,c)=>t.elo-c.elo);let d={},m=50;for(let t of u){let c=Math.floor(t.elo/50)*50;d[c]||(d[c]=[]),d[c].push(t.cardId)}return c.set(`elo`,{sorted:u,buckets:d,stats:{min:u[0]?.elo||0,max:u[u.length-1]?.elo||0,count:u.length}}),{name:`elo`,type:`btree`,path:`indices/elo.json`}}async buildTagIndex(t,c){let u={};for(let c of t)u[c.name]={cardIds:c.taggedCards,snippet:c.snippet,count:c.taggedCards.length};let d={};for(let c of t)for(let t of c.taggedCards)d[t]||(d[t]=[]),d[t].push(c.name);return c.set(`tags`,{byTag:u,byCard:d}),{name:`tags`,type:`hash`,path:`indices/tags.json`}}async buildViewIndex(t,c,u){if(!this.sourceDB)return logger.error(`Source database not available for view querying`),null;try{let d=`${c._id.replace(`_design/`,``)}/${t}`;logger.info(`Querying CouchDB view: ${d}`);let m=await this.sourceDB.query(d,{include_docs:!1});if(!m.rows||m.rows.length===0)return logger.warn(`View ${d} returned no results`),null;logger.info(`Successfully queried view ${d}: ${m.rows.length} results`);let g=this.formatViewResults(t,m.rows,c),b=`view-${c._id.replace(`_design/`,``)}-${t}`;return u.set(b,g),{name:b,type:`view`,path:`indices/${b}.json`}}catch(u){return logger.error(`Failed to query view ${c._id}/${t}:`,u),null}}formatViewResults(t,c,u){let d={type:`couchdb-view`,viewName:t,designDoc:u._id,results:c,metadata:{resultCount:c.length,generatedAt:new Date().toISOString()}};switch(t){case`elo`:return this.formatEloViewIndex(c,d);case`getTags`:return this.formatTagsViewIndex(c,d);case`cardsByInexperience`:return this.formatInexperienceViewIndex(c,d);default:return this.formatGenericViewIndex(c,d)}}formatEloViewIndex(t,c){let u=t.sort((t,c)=>typeof t.key==`number`&&typeof c.key==`number`?t.key-c.key:0);return{...c,sorted:u,stats:{min:u[0]?.key||0,max:u[u.length-1]?.key||0,count:u.length}}}formatTagsViewIndex(t,c){let u={};for(let c of t){let t=c.key;typeof t==`string`&&(u[t]||(u[t]=[]),u[t].push(c.id))}return{...c,byTag:u,tagCount:Object.keys(u).length}}formatInexperienceViewIndex(t,c){let u=t.sort((t,c)=>typeof t.key==`number`&&typeof c.key==`number`?t.key-c.key:0);return{...c,sorted:u,stats:{minInexperience:u[0]?.key||0,maxInexperience:u[u.length-1]?.key||0,count:u.length}}}formatGenericViewIndex(t,c){return{...c}}async extractAllAttachments(t,c){logger.info(`Extracting attachments...`);let u=[];for(let c of Object.values(t))u.push(...c);let d=u.filter(t=>t._attachments&&Object.keys(t._attachments).length>0);if(d.length===0){logger.info(`No attachments found`);return}logger.info(`Found ${d.length} documents with attachments`);let m=d.map(t=>this.extractDocumentAttachments(t,c));await Promise.all(m),logger.info(`Extracted ${c.size} attachment files`)}async extractDocumentAttachments(t,c){if(!t._attachments||!this.sourceDB)return;let u=t._id;for(let[d,m]of Object.entries(t._attachments))try{let t=await this.sourceDB.getAttachment(u,d),g;if(t instanceof ArrayBuffer)g=Buffer.from(t);else if(Buffer.isBuffer(t))g=t;else{let c=t;g=Buffer.from(await c.arrayBuffer())}let b=`${d}${this.getFileExtension(m.content_type)}`,S=`attachments/${u}/${b}`;c.set(S,{docId:u,attachmentName:d,filename:b,path:S,contentType:m.content_type,length:m.length||g.length,digest:m.digest,buffer:g}),logger.debug(`Extracted attachment: ${S}`)}catch(t){throw logger.error(`Failed to extract attachment ${u}/${d}:`,t),Error(`Failed to extract attachment ${u}/${d}: ${t}`)}}transformAttachmentStubs(t,c){let u={};for(let[d,m]of Object.entries(t))u[d]={path:`attachments/${c}/${`${d}${this.getFileExtension(m.content_type)}`}`,content_type:m.content_type,length:m.length,digest:m.digest,stub:!1};return u}getFileExtension(t){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`}[t]||``}},init_logger(),DEFAULT_MIGRATION_OPTIONS={chunkBatchSize:100,validateRoundTrip:!1,cleanupOnFailure:!0,timeout:3e5},init_logger(),FileSystemError=class extends Error{constructor(t,c,u,d){super(t),this.operation=c,this.filePath=u,this.cause=d,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{}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{}StaticToCouchDBMigrator=class{constructor(t={},c){_defineProperty$2(this,`options`,void 0),_defineProperty$2(this,`progressCallback`,void 0),_defineProperty$2(this,`fs`,void 0),this.options={...DEFAULT_MIGRATION_OPTIONS,...t},this.fs=c}setProgressCallback(t){this.progressCallback=t}async migrateCourse(t,c){let u=Date.now(),d={success:!1,documentsRestored:0,attachmentsRestored:0,designDocsRestored:0,courseConfigRestored:0,errors:[],warnings:[],migrationTime:0};try{logger.info(`Starting migration from ${t} to CouchDB`),this.reportProgress(`manifest`,0,1,`Validating static course...`);let m=await validateStaticCourse(t,this.fs);if(!m.valid)throw d.errors.push(...m.errors),Error(`Static course validation failed: ${m.errors.join(`, `)}`);d.warnings.push(...m.warnings),this.reportProgress(`manifest`,1,1,`Loading course manifest...`);let g=await this.loadManifest(t);logger.info(`Loaded manifest for course: ${g.courseId} (${g.courseName})`),this.reportProgress(`design_docs`,0,g.designDocs.length,`Restoring design documents...`);let b=await this.restoreDesignDocuments(g.designDocs,c);d.designDocsRestored=b.restored,d.errors.push(...b.errors),d.warnings.push(...b.warnings),this.reportProgress(`course_config`,0,1,`Restoring CourseConfig document...`);let S=await this.restoreCourseConfig(g,c);d.courseConfigRestored=S.restored,d.errors.push(...S.errors),d.warnings.push(...S.warnings),this.reportProgress(`course_config`,1,1,`CourseConfig document restored`);let C=this.calculateExpectedCounts(g);this.reportProgress(`documents`,0,g.documentCount,`Aggregating documents from chunks...`);let w=await this.aggregateDocuments(t,g),T=w.filter(t=>t._id!==`CourseConfig`);w.length!==T.length&&d.warnings.push(`Filtered out ${w.length-T.length} CourseConfig document(s) from chunks to prevent conflicts`),this.reportProgress(`documents`,T.length,g.documentCount,`Uploading documents to CouchDB...`);let E=await this.uploadDocuments(T,c);d.documentsRestored=E.restored,d.errors.push(...E.errors),d.warnings.push(...E.warnings);let D=w.filter(t=>t._attachments&&Object.keys(t._attachments).length>0);this.reportProgress(`attachments`,0,D.length,`Uploading attachments...`);let O=await this.uploadAttachments(t,D,c);if(d.attachmentsRestored=O.restored,d.errors.push(...O.errors),d.warnings.push(...O.warnings),this.options.validateRoundTrip){this.reportProgress(`validation`,0,1,`Validating migration...`);let t=await validateMigration(c,C,g);t.valid||(d.warnings.push(`Migration validation found issues`),t.issues.forEach(t=>{t.type===`error`?d.errors.push(`Validation: ${t.message}`):d.warnings.push(`Validation: ${t.message}`)})),this.reportProgress(`validation`,1,1,`Migration validation completed`)}d.success=d.errors.length===0,d.migrationTime=Date.now()-u,logger.info(`Migration completed in ${d.migrationTime}ms`),logger.info(`Documents restored: ${d.documentsRestored}`),logger.info(`Attachments restored: ${d.attachmentsRestored}`),logger.info(`Design docs restored: ${d.designDocsRestored}`),logger.info(`CourseConfig restored: ${d.courseConfigRestored}`),d.errors.length>0&&logger.error(`Migration completed with ${d.errors.length} errors`),d.warnings.length>0&&logger.warn(`Migration completed with ${d.warnings.length} warnings`)}catch(t){d.success=!1,d.migrationTime=Date.now()-u;let m=t instanceof Error?t.message:String(t);if(d.errors.push(`Migration failed: ${m}`),logger.error(`Migration failed:`,t),this.options.cleanupOnFailure)try{await this.cleanupFailedMigration(c)}catch(t){logger.error(`Failed to cleanup after migration failure:`,t),d.warnings.push(`Failed to cleanup after migration failure`)}}return d}async loadManifest(t){try{let c,u;if(this.fs)u=this.fs.joinPath(t,`manifest.json`),c=await this.fs.readFile(u);else if(u=nodeFS3&&nodePath?nodePath.join(t,`manifest.json`):`${t}/manifest.json`,nodeFS3&&this.isLocalPath(t))c=await nodeFS3.promises.readFile(u,`utf8`);else{let t=await fetch(u);if(!t.ok)throw Error(`Failed to fetch manifest: ${t.status} ${t.statusText}`);c=await t.text()}let d=JSON.parse(c);if(!d.version||!d.courseId||!d.chunks)throw Error(`Invalid manifest structure`);return d}catch(t){let c=t instanceof FileSystemError?t.message:`Failed to load manifest: ${t instanceof Error?t.message:String(t)}`;throw Error(c)}}async restoreDesignDocuments(t,c){let u={restored:0,errors:[],warnings:[]};for(let d=0;d<t.length;d++){let m=t[d];this.reportProgress(`design_docs`,d,t.length,`Restoring ${m._id}...`);try{let t;try{t=await c.get(m._id)}catch{}let d={_id:m._id,views:m.views};t?(d._rev=t._rev,logger.debug(`Updating existing design document: ${m._id}`)):logger.debug(`Creating new design document: ${m._id}`),await c.put(d),u.restored++}catch(t){let c=`Failed to restore design document ${m._id}: ${t instanceof Error?t.message:String(t)}`;u.errors.push(c),logger.error(c)}}return this.reportProgress(`design_docs`,t.length,t.length,`Restored ${u.restored} design documents`),u}async aggregateDocuments(t,c){let u=[],d=new Map;for(let m=0;m<c.chunks.length;m++){let g=c.chunks[m];this.reportProgress(`documents`,u.length,c.documentCount,`Loading chunk ${g.id}...`);try{let c=await this.loadChunk(t,g);for(let t of c){if(!t._id){logger.warn(`Document without _id found in chunk ${g.id}, skipping`);continue}d.has(t._id)&&logger.warn(`Duplicate document ID found: ${t._id}, using latest version`),d.set(t._id,t)}}catch(t){throw Error(`Failed to load chunk ${g.id}: ${t instanceof Error?t.message:String(t)}`)}}return u.push(...d.values()),logger.info(`Aggregated ${u.length} unique documents from ${c.chunks.length} chunks`),u}async loadChunk(t,c){try{let u,d;if(this.fs)d=this.fs.joinPath(t,c.path),u=await this.fs.readFile(d);else if(d=nodeFS3&&nodePath?nodePath.join(t,c.path):`${t}/${c.path}`,nodeFS3&&this.isLocalPath(t))u=await nodeFS3.promises.readFile(d,`utf8`);else{let t=await fetch(d);if(!t.ok)throw Error(`Failed to fetch chunk: ${t.status} ${t.statusText}`);u=await t.text()}let m=JSON.parse(u);if(!Array.isArray(m))throw Error(`Chunk file does not contain an array of documents`);return m}catch(t){let c=t instanceof FileSystemError?t.message:`Failed to load chunk: ${t instanceof Error?t.message:String(t)}`;throw Error(c)}}async uploadDocuments(t,c){let u={restored:0,errors:[],warnings:[]},d=this.options.chunkBatchSize;for(let m=0;m<t.length;m+=d){let g=t.slice(m,m+d);this.reportProgress(`documents`,m,t.length,`Uploading batch ${Math.floor(m/d)+1}...`);try{let t=g.map(t=>{let c={...t};return delete c._rev,delete c._attachments,c}),d=await c.bulkDocs(t);for(let t=0;t<d.length;t++){let c=d[t],m=g[t];if(`error`in c){let t=`Failed to upload document ${m._id}: ${c.error} - ${c.reason}`;u.errors.push(t),logger.error(t)}else u.restored++}}catch(t){let c;c=t instanceof Error||t&&typeof t==`object`&&`message`in t?`Failed to upload document batch starting at index ${m}: ${t.message}`:`Failed to upload document batch starting at index ${m}: ${JSON.stringify(t)}`,u.errors.push(c),logger.error(c)}}return this.reportProgress(`documents`,t.length,t.length,`Uploaded ${u.restored} documents`),u}async uploadAttachments(t,c,u){let d={restored:0,errors:[],warnings:[]},m=0;for(let g of c)if(this.reportProgress(`attachments`,m,c.length,`Processing attachments for ${g._id}...`),m++,g._attachments)for(let[c,m]of Object.entries(g._attachments))try{let b=await this.uploadSingleAttachment(t,g._id,c,m,u);b.success?d.restored++:d.errors.push(b.error||`Unknown attachment upload error`)}catch(t){let u=`Failed to upload attachment ${g._id}/${c}: ${t instanceof Error?t.message:String(t)}`;d.errors.push(u),logger.error(u)}return this.reportProgress(`attachments`,c.length,c.length,`Uploaded ${d.restored} attachments`),d}async uploadSingleAttachment(t,c,u,d,m){let g={success:!1,attachmentName:u,docId:c};try{if(!d.path)return g.error=`Attachment metadata missing file path`,g;let b,S;if(this.fs)S=this.fs.joinPath(t,d.path),b=await this.fs.readBinary(S);else if(S=nodeFS3&&nodePath?nodePath.join(t,d.path):`${t}/${d.path}`,nodeFS3&&this.isLocalPath(t))b=await nodeFS3.promises.readFile(S);else{let t=await fetch(S);if(!t.ok)return g.error=`Failed to fetch attachment: ${t.status} ${t.statusText}`,g;b=await t.arrayBuffer()}let C=await m.get(c);await m.putAttachment(c,u,C._rev,b,d.content_type),g.success=!0}catch(t){g.error=t instanceof Error?t.message:String(t)}return g}async restoreCourseConfig(t,c){let u={restored:0,errors:[],warnings:[]};try{if(!t.courseConfig)return u.warnings.push(`No courseConfig found in manifest, skipping CourseConfig document creation`),u;let d={_id:`CourseConfig`,...t.courseConfig,courseID:t.courseId};delete d._rev,await c.put(d),u.restored=1,logger.info(`CourseConfig document created for course: ${t.courseId}`)}catch(t){let c=t instanceof Error?t.message:JSON.stringify(t);u.errors.push(`Failed to restore CourseConfig: ${c}`),logger.error(`CourseConfig restoration failed:`,t)}return u}calculateExpectedCounts(t){let c={};for(let u of t.chunks)c[u.docType]=(c[u.docType]||0)+u.documentCount;return t.designDocs.length>0&&(c._design=t.designDocs.length),c}async cleanupFailedMigration(t){logger.info(`Cleaning up failed migration...`);try{let c=(await t.allDocs()).rows.map(t=>({_id:t.id,_rev:t.value.rev,_deleted:!0}));c.length>0&&(await t.bulkDocs(c),logger.info(`Cleaned up ${c.length} documents from failed migration`))}catch(t){throw logger.error(`Failed to cleanup documents:`,t),t}}reportProgress(t,c,u,d){this.progressCallback&&this.progressCallback({phase:t,current:c,total:u,message:d})}isLocalPath(t){return!t.startsWith(`http://`)&&!t.startsWith(`https://`)}},init_dataDirectory(),init_navigators(),QuotaRoundRobinMixer=class{mix(t,c){if(t.length===0)return[];let u=Math.ceil(c/t.length),d=t.map(t=>[...t.weighted].sort((t,c)=>c.score-t.score).slice(0,u));for(let t=d.length-1;t>0;t--){let c=Math.floor(Math.random()*(t+1));[d[t],d[c]]=[d[c],d[t]]}let m=[],g=0,b=Array(d.length).fill(0);for(;m.length<c&&g<d.length;){g=0;for(let t=0;t<d.length&&!(m.length>=c);t++)b[t]<d[t].length?(m.push(d[t][b[t]]),b[t]++):g++}return m}},init_logger(),init_navigators(),MAX_RUNS2=10,runHistory2=[],mixerDebugAPI={get runs(){return[...runHistory2]},showRun(t=0){if(runHistory2.length===0){logger.info(`[Mixer Debug] No runs captured yet.`);return}let c;if(typeof t==`number`){if(c=runHistory2[t],!c){logger.info(`[Mixer Debug] No run found at index ${t}. History length: ${runHistory2.length}`);return}}else if(c=runHistory2.find(c=>c.runId.endsWith(t)),!c){logger.info(`[Mixer Debug] No run found matching ID '${t}'.`);return}printMixerSummary(c)},showLastMix(){this.showRun(0)},explainSourceBalance(){if(runHistory2.length===0){logger.info(`[Mixer Debug] No runs captured yet.`);return}let t=runHistory2[0];console.group(`⚖️ Source Balance Analysis`),logger.info(`Mixer: ${t.mixerType}`),logger.info(`Requested limit: ${t.requestedLimit}`),t.quotaPerSource&&logger.info(`Quota per source: ${t.quotaPerSource}`),console.group(`Input Distribution:`);for(let c of t.sourceSummaries){let t=c.sourceName||c.sourceId;logger.info(`${t}:`),logger.info(` Provided: ${c.totalCards} cards (${c.reviewCount} reviews, ${c.newCount} new)`),logger.info(` Score range: [${c.scoreRange[0].toFixed(2)}, ${c.scoreRange[1].toFixed(2)}]`)}console.groupEnd(),console.group(`Selection Results:`);for(let c of t.sourceBreakdowns){let t=c.sourceName||c.sourceId;logger.info(`${t}:`),logger.info(` Selected: ${c.totalSelected}/${c.reviewsProvided+c.newProvided} (${c.selectionRate.toFixed(1)}%)`),logger.info(` Reviews: ${c.reviewsSelected}/${c.reviewsProvided}`),logger.info(` New: ${c.newSelected}/${c.newProvided}`),c.reviewsProvided>0&&c.reviewsSelected===0&&logger.info(` ⚠️ Had reviews but none selected!`),c.totalSelected===0&&c.reviewsProvided+c.newProvided>0&&logger.info(` ⚠️ Had cards but none selected!`)}console.groupEnd();let c=t.sourceBreakdowns.map(t=>t.selectionRate),u=c.reduce((t,c)=>t+c,0)/c.length,d=Math.max(...c.map(t=>Math.abs(t-u)));d>20&&(logger.info(`
407
+ `)}},init_couch(),init_core(),init_recording(),init_Loggable(),init_types_legacy(),init_logger(),CouchDBToStaticPacker=class{constructor(t={}){_defineProperty$2(this,`config`,void 0),_defineProperty$2(this,`sourceDB`,null),this.config={chunkSize:1e3,includeAttachments:!0,...t}}async packCourse(t,c){logger.info(`Starting static pack for course: ${c}`),this.sourceDB=t;let u={version:`1.0.0`,courseId:c,courseName:``,courseConfig:null,lastUpdated:new Date().toISOString(),documentCount:0,chunks:[],indices:[],designDocs:[]},d=await this.extractCourseConfig(t);u.courseName=d.name,u.courseConfig=d,u.designDocs=await this.extractDesignDocs(t);let m=await this.extractDocumentsByType(t),g=new Map;this.config.includeAttachments&&await this.extractAllAttachments(m,g);let b=new Map;for(let[t,c]of Object.entries(m)){let d=this.createChunks(c,t);u.chunks.push(...d),u.documentCount+=c.length,this.prepareChunkData(d,c,b)}let S=new Map;return u.indices=await this.buildIndices(m,u.designDocs,S),{manifest:u,chunks:b,indices:S,attachments:g}}async packCourseToFiles(t,c,u,d){logger.info(`Packing course ${c} to files in ${u}`);let m=await this.packCourse(t,c),g=await this.writePackedDataToFiles(m,u,d);return{manifest:m.manifest,filesWritten:g,attachmentsFound:m.attachments?m.attachments.size:0}}async writePackedDataToFiles(t,c,u){let d=0;await u.ensureDir(c);let m=u.joinPath(c,`manifest.json`);await u.writeJson(m,t.manifest,{spaces:2}),d++,logger.info(`Wrote manifest: ${m}`);let g=u.joinPath(c,`chunks`),b=u.joinPath(c,`indices`);await u.ensureDir(g),await u.ensureDir(b);for(let[c,m]of t.chunks){let t=u.joinPath(g,`${c}.json`);await u.writeJson(t,m),d++}logger.info(`Wrote ${t.chunks.size} chunk files`);for(let[c,m]of t.indices){let t=u.joinPath(b,`${c}.json`);await u.writeJson(t,m,{spaces:2}),d++}if(logger.info(`Wrote ${t.indices.size} index files`),t.attachments&&t.attachments.size>0){for(let[m,g]of t.attachments){let t=u.joinPath(c,m),b=u.dirname(t);await u.ensureDir(b),await u.writeFile(t,g.buffer),d++}logger.info(`Wrote ${t.attachments.size} attachment files`)}return d}async extractCourseConfig(t){try{return await t.get(`CourseConfig`)}catch(t){throw logger.error(`Failed to extract course config:`,t),Error(`Course config not found`)}}async extractDesignDocs(t){return(await t.allDocs({startkey:`_design/`,endkey:`_design/￰`,include_docs:!0})).rows.map(t=>({_id:t.id,views:t.doc.views||{}}))}async extractDocumentsByType(t){let c=await t.allDocs({include_docs:!0}),u={};for(let t of c.rows){if(t.id.startsWith(`_`))continue;let c=t.doc;c.docType&&(u[c.docType]||(u[c.docType]=[]),u[c.docType].push(c))}return u}createChunks(t,c){let u=[],d=t.sort((t,c)=>t._id.localeCompare(c._id));for(let t=0;t<d.length;t+=this.config.chunkSize){let m=d.slice(t,t+this.config.chunkSize),g=`${c}-${String(Math.floor(t/this.config.chunkSize)).padStart(4,`0`)}`;u.push({id:g,docType:c,startKey:m[0]._id,endKey:m[m.length-1]._id,documentCount:m.length,path:`chunks/${g}.json`})}return u}prepareChunkData(t,c,u){let d=c.sort((t,c)=>t._id.localeCompare(c._id));for(let c of t){let t=d.filter(t=>t._id>=c.startKey&&t._id<=c.endKey).map(t=>{let c={...t};return delete c._rev,this.config.includeAttachments&&c._attachments?c._attachments=this.transformAttachmentStubs(c._attachments,c._id):this.config.includeAttachments||delete c._attachments,c});u.set(c.id,t)}}async buildIndices(t,c,u){let d=[];if(t.CARD){let c=await this.buildEloIndex(t.CARD,u);d.push(c)}if(t.TAG){let c=await this.buildTagIndex(t.TAG,u);d.push(c)}for(let t of c)for(let[c,m]of Object.entries(t.views))if(m.map){logger.info(`Processing view: ${t._id}/${c}`);let m=await this.buildViewIndex(c,t,u);m?(d.push(m),logger.info(`Successfully built index: ${m.name}`)):logger.warn(`Skipped view index: ${t._id}/${c}`)}return d}async buildEloIndex(t,c){let u=[];for(let c of t)c.elo?.global?.score&&u.push({elo:c.elo.global.score,cardId:c._id});u.sort((t,c)=>t.elo-c.elo);let d={},m=50;for(let t of u){let c=Math.floor(t.elo/50)*50;d[c]||(d[c]=[]),d[c].push(t.cardId)}return c.set(`elo`,{sorted:u,buckets:d,stats:{min:u[0]?.elo||0,max:u[u.length-1]?.elo||0,count:u.length}}),{name:`elo`,type:`btree`,path:`indices/elo.json`}}async buildTagIndex(t,c){let u={};for(let c of t)u[c.name]={cardIds:c.taggedCards,snippet:c.snippet,count:c.taggedCards.length};let d={};for(let c of t)for(let t of c.taggedCards)d[t]||(d[t]=[]),d[t].push(c.name);return c.set(`tags`,{byTag:u,byCard:d}),{name:`tags`,type:`hash`,path:`indices/tags.json`}}async buildViewIndex(t,c,u){if(!this.sourceDB)return logger.error(`Source database not available for view querying`),null;try{let d=`${c._id.replace(`_design/`,``)}/${t}`;logger.info(`Querying CouchDB view: ${d}`);let m=await this.sourceDB.query(d,{include_docs:!1});if(!m.rows||m.rows.length===0)return logger.warn(`View ${d} returned no results`),null;logger.info(`Successfully queried view ${d}: ${m.rows.length} results`);let g=this.formatViewResults(t,m.rows,c),b=`view-${c._id.replace(`_design/`,``)}-${t}`;return u.set(b,g),{name:b,type:`view`,path:`indices/${b}.json`}}catch(u){return logger.error(`Failed to query view ${c._id}/${t}:`,u),null}}formatViewResults(t,c,u){let d={type:`couchdb-view`,viewName:t,designDoc:u._id,results:c,metadata:{resultCount:c.length,generatedAt:new Date().toISOString()}};switch(t){case`elo`:return this.formatEloViewIndex(c,d);case`getTags`:return this.formatTagsViewIndex(c,d);case`cardsByInexperience`:return this.formatInexperienceViewIndex(c,d);default:return this.formatGenericViewIndex(c,d)}}formatEloViewIndex(t,c){let u=t.sort((t,c)=>typeof t.key==`number`&&typeof c.key==`number`?t.key-c.key:0);return{...c,sorted:u,stats:{min:u[0]?.key||0,max:u[u.length-1]?.key||0,count:u.length}}}formatTagsViewIndex(t,c){let u={};for(let c of t){let t=c.key;typeof t==`string`&&(u[t]||(u[t]=[]),u[t].push(c.id))}return{...c,byTag:u,tagCount:Object.keys(u).length}}formatInexperienceViewIndex(t,c){let u=t.sort((t,c)=>typeof t.key==`number`&&typeof c.key==`number`?t.key-c.key:0);return{...c,sorted:u,stats:{minInexperience:u[0]?.key||0,maxInexperience:u[u.length-1]?.key||0,count:u.length}}}formatGenericViewIndex(t,c){return{...c}}async extractAllAttachments(t,c){logger.info(`Extracting attachments...`);let u=[];for(let c of Object.values(t))u.push(...c);let d=u.filter(t=>t._attachments&&Object.keys(t._attachments).length>0);if(d.length===0){logger.info(`No attachments found`);return}logger.info(`Found ${d.length} documents with attachments`);let m=d.map(t=>this.extractDocumentAttachments(t,c));await Promise.all(m),logger.info(`Extracted ${c.size} attachment files`)}async extractDocumentAttachments(t,c){if(!t._attachments||!this.sourceDB)return;let u=t._id;for(let[d,m]of Object.entries(t._attachments))try{let t=await this.sourceDB.getAttachment(u,d),g;if(t instanceof ArrayBuffer)g=Buffer.from(t);else if(Buffer.isBuffer(t))g=t;else{let c=t;g=Buffer.from(await c.arrayBuffer())}let b=`${d}${this.getFileExtension(m.content_type)}`,S=`attachments/${u}/${b}`;c.set(S,{docId:u,attachmentName:d,filename:b,path:S,contentType:m.content_type,length:m.length||g.length,digest:m.digest,buffer:g}),logger.debug(`Extracted attachment: ${S}`)}catch(t){throw logger.error(`Failed to extract attachment ${u}/${d}:`,t),Error(`Failed to extract attachment ${u}/${d}: ${t}`)}}transformAttachmentStubs(t,c){let u={};for(let[d,m]of Object.entries(t))u[d]={path:`attachments/${c}/${`${d}${this.getFileExtension(m.content_type)}`}`,content_type:m.content_type,length:m.length,digest:m.digest,stub:!1};return u}getFileExtension(t){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`}[t]||``}},init_logger(),DEFAULT_MIGRATION_OPTIONS={chunkBatchSize:100,validateRoundTrip:!1,cleanupOnFailure:!0,timeout:3e5},init_logger(),FileSystemError=class extends Error{constructor(t,c,u,d){super(t),this.operation=c,this.filePath=u,this.cause=d,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{}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{}StaticToCouchDBMigrator=class{constructor(t={},c){_defineProperty$2(this,`options`,void 0),_defineProperty$2(this,`progressCallback`,void 0),_defineProperty$2(this,`fs`,void 0),this.options={...DEFAULT_MIGRATION_OPTIONS,...t},this.fs=c}setProgressCallback(t){this.progressCallback=t}async migrateCourse(t,c){let u=Date.now(),d={success:!1,documentsRestored:0,attachmentsRestored:0,designDocsRestored:0,courseConfigRestored:0,errors:[],warnings:[],migrationTime:0};try{logger.info(`Starting migration from ${t} to CouchDB`),this.reportProgress(`manifest`,0,1,`Validating static course...`);let m=await validateStaticCourse(t,this.fs);if(!m.valid)throw d.errors.push(...m.errors),Error(`Static course validation failed: ${m.errors.join(`, `)}`);d.warnings.push(...m.warnings),this.reportProgress(`manifest`,1,1,`Loading course manifest...`);let g=await this.loadManifest(t);logger.info(`Loaded manifest for course: ${g.courseId} (${g.courseName})`),this.reportProgress(`design_docs`,0,g.designDocs.length,`Restoring design documents...`);let b=await this.restoreDesignDocuments(g.designDocs,c);d.designDocsRestored=b.restored,d.errors.push(...b.errors),d.warnings.push(...b.warnings),this.reportProgress(`course_config`,0,1,`Restoring CourseConfig document...`);let S=await this.restoreCourseConfig(g,c);d.courseConfigRestored=S.restored,d.errors.push(...S.errors),d.warnings.push(...S.warnings),this.reportProgress(`course_config`,1,1,`CourseConfig document restored`);let C=this.calculateExpectedCounts(g);this.reportProgress(`documents`,0,g.documentCount,`Aggregating documents from chunks...`);let w=await this.aggregateDocuments(t,g),T=w.filter(t=>t._id!==`CourseConfig`);w.length!==T.length&&d.warnings.push(`Filtered out ${w.length-T.length} CourseConfig document(s) from chunks to prevent conflicts`),this.reportProgress(`documents`,T.length,g.documentCount,`Uploading documents to CouchDB...`);let E=await this.uploadDocuments(T,c);d.documentsRestored=E.restored,d.errors.push(...E.errors),d.warnings.push(...E.warnings);let D=w.filter(t=>t._attachments&&Object.keys(t._attachments).length>0);this.reportProgress(`attachments`,0,D.length,`Uploading attachments...`);let O=await this.uploadAttachments(t,D,c);if(d.attachmentsRestored=O.restored,d.errors.push(...O.errors),d.warnings.push(...O.warnings),this.options.validateRoundTrip){this.reportProgress(`validation`,0,1,`Validating migration...`);let t=await validateMigration(c,C,g);t.valid||(d.warnings.push(`Migration validation found issues`),t.issues.forEach(t=>{t.type===`error`?d.errors.push(`Validation: ${t.message}`):d.warnings.push(`Validation: ${t.message}`)})),this.reportProgress(`validation`,1,1,`Migration validation completed`)}d.success=d.errors.length===0,d.migrationTime=Date.now()-u,logger.info(`Migration completed in ${d.migrationTime}ms`),logger.info(`Documents restored: ${d.documentsRestored}`),logger.info(`Attachments restored: ${d.attachmentsRestored}`),logger.info(`Design docs restored: ${d.designDocsRestored}`),logger.info(`CourseConfig restored: ${d.courseConfigRestored}`),d.errors.length>0&&logger.error(`Migration completed with ${d.errors.length} errors`),d.warnings.length>0&&logger.warn(`Migration completed with ${d.warnings.length} warnings`)}catch(t){d.success=!1,d.migrationTime=Date.now()-u;let m=t instanceof Error?t.message:String(t);if(d.errors.push(`Migration failed: ${m}`),logger.error(`Migration failed:`,t),this.options.cleanupOnFailure)try{await this.cleanupFailedMigration(c)}catch(t){logger.error(`Failed to cleanup after migration failure:`,t),d.warnings.push(`Failed to cleanup after migration failure`)}}return d}async loadManifest(t){try{let c,u;if(this.fs)u=this.fs.joinPath(t,`manifest.json`),c=await this.fs.readFile(u);else if(u=nodeFS3&&nodePath?nodePath.join(t,`manifest.json`):`${t}/manifest.json`,nodeFS3&&this.isLocalPath(t))c=await nodeFS3.promises.readFile(u,`utf8`);else{let t=await fetch(u);if(!t.ok)throw Error(`Failed to fetch manifest: ${t.status} ${t.statusText}`);c=await t.text()}let d=JSON.parse(c);if(!d.version||!d.courseId||!d.chunks)throw Error(`Invalid manifest structure`);return d}catch(t){let c=t instanceof FileSystemError?t.message:`Failed to load manifest: ${t instanceof Error?t.message:String(t)}`;throw Error(c)}}async restoreDesignDocuments(t,c){let u={restored:0,errors:[],warnings:[]};for(let d=0;d<t.length;d++){let m=t[d];this.reportProgress(`design_docs`,d,t.length,`Restoring ${m._id}...`);try{let t;try{t=await c.get(m._id)}catch{}let d={_id:m._id,views:m.views};t?(d._rev=t._rev,logger.debug(`Updating existing design document: ${m._id}`)):logger.debug(`Creating new design document: ${m._id}`),await c.put(d),u.restored++}catch(t){let c=`Failed to restore design document ${m._id}: ${t instanceof Error?t.message:String(t)}`;u.errors.push(c),logger.error(c)}}return this.reportProgress(`design_docs`,t.length,t.length,`Restored ${u.restored} design documents`),u}async aggregateDocuments(t,c){let u=[],d=new Map;for(let m=0;m<c.chunks.length;m++){let g=c.chunks[m];this.reportProgress(`documents`,u.length,c.documentCount,`Loading chunk ${g.id}...`);try{let c=await this.loadChunk(t,g);for(let t of c){if(!t._id){logger.warn(`Document without _id found in chunk ${g.id}, skipping`);continue}d.has(t._id)&&logger.warn(`Duplicate document ID found: ${t._id}, using latest version`),d.set(t._id,t)}}catch(t){throw Error(`Failed to load chunk ${g.id}: ${t instanceof Error?t.message:String(t)}`)}}return u.push(...d.values()),logger.info(`Aggregated ${u.length} unique documents from ${c.chunks.length} chunks`),u}async loadChunk(t,c){try{let u,d;if(this.fs)d=this.fs.joinPath(t,c.path),u=await this.fs.readFile(d);else if(d=nodeFS3&&nodePath?nodePath.join(t,c.path):`${t}/${c.path}`,nodeFS3&&this.isLocalPath(t))u=await nodeFS3.promises.readFile(d,`utf8`);else{let t=await fetch(d);if(!t.ok)throw Error(`Failed to fetch chunk: ${t.status} ${t.statusText}`);u=await t.text()}let m=JSON.parse(u);if(!Array.isArray(m))throw Error(`Chunk file does not contain an array of documents`);return m}catch(t){let c=t instanceof FileSystemError?t.message:`Failed to load chunk: ${t instanceof Error?t.message:String(t)}`;throw Error(c)}}async uploadDocuments(t,c){let u={restored:0,errors:[],warnings:[]},d=this.options.chunkBatchSize;for(let m=0;m<t.length;m+=d){let g=t.slice(m,m+d);this.reportProgress(`documents`,m,t.length,`Uploading batch ${Math.floor(m/d)+1}...`);try{let t=g.map(t=>{let c={...t};return delete c._rev,delete c._attachments,c}),d=await c.bulkDocs(t);for(let t=0;t<d.length;t++){let c=d[t],m=g[t];if(`error`in c){let t=`Failed to upload document ${m._id}: ${c.error} - ${c.reason}`;u.errors.push(t),logger.error(t)}else u.restored++}}catch(t){let c;c=t instanceof Error||t&&typeof t==`object`&&`message`in t?`Failed to upload document batch starting at index ${m}: ${t.message}`:`Failed to upload document batch starting at index ${m}: ${JSON.stringify(t)}`,u.errors.push(c),logger.error(c)}}return this.reportProgress(`documents`,t.length,t.length,`Uploaded ${u.restored} documents`),u}async uploadAttachments(t,c,u){let d={restored:0,errors:[],warnings:[]},m=0;for(let g of c)if(this.reportProgress(`attachments`,m,c.length,`Processing attachments for ${g._id}...`),m++,g._attachments)for(let[c,m]of Object.entries(g._attachments))try{let b=await this.uploadSingleAttachment(t,g._id,c,m,u);b.success?d.restored++:d.errors.push(b.error||`Unknown attachment upload error`)}catch(t){let u=`Failed to upload attachment ${g._id}/${c}: ${t instanceof Error?t.message:String(t)}`;d.errors.push(u),logger.error(u)}return this.reportProgress(`attachments`,c.length,c.length,`Uploaded ${d.restored} attachments`),d}async uploadSingleAttachment(t,c,u,d,m){let g={success:!1,attachmentName:u,docId:c};try{if(!d.path)return g.error=`Attachment metadata missing file path`,g;let b,S;if(this.fs)S=this.fs.joinPath(t,d.path),b=await this.fs.readBinary(S);else if(S=nodeFS3&&nodePath?nodePath.join(t,d.path):`${t}/${d.path}`,nodeFS3&&this.isLocalPath(t))b=await nodeFS3.promises.readFile(S);else{let t=await fetch(S);if(!t.ok)return g.error=`Failed to fetch attachment: ${t.status} ${t.statusText}`,g;b=await t.arrayBuffer()}let C=await m.get(c);await m.putAttachment(c,u,C._rev,b,d.content_type),g.success=!0}catch(t){g.error=t instanceof Error?t.message:String(t)}return g}async restoreCourseConfig(t,c){let u={restored:0,errors:[],warnings:[]};try{if(!t.courseConfig)return u.warnings.push(`No courseConfig found in manifest, skipping CourseConfig document creation`),u;let d={_id:`CourseConfig`,...t.courseConfig,courseID:t.courseId};delete d._rev,await c.put(d),u.restored=1,logger.info(`CourseConfig document created for course: ${t.courseId}`)}catch(t){let c=t instanceof Error?t.message:JSON.stringify(t);u.errors.push(`Failed to restore CourseConfig: ${c}`),logger.error(`CourseConfig restoration failed:`,t)}return u}calculateExpectedCounts(t){let c={};for(let u of t.chunks)c[u.docType]=(c[u.docType]||0)+u.documentCount;return t.designDocs.length>0&&(c._design=t.designDocs.length),c}async cleanupFailedMigration(t){logger.info(`Cleaning up failed migration...`);try{let c=(await t.allDocs()).rows.map(t=>({_id:t.id,_rev:t.value.rev,_deleted:!0}));c.length>0&&(await t.bulkDocs(c),logger.info(`Cleaned up ${c.length} documents from failed migration`))}catch(t){throw logger.error(`Failed to cleanup documents:`,t),t}}reportProgress(t,c,u,d){this.progressCallback&&this.progressCallback({phase:t,current:c,total:u,message:d})}isLocalPath(t){return!t.startsWith(`http://`)&&!t.startsWith(`https://`)}},init_dataDirectory(),init_navigators(),init_Pipeline(),QuotaRoundRobinMixer=class{mix(t,c){if(t.length===0)return[];let u=Math.ceil(c/t.length),d=t.map(t=>[...t.weighted].sort((t,c)=>c.score-t.score).slice(0,u));for(let t=d.length-1;t>0;t--){let c=Math.floor(Math.random()*(t+1));[d[t],d[c]]=[d[c],d[t]]}let m=[],g=0,b=Array(d.length).fill(0);for(;m.length<c&&g<d.length;){g=0;for(let t=0;t<d.length&&!(m.length>=c);t++)b[t]<d[t].length?(m.push(d[t][b[t]]),b[t]++):g++}return m}},init_logger(),init_navigators(),MAX_RUNS2=10,runHistory2=[],mixerDebugAPI={get runs(){return[...runHistory2]},showRun(t=0){if(runHistory2.length===0){logger.info(`[Mixer Debug] No runs captured yet.`);return}let c;if(typeof t==`number`){if(c=runHistory2[t],!c){logger.info(`[Mixer Debug] No run found at index ${t}. History length: ${runHistory2.length}`);return}}else if(c=runHistory2.find(c=>c.runId.endsWith(t)),!c){logger.info(`[Mixer Debug] No run found matching ID '${t}'.`);return}printMixerSummary(c)},showLastMix(){this.showRun(0)},explainSourceBalance(){if(runHistory2.length===0){logger.info(`[Mixer Debug] No runs captured yet.`);return}let t=runHistory2[0];console.group(`⚖️ Source Balance Analysis`),logger.info(`Mixer: ${t.mixerType}`),logger.info(`Requested limit: ${t.requestedLimit}`),t.quotaPerSource&&logger.info(`Quota per source: ${t.quotaPerSource}`),console.group(`Input Distribution:`);for(let c of t.sourceSummaries){let t=c.sourceName||c.sourceId;logger.info(`${t}:`),logger.info(` Provided: ${c.totalCards} cards (${c.reviewCount} reviews, ${c.newCount} new)`),logger.info(` Score range: [${c.scoreRange[0].toFixed(2)}, ${c.scoreRange[1].toFixed(2)}]`)}console.groupEnd(),console.group(`Selection Results:`);for(let c of t.sourceBreakdowns){let t=c.sourceName||c.sourceId;logger.info(`${t}:`),logger.info(` Selected: ${c.totalSelected}/${c.reviewsProvided+c.newProvided} (${c.selectionRate.toFixed(1)}%)`),logger.info(` Reviews: ${c.reviewsSelected}/${c.reviewsProvided}`),logger.info(` New: ${c.newSelected}/${c.newProvided}`),c.reviewsProvided>0&&c.reviewsSelected===0&&logger.info(` ⚠️ Had reviews but none selected!`),c.totalSelected===0&&c.reviewsProvided+c.newProvided>0&&logger.info(` ⚠️ Had cards but none selected!`)}console.groupEnd();let c=t.sourceBreakdowns.map(t=>t.selectionRate),u=c.reduce((t,c)=>t+c,0)/c.length,d=Math.max(...c.map(t=>Math.abs(t-u)));d>20&&(logger.info(`
408
408
  \u26A0\uFE0F Significant imbalance detected (max deviation: ${d.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 t=runHistory2[0];console.group(`📊 Score Distribution Comparison`),console.table(t.sourceSummaries.map(t=>({source:t.sourceName||t.sourceId,cards:t.totalCards,min:t.bottomScore.toFixed(3),max:t.topScore.toFixed(3),avg:t.avgScore.toFixed(3),range:(t.topScore-t.bottomScore).toFixed(3)})));let c=t.sourceSummaries.map(t=>t.topScore-t.bottomScore),u=t.sourceSummaries.map(t=>t.avgScore),d=Math.max(...c)-Math.min(...c),m=Math.max(...u)-Math.min(...u);(d>.3||m>.2)&&(logger.info(`
409
409
  ⚠️ Significant score distribution differences detected`),logger.info(`This may cause one source to dominate selection if using global sorting (not quota-based)`)),console.groupEnd()},showCard(t){for(let c of runHistory2){let u=c.cards.find(c=>c.cardId===t);if(u){let d=c.sourceSummaries.find(t=>t.sourceIndex===u.sourceIndex);console.group(`\u{1F3B4} Card: ${t}`),logger.info(`Course: ${u.courseId}`),logger.info(`Source: ${d?.sourceName||d?.sourceId||`unknown`}`),logger.info(`Origin: ${u.origin}`),logger.info(`Score: ${u.score.toFixed(3)}`),u.rankInSource&&logger.info(`Rank in source: #${u.rankInSource}`),u.rankInMix&&logger.info(`Rank in mixed results: #${u.rankInMix}`),logger.info(`Selected: ${u.selected?`Yes ✅`:`No ❌`}`),!u.selected&&u.rankInSource&&(logger.info(`
410
410
  Why not selected:`),c.quotaPerSource&&u.rankInSource>c.quotaPerSource&&logger.info(` - Ranked #${u.rankInSource} in source, but quota was ${c.quotaPerSource}`),logger.info(` - Check score compared to selected cards using .showRun()`)),console.groupEnd();return}}logger.info(`[Mixer Debug] Card '${t}' not found in recent runs.`)},listRuns(){if(runHistory2.length===0){logger.info(`[Mixer Debug] No runs captured yet.`);return}console.table(runHistory2.map(t=>({id:t.runId.slice(-8),time:t.timestamp.toLocaleTimeString(),mixer:t.mixerType,sources:t.sourceSummaries.length,selected:t.finalCount,reviews:t.reviewsSelected,new:t.newSelected})))},export(){let t=JSON.stringify(runHistory2,null,2);return logger.info(`[Mixer Debug] Run history exported. Copy the returned string or use:`),logger.info(` copy(window.skuilder.mixer.export())`),t},clear(){runHistory2.length=0,logger.info(`[Mixer Debug] Run history cleared.`)},help(){logger.info(`
@@ -446,16 +446,16 @@ Example:
446
446
  window.skuilder.session.showQueue()
447
447
  `)}},mountSessionDebugger(),init_logger(),SessionController=(_SessionController2=class _SessionController extends Loggable{set sessionRecord(t){this._sessionRecord=t}get secondsRemaining(){return this._secondsRemaining}get hasCardGuarantee(){return this._minCardsGuarantee>0}get report(){let t=this.reviewQ.dequeueCount,c=this.newQ.dequeueCount;return`${t} ${t===1?`review`:`reviews`}, ${c} ${c===1?`new card`:`new cards`}`}get detailedReport(){return this.newQ.toString+`
448
448
  `+this.reviewQ.toString+`
449
- `+this.failedQ.toString}constructor(t,c,u,d,m,g){super(),_defineProperty$2(this,`_className`,`SessionController`),_defineProperty$2(this,`services`,void 0),_defineProperty$2(this,`srsService`,void 0),_defineProperty$2(this,`eloService`,void 0),_defineProperty$2(this,`hydrationService`,void 0),_defineProperty$2(this,`mixer`,void 0),_defineProperty$2(this,`dataLayer`,void 0),_defineProperty$2(this,`courseNameCache`,new Map),_defineProperty$2(this,`_defaultBatchLimit`,20),_defineProperty$2(this,`_initialReviewCap`,200),_defineProperty$2(this,`sources`,void 0),_defineProperty$2(this,`_sessionRecord`,[]),_defineProperty$2(this,`_currentCard`,null),_defineProperty$2(this,`reviewQ`,new ItemQueue),_defineProperty$2(this,`newQ`,new ItemQueue),_defineProperty$2(this,`failedQ`,new ItemQueue),_defineProperty$2(this,`_replanPromise`,null),_defineProperty$2(this,`_wellIndicatedRemaining`,0),_defineProperty$2(this,`_suppressQualityReplan`,!1),_defineProperty$2(this,`_minCardsGuarantee`,0),_defineProperty$2(this,`startTime`,void 0),_defineProperty$2(this,`endTime`,void 0),_defineProperty$2(this,`_secondsRemaining`,void 0),_defineProperty$2(this,`_intervalHandle`,void 0),this.dataLayer=u,this.mixer=m||new QuotaRoundRobinMixer,this.srsService=new SrsService(u.getUserDB()),this.eloService=new EloService(u,u.getUserDB()),this.hydrationService=new CardHydrationService(d,t=>u.getCourseDB(t),()=>this._getItemsToHydrate()),this.services={response:new ResponseProcessor(this.srsService,this.eloService)},this.sources=t,this.startTime=new Date,this._secondsRemaining=c,this.endTime=new Date(this.startTime.valueOf()+1e3*this._secondsRemaining),g?.defaultBatchLimit!==void 0&&(this._defaultBatchLimit=g.defaultBatchLimit),g?.initialReviewCap!==void 0&&(this._initialReviewCap=g.initialReviewCap),this.log(`Session constructed:
449
+ `+this.failedQ.toString}constructor(t,c,u,d,m,g){super(),_defineProperty$2(this,`_className`,`SessionController`),_defineProperty$2(this,`services`,void 0),_defineProperty$2(this,`srsService`,void 0),_defineProperty$2(this,`eloService`,void 0),_defineProperty$2(this,`hydrationService`,void 0),_defineProperty$2(this,`mixer`,void 0),_defineProperty$2(this,`dataLayer`,void 0),_defineProperty$2(this,`courseNameCache`,new Map),_defineProperty$2(this,`_defaultBatchLimit`,20),_defineProperty$2(this,`_initialReviewCap`,200),_defineProperty$2(this,`sources`,void 0),_defineProperty$2(this,`_sessionRecord`,[]),_defineProperty$2(this,`_currentCard`,null),_defineProperty$2(this,`reviewQ`,new ItemQueue),_defineProperty$2(this,`newQ`,new ItemQueue),_defineProperty$2(this,`failedQ`,new ItemQueue),_defineProperty$2(this,`_replanPromise`,null),_defineProperty$2(this,`_wellIndicatedRemaining`,0),_defineProperty$2(this,`_suppressQualityReplan`,!1),_defineProperty$2(this,`_minCardsGuarantee`,0),_defineProperty$2(this,`_sessionHints`,null),_defineProperty$2(this,`_outcomeObservers`,[]),_defineProperty$2(this,`_sessionControls`,null),_defineProperty$2(this,`startTime`,void 0),_defineProperty$2(this,`endTime`,void 0),_defineProperty$2(this,`_secondsRemaining`,void 0),_defineProperty$2(this,`_intervalHandle`,void 0),this.dataLayer=u,this.mixer=m||new QuotaRoundRobinMixer,this.srsService=new SrsService(u.getUserDB()),this.eloService=new EloService(u,u.getUserDB()),this.hydrationService=new CardHydrationService(d,t=>u.getCourseDB(t),()=>this._getItemsToHydrate()),this.services={response:new ResponseProcessor(this.srsService,this.eloService)},this.sources=t,this.startTime=new Date,this._secondsRemaining=c,this.endTime=new Date(this.startTime.valueOf()+1e3*this._secondsRemaining),g?.defaultBatchLimit!==void 0&&(this._defaultBatchLimit=g.defaultBatchLimit),g?.initialReviewCap!==void 0&&(this._initialReviewCap=g.initialReviewCap),g?.outcomeObservers?.length&&(this._outcomeObservers=[...g.outcomeObservers]),this.log(`Session constructed:
450
450
  startTime: ${this.startTime}
451
451
  endTime: ${this.endTime}
452
452
  defaultBatchLimit: ${this._defaultBatchLimit}
453
- initialReviewCap: ${this._initialReviewCap}`)}tick(){this._secondsRemaining=Math.floor((this.endTime.valueOf()-Date.now())/1e3),this._secondsRemaining<=0&&clearInterval(this._intervalHandle)}estimateCleanupTime(){let t=0;for(let c=0;c<this.failedQ.length;c++){let u=this.failedQ.peek(c),d=this._sessionRecord.find(t=>t.item.cardID===u.cardID),m=0;if(d){for(let t=0;t<d.records.length;t++)m+=d.records[t].timeSpent;m/=d.records.length,t+=m}}let c=t/1e3;return this.log(`Failed card cleanup estimate: ${Math.round(c)}`),c}estimateReviewTime(){let t=5*this.reviewQ.length;return this.log(`Review card time estimate: ${t}`),t}async prepareSession(){if(this.sources.some(t=>typeof t.getWeightedCards!=`function`))throw Error(`[SessionController] All content sources must implement getWeightedCards().`);let t=await this.getWeightedContent();this._wellIndicatedRemaining=t,t>=0&&t<_SessionController.MIN_WELL_INDICATED&&this.log(`[Init] Only ${t}/${_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(t){let c=this.normalizeReplanOptions(t),u=this._replanHasIntent(c);if(this._replanPromise){if(!u)return this.log(`Replan already in progress, coalescing unhinted auto-replan`),this._replanPromise;let t=c.label?` [${c.label}]`:``;this.log(`Replan in progress; queueing hint-bearing replan${t} behind in-flight run`);let d=this._replanPromise.catch(()=>void 0).then(()=>this._runReplan(c));return this._replanPromise=d.finally(()=>{this._replanPromise===d&&(this._replanPromise=null)}),d}let d=this._runReplan(c);this._replanPromise=d.finally(()=>{this._replanPromise===d&&(this._replanPromise=null)}),await d}_replanHasIntent(t){return!!(t.label||t.limit!==void 0||t.minFollowUpCards!==void 0||t.mode&&t.mode!==`replace`||t.hints&&Object.keys(t.hints).length>0)}async _runReplan(t){t.hints||(t.hints={});let c=t.hints,u=new Set(c.excludeCards??[]);this._currentCard?.item.cardID&&u.add(this._currentCard.item.cardID);for(let t of this._sessionRecord)u.add(t.card.card_id);if(this.newQ.length>0&&u.add(this.newQ.peek(0).cardID),c.excludeCards=[...u],t.hints){let c=t.label?{...t.hints,_label:t.label}:t.hints;for(let t of this.sources)t.setEphemeralHints?.(c)}let d=t.label?` [${t.label}]`:``;this.log(`Mid-session replan requested${d} (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}`)),await this._executeReplan(t)}async _replanUncoalesced(t){let c=this._runReplan(t);this._replanPromise=c.finally(()=>{this._replanPromise===c&&(this._replanPromise=null)}),await c}normalizeReplanOptions(t){if(!t)return{};let c=[`hints`,`limit`,`mode`,`label`,`minFollowUpCards`];return Object.keys(t).some(t=>c.includes(t))?t:{hints:t}}async _executeReplan(t={}){let c=t.limit,u=t.mode??`replace`,d=await this.getWeightedContent({replan:!0,additive:u===`merge`,limit:c});this._wellIndicatedRemaining=d,c!==void 0&&c<this._defaultBatchLimit?(this._suppressQualityReplan=!0,this.log(`[Replan] Burst mode (limit=${c}): suppressing quality-based auto-replan`)):this._suppressQualityReplan=!1,d>=0&&d<_SessionController.MIN_WELL_INDICATED&&this.log(`[Replan] Only ${d}/${_SessionController.MIN_WELL_INDICATED} well-indicated cards after replan`),await this.hydrationService.ensureHydratedCards();let m=t.label?` [${t.label}]`:``;this.log(`Replan complete${m}: newQ now has ${this.newQ.length} cards (mode=${u})`),snapshotQueues(this.reviewQ.length,this.newQ.length,this.failedQ.length)}addTime(t){this.endTime=new Date(this.endTime.valueOf()+1e3*t)}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 t=this.sources.some(t=>typeof t.getWeightedCards==`function`),extractQueueItems=(t,c=10)=>{let u=[];for(let d=0;d<Math.min(t.length,c);d++){let c=t.peek(d);u.push({courseID:c.courseID||`unknown`,cardID:c.cardID||`unknown`,status:c.status||`unknown`})}return u};return{api:{mode:t?`weighted`:`legacy`,description:t?`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(t){let c=t?.replan??!1,u=t?.additive??!1,d=t?.limit??this._defaultBatchLimit,m=c?d:d+this._initialReviewCap,g=[];for(let t=0;t<this.sources.length;t++){let c=this.sources[t];try{let u=(await c.getWeightedCards(m)).cards;g.push({sourceIndex:t,weighted:u})}catch(c){if(this.error(`Failed to get content from source ${t}:`,c),this.sources.length===1)throw Error(`Cannot start session: failed to load content from source ${t}`)}}if(g.length===0){if(c)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 b=this.mixer.mix(g,m*this.sources.length),S=g.map(t=>t.weighted[0]?.courseId||`source-${t.sourceIndex}`);await Promise.all(S.map(async t=>{if(!this.courseNameCache.has(t))try{let c=await this.dataLayer.getCoursesDB().getCourseConfig(t);this.courseNameCache.set(t,c.name)}catch{}}));let C=S.map(t=>this.courseNameCache.get(t)),w=this.mixer instanceof QuotaRoundRobinMixer?Math.ceil(m*this.sources.length/g.length):void 0;captureMixerRun(this.mixer.constructor.name,g,S,C,m*this.sources.length,w,b);let T=b.filter(t=>getCardOrigin(t)===`review`).slice(0,this._initialReviewCap),E=b.filter(t=>getCardOrigin(t)===`new`).slice(0,d);logger.debug(`[reviews] got ${T.length} reviews from mixer`);let D=c?`Replan content:
453
+ initialReviewCap: ${this._initialReviewCap}`)}tick(){this._secondsRemaining=Math.floor((this.endTime.valueOf()-Date.now())/1e3),this._secondsRemaining<=0&&clearInterval(this._intervalHandle)}estimateCleanupTime(){let t=0;for(let c=0;c<this.failedQ.length;c++){let u=this.failedQ.peek(c),d=this._sessionRecord.find(t=>t.item.cardID===u.cardID),m=0;if(d){for(let t=0;t<d.records.length;t++)m+=d.records[t].timeSpent;m/=d.records.length,t+=m}}let c=t/1e3;return this.log(`Failed card cleanup estimate: ${Math.round(c)}`),c}estimateReviewTime(){let t=5*this.reviewQ.length;return this.log(`Review card time estimate: ${t}`),t}async prepareSession(){if(this.sources.some(t=>typeof t.getWeightedCards!=`function`))throw Error(`[SessionController] All content sources must implement getWeightedCards().`);let t=await this.getWeightedContent();this._wellIndicatedRemaining=t,t>=0&&t<_SessionController.MIN_WELL_INDICATED&&this.log(`[Init] Only ${t}/${_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(t){let c=this.normalizeReplanOptions(t),u=this._replanHasIntent(c);if(this._replanPromise){if(!u)return this.log(`Replan already in progress, coalescing unhinted auto-replan`),this._replanPromise;let t=c.label?` [${c.label}]`:``;this.log(`Replan in progress; queueing hint-bearing replan${t} behind in-flight run`);let d=this._replanPromise.catch(()=>void 0).then(()=>this._runReplan(c));return this._replanPromise=d.finally(()=>{this._replanPromise===d&&(this._replanPromise=null)}),d}let d=this._runReplan(c);this._replanPromise=d.finally(()=>{this._replanPromise===d&&(this._replanPromise=null)}),await d}_replanHasIntent(t){return!!(t.label||t.limit!==void 0||t.minFollowUpCards!==void 0||t.mode&&t.mode!==`replace`||t.hints&&Object.keys(t.hints).length>0||t.sessionHints!==void 0)}async _runReplan(t){t.hints||(t.hints={});let c=t.hints,u=new Set(c.excludeCards??[]);this._currentCard?.item.cardID&&u.add(this._currentCard.item.cardID);for(let t of this._sessionRecord)u.add(t.card.card_id);this.newQ.length>0&&u.add(this.newQ.peek(0).cardID),c.excludeCards=[...u],t.sessionHints!==void 0&&(this._sessionHints=t.sessionHints,this.log(`[Replan] Session hints ${t.sessionHints?`set`:`cleared`}: ${JSON.stringify(t.sessionHints)}`)),this._applyHintsToSources(t.hints,t.label);let d=t.label?` [${t.label}]`:``;this.log(`Mid-session replan requested${d} (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}`)),await this._executeReplan(t)}setSessionHints(t){this._sessionHints=t,this.log(`Session hints ${t?`set`:`cleared`}: ${JSON.stringify(t)}`)}getSessionHints(){return this._sessionHints}mergeSessionHints(t){this._sessionHints=mergeHints2([this._sessionHints,t])??null,this.log(`Session hints merged: ${JSON.stringify(this._sessionHints)}`)}_applyHintsToSources(t,c){let u=t&&c?{...t,_label:c}:t,d=mergeHints2([this._sessionHints,u]);if(d)for(let t of this.sources)t.setEphemeralHints?.(d)}_getSessionControls(){return this._sessionControls||(this._sessionControls={getSessionHints:()=>this.getSessionHints(),setSessionHints:t=>this.setSessionHints(t),mergeSessionHints:t=>this.mergeSessionHints(t),requestReplan:t=>this.requestReplan(t)}),this._sessionControls}async _notifyOutcomeObservers(t,c,u){if(this._outcomeObservers.length===0||!isQuestionRecord(t))return;let d={record:t,card:c.card,result:u},m=this._getSessionControls();for(let t of this._outcomeObservers)try{await t(d,m)}catch(t){this.error(`[OutcomeObserver] observer threw; ignoring`,t)}}async _replanUncoalesced(t){let c=this._runReplan(t);this._replanPromise=c.finally(()=>{this._replanPromise===c&&(this._replanPromise=null)}),await c}normalizeReplanOptions(t){if(!t)return{};let c=[`hints`,`sessionHints`,`limit`,`mode`,`label`,`minFollowUpCards`];return Object.keys(t).some(t=>c.includes(t))?t:{hints:t}}async _executeReplan(t={}){let c=t.limit,u=t.mode??`replace`,d=await this.getWeightedContent({replan:!0,additive:u===`merge`,limit:c});this._wellIndicatedRemaining=d,c!==void 0&&c<this._defaultBatchLimit?(this._suppressQualityReplan=!0,this.log(`[Replan] Burst mode (limit=${c}): suppressing quality-based auto-replan`)):this._suppressQualityReplan=!1,d>=0&&d<_SessionController.MIN_WELL_INDICATED&&this.log(`[Replan] Only ${d}/${_SessionController.MIN_WELL_INDICATED} well-indicated cards after replan`),await this.hydrationService.ensureHydratedCards();let m=t.label?` [${t.label}]`:``;this.log(`Replan complete${m}: newQ now has ${this.newQ.length} cards (mode=${u})`),snapshotQueues(this.reviewQ.length,this.newQ.length,this.failedQ.length)}addTime(t){this.endTime=new Date(this.endTime.valueOf()+1e3*t)}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 t=this.sources.some(t=>typeof t.getWeightedCards==`function`),extractQueueItems=(t,c=10)=>{let u=[];for(let d=0;d<Math.min(t.length,c);d++){let c=t.peek(d);u.push({courseID:c.courseID||`unknown`,cardID:c.cardID||`unknown`,status:c.status||`unknown`})}return u};return{api:{mode:t?`weighted`:`legacy`,description:t?`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(t){let c=t?.replan??!1,u=t?.additive??!1,d=t?.limit??this._defaultBatchLimit,m=c?d:d+this._initialReviewCap;c||this._applyHintsToSources();let g=[];for(let t=0;t<this.sources.length;t++){let c=this.sources[t];try{let u=(await c.getWeightedCards(m)).cards;g.push({sourceIndex:t,weighted:u})}catch(c){if(this.error(`Failed to get content from source ${t}:`,c),this.sources.length===1)throw Error(`Cannot start session: failed to load content from source ${t}`)}}if(g.length===0){if(c)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 b=this.mixer.mix(g,m*this.sources.length),S=g.map(t=>t.weighted[0]?.courseId||`source-${t.sourceIndex}`);await Promise.all(S.map(async t=>{if(!this.courseNameCache.has(t))try{let c=await this.dataLayer.getCoursesDB().getCourseConfig(t);this.courseNameCache.set(t,c.name)}catch{}}));let C=S.map(t=>this.courseNameCache.get(t)),w=this.mixer instanceof QuotaRoundRobinMixer?Math.ceil(m*this.sources.length/g.length):void 0;captureMixerRun(this.mixer.constructor.name,g,S,C,m*this.sources.length,w,b);let T=b.filter(t=>getCardOrigin(t)===`review`).slice(0,this._initialReviewCap),E=b.filter(t=>getCardOrigin(t)===`new`).slice(0,d);logger.debug(`[reviews] got ${T.length} reviews from mixer`);let D=c?`Replan content:
454
454
  `:`Mixed content session created with:
455
455
  `;if(!c)for(let t of T){let c={cardID:t.cardId,courseID:t.courseId,contentSourceType:`course`,contentSourceID:t.courseId,reviewID:t.reviewID,status:`review`};this.reviewQ.add(c,c.cardID),D+=`Review: ${t.courseId}::${t.cardId} (score: ${t.score.toFixed(2)})
456
456
  `}let O=E.filter(t=>t.score>=_SessionController.WELL_INDICATED_SCORE).length,Or=[];for(let t of E){let c={cardID:t.cardId,courseID:t.courseId,contentSourceType:`course`,contentSourceID:t.courseId,status:`new`};Or.push(c),D+=`New: ${t.courseId}::${t.cardId} (score: ${t.score.toFixed(2)})
457
457
  `}if(u){let t=this.newQ.mergeToFront(Or,t=>t.cardID);D+=`Additive merge: ${t} new cards added to front of newQ
458
- `}else if(c)this.newQ.replaceAll(Or,t=>t.cardID);else for(let t of Or)this.newQ.add(t,t.cardID);return this.log(D),O}_getItemsToHydrate(){let t=[],c=2;for(let c=0;c<Math.min(2,this.reviewQ.length);c++)t.push(this.reviewQ.peek(c));for(let c=0;c<Math.min(2,this.newQ.length);c++)t.push(this.newQ.peek(c));for(let c=0;c<Math.min(2,this.failedQ.length);c++)t.push(this.failedQ.peek(c));return t}_selectNextItemToHydrate(){let t=Math.random(),c=.1,u=.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 d=this.estimateCleanupTime(),m=this.estimateReviewTime();return this._secondsRemaining-(d+m)>20?(c=.5,u=.9):this._secondsRemaining-d>20?(c=.05,u=.9):(c=.01,u=.1),this.failedQ.length===0&&(u=1),this.reviewQ.length===0&&(c=u),t<c&&this.newQ.length?this.newQ.peek(0):t<u&&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(t=`dismiss-success`){if(this.dismissCurrentCard(t),this._minCardsGuarantee>0&&(this._minCardsGuarantee--,this.log(`[CardGuarantee] ${this._minCardsGuarantee} guaranteed cards remaining`)),this._replanPromise&&this.newQ.length===0&&this.reviewQ.length===0&&this.failedQ.length===0&&(this.log(`nextCard: queues empty, awaiting in-flight replan before drawing`),await this._replanPromise),this.newQ.length<=_SessionController.DEPLETION_PREFETCH_THRESHOLD&&this._secondsRemaining>0&&!this._replanPromise){this._suppressQualityReplan=!1;let t=this.reviewQ.length+this.failedQ.length;this.log(`[AutoReplan:depletion] newQ has ${this.newQ.length} card(s) (${t} 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 c=3,u=250,d=0;for(;this._secondsRemaining>0&&this.newQ.length===0&&this.reviewQ.length===0&&this.failedQ.length===0;)if(this.log(`[WedgeBreaker] All queues empty with ${this._secondsRemaining}s remaining. Running pipeline (attempt ${d+1}/3).`),await this._replanUncoalesced({label:`wedge-breaker`}),this.newQ.length===0&&this.reviewQ.length===0&&this.failedQ.length===0){if(d++,d>=3){this.log(`[WedgeBreaker] Pipeline returned no content 3 consecutive times. Giving up; session will end.`);break}await new Promise(t=>setTimeout(t,250))}else d=0;let m=20;for(let t=0;t<20;t++){let t=this._selectNextItemToHydrate();if(!t)return this._currentCard=null,endSessionTracking(),null;let c=this.hydrationService.getHydratedCard(t.cardID);if(c||(c=await this.hydrationService.waitForCard(t.cardID)),this.removeItemFromQueue(t),c){await this.hydrationService.ensureHydratedCards(),this._currentCard=c;let u=t.status===`review`||t.status===`failed-review`?`review`:t.status===`new`||t.status===`failed-new`?`new`:`failed`,d=t.status.startsWith(`failed`)?`failedQ`:t.status===`review`?`reviewQ`:`newQ`;return recordCardPresentation(t.cardID,t.courseID,this.courseNameCache.get(t.courseID),u,d),snapshotQueues(this.reviewQ.length,this.newQ.length,this.failedQ.length),c}this.log(`Skipping card ${t.cardID}: hydration failed, trying next`),isReview(t)&&this.srsService.removeReview(t.reviewID)}return this.log(`Exhausted 20 skip attempts finding a hydratable card`),this._currentCard=null,endSessionTracking(),null}async submitResponse(t,c,u,d,m,g,b,S,C){let w={...d.item};return await this.services.response.processResponse(t,c,w,u,d,m,g,b,S,C)}dismissCurrentCard(t=`dismiss-success`){if(this._currentCard)if(t===`dismiss-success`)this.hydrationService.removeCard(this._currentCard.item.cardID);else if(t===`marked-failed`){let t;t=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(t,t.cardID)}else (t===`dismiss-error`||t===`dismiss-failed`)&&this.hydrationService.removeCard(this._currentCard.item.cardID)}removeItemFromQueue(t){this.reviewQ.peek(0)?.cardID===t.cardID?this.reviewQ.dequeue(t=>t.cardID):this.newQ.peek(0)?.cardID===t.cardID?(this.newQ.dequeue(t=>t.cardID),this._wellIndicatedRemaining>0&&this._wellIndicatedRemaining--):this.failedQ.peek(0)?.cardID===t.cardID&&this.failedQ.dequeue(t=>t.cardID)}async endSession(){if(!this._sessionRecord||this._sessionRecord.length===0)return;let t=this._sessionRecord.flatMap(t=>t.records).filter(t=>t.userAnswer!==void 0);if(t.length===0)return;let c=null,u=[];for(let t of this.sources)if(t.getOrchestrationContext){try{c=await t.getOrchestrationContext(),t.getStrategyIds&&u.push(...t.getStrategyIds())}catch(t){logger.warn(`[SessionController] Failed to get orchestration context: ${t}`)}if(c)break}if(!c){logger.debug(`[SessionController] No orchestration context available, skipping outcome recording`);return}let d=new Date().toISOString(),m=new Date(this.startTime).toISOString();await recordUserOutcome(c,m,d,t,u)}},_defineProperty$2(_SessionController2,`MIN_WELL_INDICATED`,5),_defineProperty$2(_SessionController2,`WELL_INDICATED_SCORE`,.1),_defineProperty$2(_SessionController2,`DEPLETION_PREFETCH_THRESHOLD`,3),_SessionController2),init_TagFilteredContentSource(),init_factory()}));function useRouter(){return inject(routerKey)}function useRoute(t){return inject(routeLocationKey)}var isArray,NavigationType,NavigationDirection,NavigationFailureSymbol,NavigationFailureType,matchedRouteKey,viewDepthKey,routerKey,routeLocationKey,routerViewLocationKey,init_vue_router=__esmMin((()=>{init_vue_runtime_esm_bundler(),Array.isArray,(function(t){t.pop=`pop`,t.push=`push`})(NavigationType||(NavigationType={})),(function(t){t.back=`back`,t.forward=`forward`,t.unknown=``})(NavigationDirection||(NavigationDirection={})),Symbol(process.env.NODE_ENV===`production`?``:`navigation failure`),(function(t){t[t.aborted=4]=`aborted`,t[t.cancelled=8]=`cancelled`,t[t.duplicated=16]=`duplicated`})(NavigationFailureType||(NavigationFailureType={})),Symbol(process.env.NODE_ENV===`production`?``:`router view location matched`),Symbol(process.env.NODE_ENV===`production`?``:`router view depth`),routerKey=Symbol(process.env.NODE_ENV===`production`?``:`router`),routeLocationKey=Symbol(process.env.NODE_ENV===`production`?``:`route location`),Symbol(process.env.NODE_ENV===`production`?``:`router view location`)})),init_style=__esmMin((()=>{}));function registerRuntimeHelpers(t){Object.getOwnPropertySymbols(t).forEach(c=>{helperNameMap[c]=t[c]})}function createRoot(t,c=``){return{type:0,source:c,children:t,helpers:new Set,components:[],directives:[],hoists:[],imports:[],cached:[],temps:0,codegenNode:void 0,loc:locStub}}function createVNodeCall(t,c,u,d,m,g,b,S=!1,C=!1,w=!1,T=locStub){return t&&(S?(t.helper(OPEN_BLOCK),t.helper(getVNodeBlockHelper(t.inSSR,w))):t.helper(getVNodeHelper(t.inSSR,w)),b&&t.helper(WITH_DIRECTIVES)),{type:13,tag:c,props:u,children:d,patchFlag:m,dynamicProps:g,directives:b,isBlock:S,disableTracking:C,isComponent:w,loc:T}}function createArrayExpression(t,c=locStub){return{type:17,loc:c,elements:t}}function createObjectExpression(t,c=locStub){return{type:15,loc:c,properties:t}}function createObjectProperty(t,c){return{type:16,loc:locStub,key:isString$1(t)?createSimpleExpression(t,!0):t,value:c}}function createSimpleExpression(t,c=!1,u=locStub,d=0){return{type:4,loc:u,content:t,isStatic:c,constType:c?3:d}}function createInterpolation(t,c){return{type:5,loc:c,content:isString$1(t)?createSimpleExpression(t,!1,c):t}}function createCompoundExpression(t,c=locStub){return{type:8,loc:c,children:t}}function createCallExpression(t,c=[],u=locStub){return{type:14,loc:u,callee:t,arguments:c}}function createFunctionExpression(t,c=void 0,u=!1,d=!1,m=locStub){return{type:18,params:t,returns:c,newline:u,isSlot:d,loc:m}}function createConditionalExpression(t,c,u,d=!0){return{type:19,test:t,consequent:c,alternate:u,newline:d,loc:locStub}}function createCacheExpression(t,c,u=!1,d=!1){return{type:20,index:t,value:c,needPauseTracking:u,inVOnce:d,needArraySpread:!1,loc:locStub}}function createBlockStatement(t){return{type:21,body:t,loc:locStub}}function createTemplateLiteral(t){return{type:22,elements:t,loc:locStub}}function createIfStatement(t,c,u){return{type:23,test:t,consequent:c,alternate:u,loc:locStub}}function createAssignmentExpression(t,c){return{type:24,left:t,right:c,loc:locStub}}function createSequenceExpression(t){return{type:25,expressions:t,loc:locStub}}function createReturnStatement(t){return{type:26,returns:t,loc:locStub}}function getVNodeHelper(t,c){return t||c?CREATE_VNODE:CREATE_ELEMENT_VNODE}function getVNodeBlockHelper(t,c){return t||c?CREATE_BLOCK:CREATE_ELEMENT_BLOCK}function convertToBlock(t,{helper:c,removeHelper:u,inSSR:d}){t.isBlock||(t.isBlock=!0,u(getVNodeHelper(d,t.isComponent)),c(OPEN_BLOCK),c(getVNodeBlockHelper(d,t.isComponent)))}function isTagStartChar(t){return t>=97&&t<=122||t>=65&&t<=90}function isWhitespace(t){return t===32||t===10||t===9||t===12||t===13}function isEndOfTagSection(t){return t===47||t===62||isWhitespace(t)}function toCharCodes(t){let c=new Uint8Array(t.length);for(let u=0;u<t.length;u++)c[u]=t.charCodeAt(u);return c}function getCompatValue(t,{compatConfig:c}){let u=c&&c[t];return t===`MODE`?u||3:u}function isCompatEnabled(t,c){let u=getCompatValue(`MODE`,c),d=getCompatValue(t,c);return u===3?d===!0:d!==!1}function checkCompatEnabled(t,c,u,...d){let m=isCompatEnabled(t,c);return process.env.NODE_ENV!==`production`&&m&&warnDeprecation(t,c,u,...d),m}function warnDeprecation(t,c,u,...d){if(getCompatValue(t,c)===`suppress-warning`)return;let{message:m,link:g}=deprecationData[t],b=`(deprecation ${t}) ${typeof m==`function`?m(...d):m}${g?`
458
+ `}else if(c)this.newQ.replaceAll(Or,t=>t.cardID);else for(let t of Or)this.newQ.add(t,t.cardID);return this.log(D),O}_getItemsToHydrate(){let t=[],c=2;for(let c=0;c<Math.min(2,this.reviewQ.length);c++)t.push(this.reviewQ.peek(c));for(let c=0;c<Math.min(2,this.newQ.length);c++)t.push(this.newQ.peek(c));for(let c=0;c<Math.min(2,this.failedQ.length);c++)t.push(this.failedQ.peek(c));return t}_selectNextItemToHydrate(){let t=Math.random(),c=.1,u=.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 d=this.estimateCleanupTime(),m=this.estimateReviewTime();return this._secondsRemaining-(d+m)>20?(c=.5,u=.9):this._secondsRemaining-d>20?(c=.05,u=.9):(c=.01,u=.1),this.failedQ.length===0&&(u=1),this.reviewQ.length===0&&(c=u),t<c&&this.newQ.length?this.newQ.peek(0):t<u&&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(t=`dismiss-success`){if(this.dismissCurrentCard(t),this._minCardsGuarantee>0&&(this._minCardsGuarantee--,this.log(`[CardGuarantee] ${this._minCardsGuarantee} guaranteed cards remaining`)),this._replanPromise&&this.newQ.length===0&&this.reviewQ.length===0&&this.failedQ.length===0&&(this.log(`nextCard: queues empty, awaiting in-flight replan before drawing`),await this._replanPromise),this.newQ.length<=_SessionController.DEPLETION_PREFETCH_THRESHOLD&&this._secondsRemaining>0&&!this._replanPromise){this._suppressQualityReplan=!1;let t=this.reviewQ.length+this.failedQ.length;this.log(`[AutoReplan:depletion] newQ has ${this.newQ.length} card(s) (${t} 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 c=3,u=250,d=0;for(;this._secondsRemaining>0&&this.newQ.length===0&&this.reviewQ.length===0&&this.failedQ.length===0;)if(this.log(`[WedgeBreaker] All queues empty with ${this._secondsRemaining}s remaining. Running pipeline (attempt ${d+1}/3).`),await this._replanUncoalesced({label:`wedge-breaker`}),this.newQ.length===0&&this.reviewQ.length===0&&this.failedQ.length===0){if(d++,d>=3){this.log(`[WedgeBreaker] Pipeline returned no content 3 consecutive times. Giving up; session will end.`);break}await new Promise(t=>setTimeout(t,250))}else d=0;let m=20;for(let t=0;t<20;t++){let t=this._selectNextItemToHydrate();if(!t)return this._currentCard=null,endSessionTracking(),null;let c=this.hydrationService.getHydratedCard(t.cardID);if(c||(c=await this.hydrationService.waitForCard(t.cardID)),this.removeItemFromQueue(t),c){await this.hydrationService.ensureHydratedCards(),this._currentCard=c;let u=t.status===`review`||t.status===`failed-review`?`review`:t.status===`new`||t.status===`failed-new`?`new`:`failed`,d=t.status.startsWith(`failed`)?`failedQ`:t.status===`review`?`reviewQ`:`newQ`;return recordCardPresentation(t.cardID,t.courseID,this.courseNameCache.get(t.courseID),u,d),snapshotQueues(this.reviewQ.length,this.newQ.length,this.failedQ.length),c}this.log(`Skipping card ${t.cardID}: hydration failed, trying next`),isReview(t)&&this.srsService.removeReview(t.reviewID)}return this.log(`Exhausted 20 skip attempts finding a hydratable card`),this._currentCard=null,endSessionTracking(),null}async submitResponse(t,c,u,d,m,g,b,S,C){let w={...d.item},T=await this.services.response.processResponse(t,c,w,u,d,m,g,b,S,C);return await this._notifyOutcomeObservers(t,d,T),T}dismissCurrentCard(t=`dismiss-success`){if(this._currentCard)if(t===`dismiss-success`)this.hydrationService.removeCard(this._currentCard.item.cardID);else if(t===`marked-failed`){let t;t=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(t,t.cardID)}else (t===`dismiss-error`||t===`dismiss-failed`)&&this.hydrationService.removeCard(this._currentCard.item.cardID)}removeItemFromQueue(t){this.reviewQ.peek(0)?.cardID===t.cardID?this.reviewQ.dequeue(t=>t.cardID):this.newQ.peek(0)?.cardID===t.cardID?(this.newQ.dequeue(t=>t.cardID),this._wellIndicatedRemaining>0&&this._wellIndicatedRemaining--):this.failedQ.peek(0)?.cardID===t.cardID&&this.failedQ.dequeue(t=>t.cardID)}async endSession(){if(!this._sessionRecord||this._sessionRecord.length===0)return;let t=this._sessionRecord.flatMap(t=>t.records).filter(t=>t.userAnswer!==void 0);if(t.length===0)return;let c=null,u=[];for(let t of this.sources)if(t.getOrchestrationContext){try{c=await t.getOrchestrationContext(),t.getStrategyIds&&u.push(...t.getStrategyIds())}catch(t){logger.warn(`[SessionController] Failed to get orchestration context: ${t}`)}if(c)break}if(!c){logger.debug(`[SessionController] No orchestration context available, skipping outcome recording`);return}let d=new Date().toISOString(),m=new Date(this.startTime).toISOString();await recordUserOutcome(c,m,d,t,u)}},_defineProperty$2(_SessionController2,`MIN_WELL_INDICATED`,5),_defineProperty$2(_SessionController2,`WELL_INDICATED_SCORE`,.1),_defineProperty$2(_SessionController2,`DEPLETION_PREFETCH_THRESHOLD`,3),_SessionController2),init_TagFilteredContentSource(),init_factory()}));function useRouter(){return inject(routerKey)}function useRoute(t){return inject(routeLocationKey)}var isArray,NavigationType,NavigationDirection,NavigationFailureSymbol,NavigationFailureType,matchedRouteKey,viewDepthKey,routerKey,routeLocationKey,routerViewLocationKey,init_vue_router=__esmMin((()=>{init_vue_runtime_esm_bundler(),Array.isArray,(function(t){t.pop=`pop`,t.push=`push`})(NavigationType||(NavigationType={})),(function(t){t.back=`back`,t.forward=`forward`,t.unknown=``})(NavigationDirection||(NavigationDirection={})),Symbol(process.env.NODE_ENV===`production`?``:`navigation failure`),(function(t){t[t.aborted=4]=`aborted`,t[t.cancelled=8]=`cancelled`,t[t.duplicated=16]=`duplicated`})(NavigationFailureType||(NavigationFailureType={})),Symbol(process.env.NODE_ENV===`production`?``:`router view location matched`),Symbol(process.env.NODE_ENV===`production`?``:`router view depth`),routerKey=Symbol(process.env.NODE_ENV===`production`?``:`router`),routeLocationKey=Symbol(process.env.NODE_ENV===`production`?``:`route location`),Symbol(process.env.NODE_ENV===`production`?``:`router view location`)})),init_style=__esmMin((()=>{}));function registerRuntimeHelpers(t){Object.getOwnPropertySymbols(t).forEach(c=>{helperNameMap[c]=t[c]})}function createRoot(t,c=``){return{type:0,source:c,children:t,helpers:new Set,components:[],directives:[],hoists:[],imports:[],cached:[],temps:0,codegenNode:void 0,loc:locStub}}function createVNodeCall(t,c,u,d,m,g,b,S=!1,C=!1,w=!1,T=locStub){return t&&(S?(t.helper(OPEN_BLOCK),t.helper(getVNodeBlockHelper(t.inSSR,w))):t.helper(getVNodeHelper(t.inSSR,w)),b&&t.helper(WITH_DIRECTIVES)),{type:13,tag:c,props:u,children:d,patchFlag:m,dynamicProps:g,directives:b,isBlock:S,disableTracking:C,isComponent:w,loc:T}}function createArrayExpression(t,c=locStub){return{type:17,loc:c,elements:t}}function createObjectExpression(t,c=locStub){return{type:15,loc:c,properties:t}}function createObjectProperty(t,c){return{type:16,loc:locStub,key:isString$1(t)?createSimpleExpression(t,!0):t,value:c}}function createSimpleExpression(t,c=!1,u=locStub,d=0){return{type:4,loc:u,content:t,isStatic:c,constType:c?3:d}}function createInterpolation(t,c){return{type:5,loc:c,content:isString$1(t)?createSimpleExpression(t,!1,c):t}}function createCompoundExpression(t,c=locStub){return{type:8,loc:c,children:t}}function createCallExpression(t,c=[],u=locStub){return{type:14,loc:u,callee:t,arguments:c}}function createFunctionExpression(t,c=void 0,u=!1,d=!1,m=locStub){return{type:18,params:t,returns:c,newline:u,isSlot:d,loc:m}}function createConditionalExpression(t,c,u,d=!0){return{type:19,test:t,consequent:c,alternate:u,newline:d,loc:locStub}}function createCacheExpression(t,c,u=!1,d=!1){return{type:20,index:t,value:c,needPauseTracking:u,inVOnce:d,needArraySpread:!1,loc:locStub}}function createBlockStatement(t){return{type:21,body:t,loc:locStub}}function createTemplateLiteral(t){return{type:22,elements:t,loc:locStub}}function createIfStatement(t,c,u){return{type:23,test:t,consequent:c,alternate:u,loc:locStub}}function createAssignmentExpression(t,c){return{type:24,left:t,right:c,loc:locStub}}function createSequenceExpression(t){return{type:25,expressions:t,loc:locStub}}function createReturnStatement(t){return{type:26,returns:t,loc:locStub}}function getVNodeHelper(t,c){return t||c?CREATE_VNODE:CREATE_ELEMENT_VNODE}function getVNodeBlockHelper(t,c){return t||c?CREATE_BLOCK:CREATE_ELEMENT_BLOCK}function convertToBlock(t,{helper:c,removeHelper:u,inSSR:d}){t.isBlock||(t.isBlock=!0,u(getVNodeHelper(d,t.isComponent)),c(OPEN_BLOCK),c(getVNodeBlockHelper(d,t.isComponent)))}function isTagStartChar(t){return t>=97&&t<=122||t>=65&&t<=90}function isWhitespace(t){return t===32||t===10||t===9||t===12||t===13}function isEndOfTagSection(t){return t===47||t===62||isWhitespace(t)}function toCharCodes(t){let c=new Uint8Array(t.length);for(let u=0;u<t.length;u++)c[u]=t.charCodeAt(u);return c}function getCompatValue(t,{compatConfig:c}){let u=c&&c[t];return t===`MODE`?u||3:u}function isCompatEnabled(t,c){let u=getCompatValue(`MODE`,c),d=getCompatValue(t,c);return u===3?d===!0:d!==!1}function checkCompatEnabled(t,c,u,...d){let m=isCompatEnabled(t,c);return process.env.NODE_ENV!==`production`&&m&&warnDeprecation(t,c,u,...d),m}function warnDeprecation(t,c,u,...d){if(getCompatValue(t,c)===`suppress-warning`)return;let{message:m,link:g}=deprecationData[t],b=`(deprecation ${t}) ${typeof m==`function`?m(...d):m}${g?`
459
459
  Details: ${g}`:``}`,S=SyntaxError(b);S.code=t,u&&(S.loc=u),c.onWarn(S)}function defaultOnError(t){throw t}function defaultOnWarn(t){process.env.NODE_ENV!==`production`&&console.warn(`[Vue warn] ${t.message}`)}function createCompilerError(t,c,u,d){let m=process.env.NODE_ENV===`production`?`https://vuejs.org/error-reference/#compiler-${t}`:(u||errorMessages)[t]+(d||``),g=SyntaxError(String(m));return g.code=t,g.loc=c,g}function walkIdentifiers(t,c,u=!1,d=[],m=Object.create(null)){}function isReferencedIdentifier(t,c,u){return!1}function isInDestructureAssignment(t,c){if(t&&(t.type===`ObjectProperty`||t.type===`ArrayPattern`)){let t=c.length;for(;t--;){let u=c[t];if(u.type===`AssignmentExpression`)return!0;if(u.type!==`ObjectProperty`&&!u.type.endsWith(`Pattern`))break}}return!1}function isInNewExpression(t){let c=t.length;for(;c--;){let u=t[c];if(u.type===`NewExpression`)return!0;if(u.type!==`MemberExpression`)break}return!1}function walkFunctionParams(t,c){for(let u of t.params)for(let t of extractIdentifiers(u))c(t)}function walkBlockDeclarations(t,c){for(let u of t.body)if(u.type===`VariableDeclaration`){if(u.declare)continue;for(let t of u.declarations)for(let u of extractIdentifiers(t.id))c(u)}else if(u.type===`FunctionDeclaration`||u.type===`ClassDeclaration`){if(u.declare||!u.id)continue;c(u.id)}else isForStatement(u)&&walkForStatement(u,!0,c)}function isForStatement(t){return t.type===`ForOfStatement`||t.type===`ForInStatement`||t.type===`ForStatement`}function walkForStatement(t,c,u){let d=t.type===`ForStatement`?t.init:t.left;if(d&&d.type===`VariableDeclaration`&&(d.kind===`var`?c:!c))for(let t of d.declarations)for(let c of extractIdentifiers(t.id))u(c)}function extractIdentifiers(t,c=[]){switch(t.type){case`Identifier`:c.push(t);break;case`MemberExpression`:let u=t;for(;u.type===`MemberExpression`;)u=u.object;c.push(u);break;case`ObjectPattern`:for(let u of t.properties)u.type===`RestElement`?extractIdentifiers(u.argument,c):extractIdentifiers(u.value,c);break;case`ArrayPattern`:t.elements.forEach(t=>{t&&extractIdentifiers(t,c)});break;case`RestElement`:extractIdentifiers(t.argument,c);break;case`AssignmentPattern`:extractIdentifiers(t.left,c);break}return c}function unwrapTSNode(t){return TS_NODE_TYPES.includes(t.type)?unwrapTSNode(t.expression):t}function isCoreComponent(t){switch(t){case`Teleport`:case`teleport`:return TELEPORT;case`Suspense`:case`suspense`:return SUSPENSE;case`KeepAlive`:case`keep-alive`:return KEEP_ALIVE;case`BaseTransition`:case`base-transition`:return BASE_TRANSITION}}function advancePositionWithClone(t,c,u=c.length){return advancePositionWithMutation({offset:t.offset,line:t.line,column:t.column},c,u)}function advancePositionWithMutation(t,c,u=c.length){let d=0,m=-1;for(let t=0;t<u;t++)c.charCodeAt(t)===10&&(d++,m=t);return t.offset+=u,t.line+=d,t.column=m===-1?t.column+u:u-m,t}function assert(t,c){if(!t)throw Error(c||`unexpected compiler condition`)}function findDir(t,c,u=!1){for(let d=0;d<t.props.length;d++){let m=t.props[d];if(m.type===7&&(u||m.exp)&&(isString$1(c)?m.name===c:c.test(m.name)))return m}}function findProp(t,c,u=!1,d=!1){for(let m=0;m<t.props.length;m++){let g=t.props[m];if(g.type===6){if(u)continue;if(g.name===c&&(g.value||d))return g}else if(g.name===`bind`&&(g.exp||d)&&isStaticArgOf(g.arg,c))return g}}function isStaticArgOf(t,c){return!!(t&&isStaticExp(t)&&t.content===c)}function hasDynamicKeyVBind(t){return t.props.some(t=>t.type===7&&t.name===`bind`&&(!t.arg||t.arg.type!==4||!t.arg.isStatic))}function isText$1(t){return t.type===5||t.type===2}function isVSlot(t){return t.type===7&&t.name===`slot`}function isTemplateNode(t){return t.type===1&&t.tagType===3}function isSlotOutlet(t){return t.type===1&&t.tagType===2}function getUnnormalizedProps(t,c=[]){if(t&&!isString$1(t)&&t.type===14){let u=t.callee;if(!isString$1(u)&&propsHelperSet.has(u))return getUnnormalizedProps(t.arguments[0],c.concat(t))}return[t,c]}function injectProp(t,c,u){let d,m=t.type===13?t.props:t.arguments[2],g=[],b;if(m&&!isString$1(m)&&m.type===14){let t=getUnnormalizedProps(m);m=t[0],g=t[1],b=g[g.length-1]}if(m==null||isString$1(m))d=createObjectExpression([c]);else if(m.type===14){let t=m.arguments[0];!isString$1(t)&&t.type===15?hasProp(c,t)||t.properties.unshift(c):m.callee===TO_HANDLERS?d=createCallExpression(u.helper(MERGE_PROPS),[createObjectExpression([c]),m]):m.arguments.unshift(createObjectExpression([c])),!d&&(d=m)}else m.type===15?(hasProp(c,m)||m.properties.unshift(c),d=m):(d=createCallExpression(u.helper(MERGE_PROPS),[createObjectExpression([c]),m]),b&&b.callee===GUARD_REACTIVE_PROPS&&(b=g[g.length-2]));t.type===13?b?b.arguments[0]=d:t.props=d:b?b.arguments[0]=d:t.arguments[2]=d}function hasProp(t,c){let u=!1;if(t.key.type===4){let d=t.key.content;u=c.properties.some(t=>t.key.type===4&&t.key.content===d)}return u}function toValidAssetId(t,c){return`_${c}_${t.replace(/[^\w]/g,(c,u)=>c===`-`?`_`:t.charCodeAt(u).toString())}`}function hasScopeRef(t,c){if(!t||Object.keys(c).length===0)return!1;switch(t.type){case 1:for(let u=0;u<t.props.length;u++){let d=t.props[u];if(d.type===7&&(hasScopeRef(d.arg,c)||hasScopeRef(d.exp,c)))return!0}return t.children.some(t=>hasScopeRef(t,c));case 11:return hasScopeRef(t.source,c)?!0:t.children.some(t=>hasScopeRef(t,c));case 9:return t.branches.some(t=>hasScopeRef(t,c));case 10:return hasScopeRef(t.condition,c)?!0:t.children.some(t=>hasScopeRef(t,c));case 4:return!t.isStatic&&isSimpleIdentifier(t.content)&&!!c[t.content];case 8:return t.children.some(t=>isObject$1(t)&&hasScopeRef(t,c));case 5:case 12:return hasScopeRef(t.content,c);case 2:case 3:case 20:return!1;default:return process.env.NODE_ENV,!1}}function getMemoedVNodeCall(t){return t.type===14&&t.callee===WITH_MEMO?t.arguments[1].returns:t}function parseForExpression(t){let c=t.loc,u=t.content,d=u.match(forAliasRE);if(!d)return;let[,m,g]=d,createAliasExpression=(t,u,d=!1)=>{let m=c.start.offset+u;return createExp(t,!1,getLoc(m,m+t.length),0,d?1:0)},b={source:createAliasExpression(g.trim(),u.indexOf(g,m.length)),value:void 0,key:void 0,index:void 0,finalized:!1},S=m.trim().replace(stripParensRE,``).trim(),C=m.indexOf(S),w=S.match(forIteratorRE);if(w){S=S.replace(forIteratorRE,``).trim();let t=w[1].trim(),c;if(t&&(c=u.indexOf(t,C+S.length),b.key=createAliasExpression(t,c,!0)),w[2]){let d=w[2].trim();d&&(b.index=createAliasExpression(d,u.indexOf(d,b.key?c+t.length:C+S.length),!0))}}return S&&(b.value=createAliasExpression(S,C,!0)),b}function getSlice(t,c){return currentInput.slice(t,c)}function endOpenTag(t){tokenizer.inSFCRoot&&(currentOpenTag.innerLoc=getLoc(t+1,t+1)),addNode(currentOpenTag);let{tag:c,ns:u}=currentOpenTag;u===0&&currentOptions.isPreTag(c)&&inPre++,currentOptions.isVoidTag(c)?onCloseTag(currentOpenTag,t):(stack.unshift(currentOpenTag),(u===1||u===2)&&(tokenizer.inXML=!0)),currentOpenTag=null}function onText(t,c,u){{let c=stack[0]&&stack[0].tag;c!==`script`&&c!==`style`&&t.includes(`&`)&&(t=currentOptions.decodeEntities(t,!1))}let d=stack[0]||currentRoot,m=d.children[d.children.length-1];m&&m.type===2?(m.content+=t,setLocEnd(m.loc,u)):d.children.push({type:2,content:t,loc:getLoc(c,u)})}function onCloseTag(t,c,u=!1){u?setLocEnd(t.loc,backTrack(c,60)):setLocEnd(t.loc,lookAhead(c,62)+1),tokenizer.inSFCRoot&&(t.children.length?t.innerLoc.end=extend$2({},t.children[t.children.length-1].loc.end):t.innerLoc.end=extend$2({},t.innerLoc.start),t.innerLoc.source=getSlice(t.innerLoc.start.offset,t.innerLoc.end.offset));let{tag:d,ns:m,children:g}=t;if(inVPre||(d===`slot`?t.tagType=2:isFragmentTemplate(t)?t.tagType=3:isComponent(t)&&(t.tagType=1)),tokenizer.inRCDATA||(t.children=condenseWhitespace(g)),m===0&&currentOptions.isIgnoreNewlineTag(d)){let t=g[0];t&&t.type===2&&(t.content=t.content.replace(/^\r?\n/,``))}m===0&&currentOptions.isPreTag(d)&&inPre--,currentVPreBoundary===t&&(inVPre=tokenizer.inVPre=!1,currentVPreBoundary=null),tokenizer.inXML&&(stack[0]?stack[0].ns:currentOptions.ns)===0&&(tokenizer.inXML=!1);{let c=t.props;if(process.env.NODE_ENV!==`production`&&isCompatEnabled(`COMPILER_V_IF_V_FOR_PRECEDENCE`,currentOptions)){let u=!1,d=!1;for(let m=0;m<c.length;m++){let g=c[m];if(g.type===7&&(g.name===`if`?u=!0:g.name===`for`&&(d=!0)),u&&d){warnDeprecation(`COMPILER_V_IF_V_FOR_PRECEDENCE`,currentOptions,t.loc);break}}}if(!tokenizer.inSFCRoot&&isCompatEnabled(`COMPILER_NATIVE_TEMPLATE`,currentOptions)&&t.tag===`template`&&!isFragmentTemplate(t)){process.env.NODE_ENV!==`production`&&warnDeprecation(`COMPILER_NATIVE_TEMPLATE`,currentOptions,t.loc);let c=stack[0]||currentRoot,u=c.children.indexOf(t);c.children.splice(u,1,...t.children)}let u=c.find(t=>t.type===6&&t.name===`inline-template`);u&&checkCompatEnabled(`COMPILER_INLINE_TEMPLATE`,currentOptions,u.loc)&&t.children.length&&(u.value={type:2,content:getSlice(t.children[0].loc.start.offset,t.children[t.children.length-1].loc.end.offset),loc:u.loc})}}function lookAhead(t,c){let u=t;for(;currentInput.charCodeAt(u)!==c&&u<currentInput.length-1;)u++;return u}function backTrack(t,c){let u=t;for(;currentInput.charCodeAt(u)!==c&&u>=0;)u--;return u}function isFragmentTemplate({tag:t,props:c}){if(t===`template`){for(let t=0;t<c.length;t++)if(c[t].type===7&&specialTemplateDir.has(c[t].name))return!0}return!1}function isComponent({tag:t,props:c}){if(currentOptions.isCustomElement(t))return!1;if(t===`component`||isUpperCase(t.charCodeAt(0))||isCoreComponent(t)||currentOptions.isBuiltInComponent&&currentOptions.isBuiltInComponent(t)||currentOptions.isNativeTag&&!currentOptions.isNativeTag(t))return!0;for(let t=0;t<c.length;t++){let u=c[t];if(u.type===6){if(u.name===`is`&&u.value&&(u.value.content.startsWith(`vue:`)||checkCompatEnabled(`COMPILER_IS_ON_ELEMENT`,currentOptions,u.loc)))return!0}else if(u.name===`bind`&&isStaticArgOf(u.arg,`is`)&&checkCompatEnabled(`COMPILER_IS_ON_ELEMENT`,currentOptions,u.loc))return!0}return!1}function isUpperCase(t){return t>64&&t<91}function condenseWhitespace(t,c){let u=currentOptions.whitespace!==`preserve`,d=!1;for(let c=0;c<t.length;c++){let m=t[c];if(m.type===2)if(inPre)m.content=m.content.replace(windowsNewlineRE,`
460
460
  `);else if(isAllWhitespace(m.content)){let g=t[c-1]&&t[c-1].type,b=t[c+1]&&t[c+1].type;!g||!b||u&&(g===3&&(b===3||b===1)||g===1&&(b===3||b===1&&hasNewlineChar(m.content)))?(d=!0,t[c]=null):m.content=` `}else u&&(m.content=condense(m.content))}return d?t.filter(Boolean):t}function isAllWhitespace(t){for(let c=0;c<t.length;c++)if(!isWhitespace(t.charCodeAt(c)))return!1;return!0}function hasNewlineChar(t){for(let c=0;c<t.length;c++){let u=t.charCodeAt(c);if(u===10||u===13)return!0}return!1}function condense(t){let c=``,u=!1;for(let d=0;d<t.length;d++)isWhitespace(t.charCodeAt(d))?u||(c+=` `,u=!0):(c+=t[d],u=!1);return c}function addNode(t){(stack[0]||currentRoot).children.push(t)}function getLoc(t,c){return{start:tokenizer.getPos(t),end:c==null?c:tokenizer.getPos(c),source:c==null?c:getSlice(t,c)}}function cloneLoc(t){return getLoc(t.start.offset,t.end.offset)}function setLocEnd(t,c){t.end=tokenizer.getPos(c),t.source=getSlice(t.start.offset,c)}function dirToAttr(t){let c={type:6,name:t.rawName,nameLoc:getLoc(t.loc.start.offset,t.loc.start.offset+t.rawName.length),value:void 0,loc:t.loc};if(t.exp){let u=t.exp.loc;u.end.offset<t.loc.end.offset&&(u.start.offset--,u.start.column--,u.end.offset++,u.end.column++),c.value={type:2,content:t.exp.content,loc:u}}return c}function createExp(t,c=!1,u,d=0,m=0){return createSimpleExpression(t,c,u,d)}function emitError(t,c,u){currentOptions.onError(createCompilerError(t,getLoc(c,c),void 0,u))}function reset(){tokenizer.reset(),currentOpenTag=null,currentProp=null,currentAttrValue=``,currentAttrStartIndex=-1,currentAttrEndIndex=-1,stack.length=0}function baseParse(t,c){if(reset(),currentInput=t,currentOptions=extend$2({},defaultParserOptions),c){let t;for(t in c)c[t]!=null&&(currentOptions[t]=c[t])}if(process.env.NODE_ENV!==`production`&&!currentOptions.decodeEntities)throw Error(`[@vue/compiler-core] decodeEntities option is required in browser builds.`);tokenizer.mode=currentOptions.parseMode===`html`?1:currentOptions.parseMode===`sfc`?2:0,tokenizer.inXML=currentOptions.ns===1||currentOptions.ns===2;let u=c&&c.delimiters;u&&(tokenizer.delimiterOpen=toCharCodes(u[0]),tokenizer.delimiterClose=toCharCodes(u[1]));let d=currentRoot=createRoot([],t);return tokenizer.parse(currentInput),d.loc=getLoc(0,t.length),d.children=condenseWhitespace(d.children),currentRoot=null,d}function cacheStatic(t,c){walk(t,void 0,c,isSingleElementRoot(t,t.children[0]))}function isSingleElementRoot(t,c){let{children:u}=t;return u.length===1&&c.type===1&&!isSlotOutlet(c)}function walk(t,c,u,d=!1,m=!1){let{children:g}=t,b=[];for(let c=0;c<g.length;c++){let S=g[c];if(S.type===1&&S.tagType===0){let t=d?0:getConstantType(S,u);if(t>0){if(t>=2){S.codegenNode.patchFlag=-1,b.push(S);continue}}else{let t=S.codegenNode;if(t.type===13){let c=t.patchFlag;if((c===void 0||c===512||c===1)&&getGeneratedPropsConstantType(S,u)>=2){let c=getNodeProps(S);c&&(t.props=u.hoist(c))}t.dynamicProps&&(t.dynamicProps=u.hoist(t.dynamicProps))}}}else if(S.type===12&&(d?0:getConstantType(S,u))>=2){b.push(S);continue}if(S.type===1){let c=S.tagType===1;c&&u.scopes.vSlot++,walk(S,t,u,!1,m),c&&u.scopes.vSlot--}else if(S.type===11)walk(S,t,u,S.children.length===1,!0);else if(S.type===9)for(let c=0;c<S.branches.length;c++)walk(S.branches[c],t,u,S.branches[c].children.length===1,m)}let S=!1;if(b.length===g.length&&t.type===1){if(t.tagType===0&&t.codegenNode&&t.codegenNode.type===13&&isArray$3(t.codegenNode.children))t.codegenNode.children=getCacheExpression(createArrayExpression(t.codegenNode.children)),S=!0;else if(t.tagType===1&&t.codegenNode&&t.codegenNode.type===13&&t.codegenNode.children&&!isArray$3(t.codegenNode.children)&&t.codegenNode.children.type===15){let c=getSlotNode(t.codegenNode,`default`);c&&(c.returns=getCacheExpression(createArrayExpression(c.returns)),S=!0)}else if(t.tagType===3&&c&&c.type===1&&c.tagType===1&&c.codegenNode&&c.codegenNode.type===13&&c.codegenNode.children&&!isArray$3(c.codegenNode.children)&&c.codegenNode.children.type===15){let u=findDir(t,`slot`,!0),d=u&&u.arg&&getSlotNode(c.codegenNode,u.arg);d&&(d.returns=getCacheExpression(createArrayExpression(d.returns)),S=!0)}}if(!S)for(let t of b)t.codegenNode=u.cache(t.codegenNode);function getCacheExpression(t){let c=u.cache(t);return m&&u.hmr&&(c.needArraySpread=!0),c}function getSlotNode(t,c){if(t.children&&!isArray$3(t.children)&&t.children.type===15){let u=t.children.properties.find(t=>t.key===c||t.key.content===c);return u&&u.value}}b.length&&u.transformHoist&&u.transformHoist(g,u,t)}function getConstantType(t,c){let{constantCache:u}=c;switch(t.type){case 1:if(t.tagType!==0)return 0;let d=u.get(t);if(d!==void 0)return d;let m=t.codegenNode;if(m.type!==13||m.isBlock&&t.tag!==`svg`&&t.tag!==`foreignObject`&&t.tag!==`math`)return 0;if(m.patchFlag===void 0){let d=3,g=getGeneratedPropsConstantType(t,c);if(g===0)return u.set(t,0),0;g<d&&(d=g);for(let m=0;m<t.children.length;m++){let g=getConstantType(t.children[m],c);if(g===0)return u.set(t,0),0;g<d&&(d=g)}if(d>1)for(let m=0;m<t.props.length;m++){let g=t.props[m];if(g.type===7&&g.name===`bind`&&g.exp){let m=getConstantType(g.exp,c);if(m===0)return u.set(t,0),0;m<d&&(d=m)}}if(m.isBlock){for(let c=0;c<t.props.length;c++)if(t.props[c].type===7)return u.set(t,0),0;c.removeHelper(OPEN_BLOCK),c.removeHelper(getVNodeBlockHelper(c.inSSR,m.isComponent)),m.isBlock=!1,c.helper(getVNodeHelper(c.inSSR,m.isComponent))}return u.set(t,d),d}else return u.set(t,0),0;case 2:case 3:return 3;case 9:case 11:case 10:return 0;case 5:case 12:return getConstantType(t.content,c);case 4:return t.constType;case 8:let g=3;for(let u=0;u<t.children.length;u++){let d=t.children[u];if(isString$1(d)||isSymbol(d))continue;let m=getConstantType(d,c);if(m===0)return 0;m<g&&(g=m)}return g;case 20:return 2;default:return process.env.NODE_ENV,0}}function getConstantTypeOfHelperCall(t,c){if(t.type===14&&!isString$1(t.callee)&&allowHoistedHelperSet.has(t.callee)){let u=t.arguments[0];if(u.type===4)return getConstantType(u,c);if(u.type===14)return getConstantTypeOfHelperCall(u,c)}return 0}function getGeneratedPropsConstantType(t,c){let u=3,d=getNodeProps(t);if(d&&d.type===15){let{properties:t}=d;for(let d=0;d<t.length;d++){let{key:m,value:g}=t[d],b=getConstantType(m,c);if(b===0)return b;b<u&&(u=b);let S;if(S=g.type===4?getConstantType(g,c):g.type===14?getConstantTypeOfHelperCall(g,c):0,S===0)return S;S<u&&(u=S)}}return u}function getNodeProps(t){let c=t.codegenNode;if(c.type===13)return c.props}function createTransformContext(t,{filename:c=``,prefixIdentifiers:u=!1,hoistStatic:d=!1,hmr:m=!1,cacheHandlers:g=!1,nodeTransforms:b=[],directiveTransforms:S={},transformHoist:C=null,isBuiltInComponent:w=NOOP,isCustomElement:T=NOOP,expressionPlugins:E=[],scopeId:D=null,slotted:O=!0,ssr:Or=!1,inSSR:kr=!1,ssrCssVars:Ar=``,bindingMetadata:jr=EMPTY_OBJ,inline:Mr=!1,isTS:Nr=!1,onError:Pr=defaultOnError,onWarn:Fr=defaultOnWarn,compatConfig:Ir}){let Lr=c.replace(/\?.*$/,``).match(/([^/\\]+)\.\w+$/),Rr={filename:c,selfName:Lr&&capitalize(camelize(Lr[1])),prefixIdentifiers:u,hoistStatic:d,hmr:m,cacheHandlers:g,nodeTransforms:b,directiveTransforms:S,transformHoist:C,isBuiltInComponent:w,isCustomElement:T,expressionPlugins:E,scopeId:D,slotted:O,ssr:Or,inSSR:kr,ssrCssVars:Ar,bindingMetadata:jr,inline:Mr,isTS:Nr,onError:Pr,onWarn:Fr,compatConfig:Ir,root:t,helpers:new Map,components:new Set,directives:new Set,hoists:[],imports:[],cached:[],constantCache:new WeakMap,temps:0,identifiers:Object.create(null),scopes:{vFor:0,vSlot:0,vPre:0,vOnce:0},parent:null,grandParent:null,currentNode:t,childIndex:0,inVOnce:!1,helper(t){let c=Rr.helpers.get(t)||0;return Rr.helpers.set(t,c+1),t},removeHelper(t){let c=Rr.helpers.get(t);if(c){let u=c-1;u?Rr.helpers.set(t,u):Rr.helpers.delete(t)}},helperString(t){return`_${helperNameMap[Rr.helper(t)]}`},replaceNode(t){if(process.env.NODE_ENV!==`production`){if(!Rr.currentNode)throw Error(`Node being replaced is already removed.`);if(!Rr.parent)throw Error(`Cannot replace root node.`)}Rr.parent.children[Rr.childIndex]=Rr.currentNode=t},removeNode(t){if(process.env.NODE_ENV!==`production`&&!Rr.parent)throw Error(`Cannot remove root node.`);let c=Rr.parent.children,u=t?c.indexOf(t):Rr.currentNode?Rr.childIndex:-1;if(process.env.NODE_ENV!==`production`&&u<0)throw Error(`node being removed is not a child of current parent`);!t||t===Rr.currentNode?(Rr.currentNode=null,Rr.onNodeRemoved()):Rr.childIndex>u&&(Rr.childIndex--,Rr.onNodeRemoved()),Rr.parent.children.splice(u,1)},onNodeRemoved:NOOP,addIdentifiers(t){},removeIdentifiers(t){},hoist(t){isString$1(t)&&(t=createSimpleExpression(t)),Rr.hoists.push(t);let c=createSimpleExpression(`_hoisted_${Rr.hoists.length}`,!1,t.loc,2);return c.hoisted=t,c},cache(t,c=!1,u=!1){let d=createCacheExpression(Rr.cached.length,t,c,u);return Rr.cached.push(d),d}};return Rr.filters=new Set,Rr}function transform(t,c){let u=createTransformContext(t,c);traverseNode(t,u),c.hoistStatic&&cacheStatic(t,u),c.ssr||createRootCodegen(t,u),t.helpers=new Set([...u.helpers.keys()]),t.components=[...u.components],t.directives=[...u.directives],t.imports=u.imports,t.hoists=u.hoists,t.temps=u.temps,t.cached=u.cached,t.transformed=!0,t.filters=[...u.filters]}function createRootCodegen(t,c){let{helper:u}=c,{children:d}=t;if(d.length===1){let u=d[0];if(isSingleElementRoot(t,u)&&u.codegenNode){let d=u.codegenNode;d.type===13&&convertToBlock(d,c),t.codegenNode=d}else t.codegenNode=u}else if(d.length>1){let m=64;process.env.NODE_ENV!==`production`&&d.filter(t=>t.type!==3).length===1&&(m|=2048),t.codegenNode=createVNodeCall(c,u(FRAGMENT),void 0,t.children,m,void 0,void 0,!0,void 0,!1)}}function traverseChildren(t,c){let u=0,nodeRemoved=()=>{u--};for(;u<t.children.length;u++){let d=t.children[u];isString$1(d)||(c.grandParent=c.parent,c.parent=t,c.childIndex=u,c.onNodeRemoved=nodeRemoved,traverseNode(d,c))}}function traverseNode(t,c){c.currentNode=t;let{nodeTransforms:u}=c,d=[];for(let m=0;m<u.length;m++){let g=u[m](t,c);if(g&&(isArray$3(g)?d.push(...g):d.push(g)),c.currentNode)t=c.currentNode;else return}switch(t.type){case 3:c.ssr||c.helper(CREATE_COMMENT);break;case 5:c.ssr||c.helper(TO_DISPLAY_STRING);break;case 9:for(let u=0;u<t.branches.length;u++)traverseNode(t.branches[u],c);break;case 10:case 11:case 1:case 0:traverseChildren(t,c);break}c.currentNode=t;let m=d.length;for(;m--;)d[m]()}function createStructuralDirectiveTransform(t,c){let u=isString$1(t)?c=>c===t:c=>t.test(c);return(t,d)=>{if(t.type===1){let{props:m}=t;if(t.tagType===3&&m.some(isVSlot))return;let g=[];for(let b=0;b<m.length;b++){let S=m[b];if(S.type===7&&u(S.name)){m.splice(b,1),b--;let u=c(t,S,d);u&&g.push(u)}}return g}}}function createCodegenContext(t,{mode:c=`function`,prefixIdentifiers:u=c===`module`,sourceMap:d=!1,filename:m=`template.vue.html`,scopeId:g=null,optimizeImports:b=!1,runtimeGlobalName:S=`Vue`,runtimeModuleName:C=`vue`,ssrRuntimeModuleName:w=`vue/server-renderer`,ssr:T=!1,isTS:E=!1,inSSR:D=!1}){let O={mode:c,prefixIdentifiers:u,sourceMap:d,filename:m,scopeId:g,optimizeImports:b,runtimeGlobalName:S,runtimeModuleName:C,ssrRuntimeModuleName:w,ssr:T,isTS:E,inSSR:D,source:t.source,code:``,column:1,line:1,offset:0,indentLevel:0,pure:!1,map:void 0,helper(t){return`_${helperNameMap[t]}`},push(t,c=-2,u){O.code+=t},indent(){newline(++O.indentLevel)},deindent(t=!1){t?--O.indentLevel:newline(--O.indentLevel)},newline(){newline(O.indentLevel)}};function newline(t){O.push(`
461
461
  `+` `.repeat(t),0)}return O}function generate(t,c={}){let u=createCodegenContext(t,c);c.onContextCreated&&c.onContextCreated(u);let{mode:d,push:m,prefixIdentifiers:g,indent:b,deindent:S,newline:C,scopeId:w,ssr:T}=u,E=Array.from(t.helpers),D=E.length>0,O=!g&&d!==`module`;if(genFunctionPreamble(t,u),m(`function ${T?`ssrRender`:`render`}(${(T?[`_ctx`,`_push`,`_parent`,`_attrs`]:[`_ctx`,`_cache`]).join(`, `)}) {`),b(),O&&(m(`with (_ctx) {`),b(),D&&(m(`const { ${E.map(aliasHelper).join(`, `)} } = _Vue
@@ -475,11 +475,11 @@ Displayable Constructor was called with no view Data.
475
475
  Card was displayed at ${t.timeStamp}
476
476
  User spent ${t.timeSpent} milliseconds with the card.
477
477
  `),this.$emit(`emitResponse`,t)}}}),__name(_sfc_render$17,`_sfc_render`),Q$1=F$3(ct$1,[[`render`,_sfc_render$17],[`__scopeId`,`data-v-2d892b82`]]),lt$1=defineComponent({name:`SessionControllerDebug`,props:{sessionController:{type:Object,required:!0}},setup(t){let c=ref(0),u=null;return onMounted(()=>{u=setInterval(()=>{c.value++},500)}),onUnmounted(()=>{u&&clearInterval(u)}),{debugInfo:computed(()=>(c.value,t.sessionController?t.sessionController.getDebugInfo():{reviewQueue:{length:0,dequeueCount:0,items:[]},newQueue:{length:0,dequeueCount:0,items:[]},failedQueue:{length:0,dequeueCount:0,items:[]},hydratedCache:{count:0,failedCacheSize:0,items:[]}}))}}}),ut$1={class:`debug-section`},dt$1={class:`debug-header`},ft$1={class:`debug-stats`},pt$1={class:`text-caption`},mt$1={class:`text-caption ml-2`},ht$1={key:0,class:`debug-items`},gt$1={class:`text-caption`},_t={class:`text-caption text-grey ml-1`},vt$1={key:0,class:`text-caption text-grey`},yt={key:1,class:`text-caption text-grey`},bt={class:`debug-section`},xt={class:`debug-header`},St={class:`debug-stats`},Ct={class:`text-caption`},wt={class:`text-caption ml-2`},Tt={key:0,class:`debug-items`},Et={class:`text-caption`},Dt={class:`text-caption text-grey ml-1`},Ot={key:0,class:`text-caption text-grey`},kt={key:1,class:`text-caption text-grey`},At={class:`debug-section`},jt={class:`debug-header`},Mt={class:`debug-stats`},Nt={class:`text-caption`},Pt={class:`text-caption ml-2`},Ft={key:0,class:`debug-items`},It={class:`text-caption`},Lt={class:`text-caption text-grey ml-1`},Rt={key:0,class:`text-caption text-grey`},zt={key:1,class:`text-caption text-grey`},Bt={class:`debug-section`},Vt={class:`debug-header`},Ht={class:`debug-stats`},Ut={class:`text-caption`},Wt={class:`text-caption ml-2`},Gt={key:0,class:`debug-items`},Kt={class:`text-caption`},qt={key:0,class:`text-caption text-grey`},Jt={key:1,class:`text-caption text-grey`},__name(_sfc_render$16,`_sfc_render`),Yt=F$3(lt$1,[[`render`,_sfc_render$16],[`__scopeId`,`data-v-1a6ac7a1`]]),Xt={},(function main(t,c,u,d){var m=!!(t.Worker&&t.Blob&&t.Promise&&t.OffscreenCanvas&&t.OffscreenCanvasRenderingContext2D&&t.HTMLCanvasElement&&t.HTMLCanvasElement.prototype.transferControlToOffscreen&&t.URL&&t.URL.createObjectURL),g=typeof Path2D==`function`&&typeof DOMMatrix==`function`,b=(function(){if(!t.OffscreenCanvas)return!1;var c=new OffscreenCanvas(1,1),u=c.getContext(`2d`);u.fillRect(0,0,1,1);var d=c.transferToImageBitmap();try{u.createPattern(d,`no-repeat`)}catch{return!1}return!0})();function noop(){}function promise(u){var d=c.exports.Promise,m=d===void 0?t.Promise:d;return typeof m==`function`?new m(u):(u(noop,noop),null)}var S=(function(t,c){return{transform:function(u){if(t)return u;if(c.has(u))return c.get(u);var d=new OffscreenCanvas(u.width,u.height);return d.getContext(`2d`).drawImage(u,0,0),c.set(u,d),d},clear:function(){c.clear()}}})(b,new Map),C=function(){var t=16,frame,cancel,c={},u=0;return typeof requestAnimationFrame==`function`&&typeof cancelAnimationFrame==`function`?(frame=function(d){var m=Math.random();return c[m]=requestAnimationFrame(function onFrame(g){u===g||u+t-1<g?(u=g,delete c[m],d()):c[m]=requestAnimationFrame(onFrame)}),m},cancel=function(t){c[t]&&cancelAnimationFrame(c[t])}):(frame=function(c){return setTimeout(c,t)},cancel=function(t){return clearTimeout(t)}),{frame,cancel}}(),w=(function(){var t,c,d={};function decorate(t){function execute(c,u){t.postMessage({options:c||{},callback:u})}t.init=function initWorker(c){var u=c.transferControlToOffscreen();t.postMessage({canvas:u},[u])},t.fire=function fireWorker(u,m,g){if(c)return execute(u,null),c;var b=Math.random().toString(36).slice(2);return c=promise(function(m){function workerDone(u){u.data.callback===b&&(delete d[b],t.removeEventListener(`message`,workerDone),c=null,S.clear(),g(),m())}t.addEventListener(`message`,workerDone),execute(u,b),d[b]=workerDone.bind(null,{data:{callback:b}})}),c},t.reset=function resetWorker(){for(var c in t.postMessage({reset:!0}),d)d[c](),delete d[c]}}return function(){if(t)return t;if(!u&&m){var c=[`var CONFETTI, SIZE = {}, module = {};`,`(`+main.toString()+`)(this, module, true, SIZE);`,`onmessage = function(msg) {`,` if (msg.data.options) {`,` CONFETTI(msg.data.options).then(function () {`,` if (msg.data.callback) {`,` postMessage({ callback: msg.data.callback });`,` }`,` });`,` } else if (msg.data.reset) {`,` CONFETTI && CONFETTI.reset();`,` } else if (msg.data.resize) {`,` SIZE.width = msg.data.resize.width;`,` SIZE.height = msg.data.resize.height;`,` } else if (msg.data.canvas) {`,` SIZE.width = msg.data.canvas.width;`,` SIZE.height = msg.data.canvas.height;`,` CONFETTI = module.exports.create(msg.data.canvas);`,` }`,`}`].join(`
478
- `);try{t=new Worker(URL.createObjectURL(new Blob([c])))}catch(t){return typeof console.warn==`function`&&console.warn(`🎊 Could not load worker`,t),null}decorate(t)}return t}})(),T={particleCount:50,angle:90,spread:45,startVelocity:45,decay:.9,gravity:1,drift:0,ticks:200,x:.5,y:.5,shapes:[`square`,`circle`],zIndex:100,colors:[`#26ccff`,`#a25afd`,`#ff5e7e`,`#88ff5a`,`#fcff42`,`#ffa62d`,`#ff36ff`],disableForReducedMotion:!1,scalar:1};function convert(t,c){return c?c(t):t}function isOk(t){return t!=null}function prop(t,c,u){return convert(t&&isOk(t[c])?t[c]:T[c],u)}function onlyPositiveInt(t){return t<0?0:Math.floor(t)}function randomInt(t,c){return Math.floor(Math.random()*(c-t))+t}function toDecimal(t){return parseInt(t,16)}function colorsToRgb(t){return t.map(hexToRgb)}function hexToRgb(t){var c=String(t).replace(/[^0-9a-f]/gi,``);return c.length<6&&(c=c[0]+c[0]+c[1]+c[1]+c[2]+c[2]),{r:toDecimal(c.substring(0,2)),g:toDecimal(c.substring(2,4)),b:toDecimal(c.substring(4,6))}}function getOrigin(t){var c=prop(t,`origin`,Object);return c.x=prop(c,`x`,Number),c.y=prop(c,`y`,Number),c}function setCanvasWindowSize(t){t.width=document.documentElement.clientWidth,t.height=document.documentElement.clientHeight}function setCanvasRectSize(t){var c=t.getBoundingClientRect();t.width=c.width,t.height=c.height}function getCanvas(t){var c=document.createElement(`canvas`);return c.style.position=`fixed`,c.style.top=`0px`,c.style.left=`0px`,c.style.pointerEvents=`none`,c.style.zIndex=t,c}function ellipse(t,c,u,d,m,g,b,S,C){t.save(),t.translate(c,u),t.rotate(g),t.scale(d,m),t.arc(0,0,1,b,S,C),t.restore()}function randomPhysics(t){var c=t.angle*(Math.PI/180),u=t.spread*(Math.PI/180);return{x:t.x,y:t.y,wobble:Math.random()*10,wobbleSpeed:Math.min(.11,Math.random()*.1+.05),velocity:t.startVelocity*.5+Math.random()*t.startVelocity,angle2D:-c+(.5*u-Math.random()*u),tiltAngle:(Math.random()*.5+.25)*Math.PI,color:t.color,shape:t.shape,tick:0,totalTicks:t.ticks,decay:t.decay,drift:t.drift,random:Math.random()+2,tiltSin:0,tiltCos:0,wobbleX:0,wobbleY:0,gravity:t.gravity*3,ovalScalar:.6,scalar:t.scalar,flat:t.flat}}function updateFetti(t,c){c.x+=Math.cos(c.angle2D)*c.velocity+c.drift,c.y+=Math.sin(c.angle2D)*c.velocity+c.gravity,c.velocity*=c.decay,c.flat?(c.wobble=0,c.wobbleX=c.x+10*c.scalar,c.wobbleY=c.y+10*c.scalar,c.tiltSin=0,c.tiltCos=0,c.random=1):(c.wobble+=c.wobbleSpeed,c.wobbleX=c.x+10*c.scalar*Math.cos(c.wobble),c.wobbleY=c.y+10*c.scalar*Math.sin(c.wobble),c.tiltAngle+=.1,c.tiltSin=Math.sin(c.tiltAngle),c.tiltCos=Math.cos(c.tiltAngle),c.random=Math.random()+2);var u=c.tick++/c.totalTicks,d=c.x+c.random*c.tiltCos,m=c.y+c.random*c.tiltSin,b=c.wobbleX+c.random*c.tiltCos,C=c.wobbleY+c.random*c.tiltSin;if(t.fillStyle=`rgba(`+c.color.r+`, `+c.color.g+`, `+c.color.b+`, `+(1-u)+`)`,t.beginPath(),g&&c.shape.type===`path`&&typeof c.shape.path==`string`&&Array.isArray(c.shape.matrix))t.fill(transformPath2D(c.shape.path,c.shape.matrix,c.x,c.y,Math.abs(b-d)*.1,Math.abs(C-m)*.1,Math.PI/10*c.wobble));else if(c.shape.type===`bitmap`){var w=Math.PI/10*c.wobble,T=Math.abs(b-d)*.1,E=Math.abs(C-m)*.1,D=c.shape.bitmap.width*c.scalar,O=c.shape.bitmap.height*c.scalar,Or=new DOMMatrix([Math.cos(w)*T,Math.sin(w)*T,-Math.sin(w)*E,Math.cos(w)*E,c.x,c.y]);Or.multiplySelf(new DOMMatrix(c.shape.matrix));var kr=t.createPattern(S.transform(c.shape.bitmap),`no-repeat`);kr.setTransform(Or),t.globalAlpha=1-u,t.fillStyle=kr,t.fillRect(c.x-D/2,c.y-O/2,D,O),t.globalAlpha=1}else if(c.shape===`circle`)t.ellipse?t.ellipse(c.x,c.y,Math.abs(b-d)*c.ovalScalar,Math.abs(C-m)*c.ovalScalar,Math.PI/10*c.wobble,0,2*Math.PI):ellipse(t,c.x,c.y,Math.abs(b-d)*c.ovalScalar,Math.abs(C-m)*c.ovalScalar,Math.PI/10*c.wobble,0,2*Math.PI);else if(c.shape===`star`)for(var Ar=Math.PI/2*3,jr=4*c.scalar,Mr=8*c.scalar,Nr=c.x,Pr=c.y,Fr=5,Ir=Math.PI/Fr;Fr--;)Nr=c.x+Math.cos(Ar)*Mr,Pr=c.y+Math.sin(Ar)*Mr,t.lineTo(Nr,Pr),Ar+=Ir,Nr=c.x+Math.cos(Ar)*jr,Pr=c.y+Math.sin(Ar)*jr,t.lineTo(Nr,Pr),Ar+=Ir;else t.moveTo(Math.floor(c.x),Math.floor(c.y)),t.lineTo(Math.floor(c.wobbleX),Math.floor(m)),t.lineTo(Math.floor(b),Math.floor(C)),t.lineTo(Math.floor(d),Math.floor(c.wobbleY));return t.closePath(),t.fill(),c.tick<c.totalTicks}function animate(t,c,m,g,b){var w=c.slice(),T=t.getContext(`2d`),E,D,O=promise(function(c){function onDone(){E=D=null,T.clearRect(0,0,g.width,g.height),S.clear(),b(),c()}function update(){u&&!(g.width===d.width&&g.height===d.height)&&(g.width=t.width=d.width,g.height=t.height=d.height),!g.width&&!g.height&&(m(t),g.width=t.width,g.height=t.height),T.clearRect(0,0,g.width,g.height),w=w.filter(function(t){return updateFetti(T,t)}),w.length?E=C.frame(update):onDone()}E=C.frame(update),D=onDone});return{addFettis:function(t){return w=w.concat(t),O},canvas:t,promise:O,reset:function(){E&&C.cancel(E),D&&D()}}}function confettiCannon(c,u){var d=!c,g=!!prop(u||{},`resize`),b=!1,S=prop(u,`disableForReducedMotion`,Boolean),C=m&&prop(u||{},`useWorker`)?w():null,T=d?setCanvasWindowSize:setCanvasRectSize,E=c&&C?!!c.__confetti_initialized:!1,D=typeof matchMedia==`function`&&matchMedia(`(prefers-reduced-motion)`).matches,O;function fireLocal(t,u,d){for(var m=prop(t,`particleCount`,onlyPositiveInt),g=prop(t,`angle`,Number),b=prop(t,`spread`,Number),S=prop(t,`startVelocity`,Number),C=prop(t,`decay`,Number),w=prop(t,`gravity`,Number),E=prop(t,`drift`,Number),D=prop(t,`colors`,colorsToRgb),Or=prop(t,`ticks`,Number),kr=prop(t,`shapes`),Ar=prop(t,`scalar`),jr=!!prop(t,`flat`),Mr=getOrigin(t),Nr=m,Pr=[],Fr=c.width*Mr.x,Ir=c.height*Mr.y;Nr--;)Pr.push(randomPhysics({x:Fr,y:Ir,angle:g,spread:b,startVelocity:S,color:D[Nr%D.length],shape:kr[randomInt(0,kr.length)],ticks:Or,decay:C,gravity:w,drift:E,scalar:Ar,flat:jr}));return O?O.addFettis(Pr):(O=animate(c,Pr,T,u,d),O.promise)}function fire(u){var m=S||prop(u,`disableForReducedMotion`,Boolean),w=prop(u,`zIndex`,Number);if(m&&D)return promise(function(t){t()});d&&O?c=O.canvas:d&&!c&&(c=getCanvas(w),document.body.appendChild(c)),g&&!E&&T(c);var Or={width:c.width,height:c.height};C&&!E&&C.init(c),E=!0,C&&(c.__confetti_initialized=!0);function onResize(){if(C){var t={getBoundingClientRect:function(){if(!d)return c.getBoundingClientRect()}};T(t),C.postMessage({resize:{width:t.width,height:t.height}});return}Or.width=Or.height=null}function done(){O=null,g&&(b=!1,t.removeEventListener(`resize`,onResize)),d&&c&&(document.body.contains(c)&&document.body.removeChild(c),c=null,E=!1)}return g&&!b&&(b=!0,t.addEventListener(`resize`,onResize,!1)),C?C.fire(u,Or,done):fireLocal(u,Or,done)}return fire.reset=function(){C&&C.reset(),O&&O.reset()},fire}var E;function getDefaultFire(){return E||(E=confettiCannon(null,{useWorker:!0,resize:!0})),E}function transformPath2D(t,c,u,d,m,g,b){var S=new Path2D(t),C=new Path2D;C.addPath(S,new DOMMatrix(c));var w=new Path2D;return w.addPath(C,new DOMMatrix([Math.cos(b)*m,Math.sin(b)*m,-Math.sin(b)*g,Math.cos(b)*g,u,d])),w}function shapeFromPath(t){if(!g)throw Error(`path confetti are not supported in this browser`);var c,u;typeof t==`string`?c=t:(c=t.path,u=t.matrix);var d=new Path2D(c),m=document.createElement(`canvas`).getContext(`2d`);if(!u){for(var b=1e3,S=b,C=b,w=0,T=0,E,D,O=0;O<b;O+=2)for(var Or=0;Or<b;Or+=2)m.isPointInPath(d,O,Or,`nonzero`)&&(S=Math.min(S,O),C=Math.min(C,Or),w=Math.max(w,O),T=Math.max(T,Or));E=w-S,D=T-C;var kr=10,Ar=Math.min(kr/E,kr/D);u=[Ar,0,0,Ar,-Math.round(E/2+S)*Ar,-Math.round(D/2+C)*Ar]}return{type:`path`,path:c,matrix:u}}function shapeFromText(t){var c,u=1,d=`#000000`,m=`"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", "EmojiOne Color", "Android Emoji", "Twemoji Mozilla", "system emoji", sans-serif`;typeof t==`string`?c=t:(c=t.text,u=`scalar`in t?t.scalar:u,m=`fontFamily`in t?t.fontFamily:m,d=`color`in t?t.color:d);var g=10*u,b=``+g+`px `+m,S=new OffscreenCanvas(g,g),C=S.getContext(`2d`);C.font=b;var w=C.measureText(c),T=Math.ceil(w.actualBoundingBoxRight+w.actualBoundingBoxLeft),E=Math.ceil(w.actualBoundingBoxAscent+w.actualBoundingBoxDescent),D=2,O=w.actualBoundingBoxLeft+D,Or=w.actualBoundingBoxAscent+D;T+=D+D,E+=D+D,S=new OffscreenCanvas(T,E),C=S.getContext(`2d`),C.font=b,C.fillStyle=d,C.fillText(c,O,Or);var kr=1/u;return{type:`bitmap`,bitmap:S.transferToImageBitmap(),matrix:[kr,0,0,kr,-T*kr/2,-E*kr/2]}}c.exports=function(){return getDefaultFire().apply(this,arguments)},c.exports.reset=function(){getDefaultFire().reset()},c.exports.create=confettiCannon,c.exports.shapeFromPath=shapeFromPath,c.exports.shapeFromText=shapeFromText})((function(){return typeof window<`u`?window:typeof self<`u`?self:this||{}})(),Xt,!1),Zt=Xt.exports,Xt.exports.create,Qt=defineComponent({name:`StudySession`,ref:{},components:{CardViewer:Q$1,StudySessionTimer:st$1,SkMouseTrap:Ne$1,HeatMap:Ae$1,SessionControllerDebug:Yt},props:{sessionTimeLimit:{type:Number,required:!0},contentSources:{type:Array,required:!0},user:{type:Object,required:!0},dataLayer:{type:Object,required:!0},sessionConfig:{type:Object,default:()=>({likesConfetti:!1})},getViewComponent:{type:Function,required:!0},frameless:{type:Boolean,default:!1},hideFooter:{type:Boolean,default:!1},transitionName:{type:String,default:`component-fade`},transitionMode:{type:String,default:`out-in`}},emits:[`session-finished`,`session-started`,`card-loaded`,`card-response`,`time-changed`,`session-prepared`,`session-error`,`replan-requested`],data(){return{cardID:``,view:null,data:[],courseID:``,card_elo:1e3,courseNames:{},cardCount:1,sessionController:null,sessionPrepared:!1,sessionFinished:!1,sessionRecord:[],percentageRemaining:100,timerIsActive:!0,loading:!1,userCourseRegDoc:null,sessionContentSources:[],timeRemaining:300,replanPending:!1,replanOptions:null,deferredNextCardAction:null,intervalHandler:null,cardType:``,debugMode:window.debugMode===!0}},computed:{currentCard(){return this.sessionRecord[this.sessionRecord.length-1]}},async created(){this.userCourseRegDoc=await this.user.getCourseRegistrationsDoc(),console.log(`[StudySession] Created lifecycle hook - starting initSession`),await this.initSession(),console.log(`[StudySession] InitSession completed in created hook`)},errorCaptured(t,c,u){return console.error(`[StudySession] Card render error (${u}), skipping card "${this.cardID}":`,t),this.sessionController&&this.sessionController.nextCard().then(t=>this.loadCard(t)),!1},methods:{async handleReadyToAdvance(){let t=this.deferredNextCardAction;if(!t){console.warn(`[StudySession] ready-to-advance received but no deferred action stashed — ignoring`);return}console.log(`[StudySession] ready-to-advance received — advancing with stashed action: ${t}`),this.deferredNextCardAction=null,this.loadCard(await this.sessionController.nextCard(t))},handleReplanRequest(t){if(this.sessionController){let c=t?.label,u=c?` [${c}]`:``;console.log(`[StudySession] Replan requested by card view${u}, deferring until after response processing`),this.replanPending=!0,this.replanOptions=t??null}},user_elo(t){let c=this.userCourseRegDoc.courses.find(c=>c.courseID===t);return toCourseElo(c?c.elo:void 0)},handleClassroomMessage(){return t=>(Z$2({text:this.user?.getUsername()||`[Unknown user]`,status:Status.ok}),console.log(`[StudySession] There was a change in the classroom DB:`),console.log(`[StudySession] change: ${t}`),console.log(`[StudySession] Stringified change: ${JSON.stringify(t)}`),{})},incrementSessionClock(){let t=60*this.sessionTimeLimit-this.timeRemaining;this.sessionController.addTime(Math.min(t,60)),this.tick()},tick(){this.timeRemaining=this.sessionController.secondsRemaining,this.percentageRemaining=this.timeRemaining>60?100*(this.timeRemaining/(60*this.sessionTimeLimit)):100*(this.timeRemaining/60),this.$emit(`time-changed`,this.timeRemaining),this.timeRemaining===0&&clearInterval(this.intervalHandler)},async initSession(){let t=[];try{console.log(`[StudySession] starting study session w/ sources: ${JSON.stringify(this.contentSources)}`),console.log(`[StudySession] Beginning preparation process`),this.sessionContentSources=markRaw((await Promise.all(this.contentSources.map(async t=>{try{return await getStudySource(t,this.user)}catch(c){return console.error(`Failed to load study source: ${t.type}/${t.id}`,c),null}}))).filter(t=>t!==null)),this.timeRemaining=this.sessionTimeLimit*60,t=await Promise.all(this.contentSources.filter(t=>t.type===`classroom`).map(async t=>await this.dataLayer.getClassroomDB(t.id,`student`))),t.forEach(t=>{});let c={};if(this.sessionConfig?.defaultBatchLimit!==void 0&&(c.defaultBatchLimit=this.sessionConfig.defaultBatchLimit),this.sessionConfig?.initialReviewCap!==void 0&&(c.initialReviewCap=this.sessionConfig.initialReviewCap),this.sessionController=markRaw(new SessionController(this.sessionContentSources,60*this.sessionTimeLimit,this.dataLayer,this.getViewComponent,void 0,c)),this.sessionController.sessionRecord=this.sessionRecord,this.sessionConfig?.initHints){for(let t of this.sessionContentSources)t.setEphemeralHints?.(this.sessionConfig.initHints);console.log(`[StudySession] Applied init hints to content sources`)}await this.sessionController.prepareSession(),this.intervalHandler=setInterval(this.tick,1e3),this.sessionPrepared=!0,console.log(`[StudySession] Session preparation complete, emitting session-prepared event`),this.$emit(`session-prepared`),console.log(`[StudySession] Event emission completed`)}catch(t){console.error(`[StudySession] Error during session preparation:`,t),this.$emit(`session-error`,{message:`Failed to prepare study session`,error:t})}try{this.contentSources.filter(t=>t.type===`course`).forEach(async t=>this.courseNames[t.id]=(await this.dataLayer.getCoursesDB().getCourseConfig(t.id)).name),console.log(`[StudySession] Session created:
478
+ `);try{t=new Worker(URL.createObjectURL(new Blob([c])))}catch(t){return typeof console.warn==`function`&&console.warn(`🎊 Could not load worker`,t),null}decorate(t)}return t}})(),T={particleCount:50,angle:90,spread:45,startVelocity:45,decay:.9,gravity:1,drift:0,ticks:200,x:.5,y:.5,shapes:[`square`,`circle`],zIndex:100,colors:[`#26ccff`,`#a25afd`,`#ff5e7e`,`#88ff5a`,`#fcff42`,`#ffa62d`,`#ff36ff`],disableForReducedMotion:!1,scalar:1};function convert(t,c){return c?c(t):t}function isOk(t){return t!=null}function prop(t,c,u){return convert(t&&isOk(t[c])?t[c]:T[c],u)}function onlyPositiveInt(t){return t<0?0:Math.floor(t)}function randomInt(t,c){return Math.floor(Math.random()*(c-t))+t}function toDecimal(t){return parseInt(t,16)}function colorsToRgb(t){return t.map(hexToRgb)}function hexToRgb(t){var c=String(t).replace(/[^0-9a-f]/gi,``);return c.length<6&&(c=c[0]+c[0]+c[1]+c[1]+c[2]+c[2]),{r:toDecimal(c.substring(0,2)),g:toDecimal(c.substring(2,4)),b:toDecimal(c.substring(4,6))}}function getOrigin(t){var c=prop(t,`origin`,Object);return c.x=prop(c,`x`,Number),c.y=prop(c,`y`,Number),c}function setCanvasWindowSize(t){t.width=document.documentElement.clientWidth,t.height=document.documentElement.clientHeight}function setCanvasRectSize(t){var c=t.getBoundingClientRect();t.width=c.width,t.height=c.height}function getCanvas(t){var c=document.createElement(`canvas`);return c.style.position=`fixed`,c.style.top=`0px`,c.style.left=`0px`,c.style.pointerEvents=`none`,c.style.zIndex=t,c}function ellipse(t,c,u,d,m,g,b,S,C){t.save(),t.translate(c,u),t.rotate(g),t.scale(d,m),t.arc(0,0,1,b,S,C),t.restore()}function randomPhysics(t){var c=t.angle*(Math.PI/180),u=t.spread*(Math.PI/180);return{x:t.x,y:t.y,wobble:Math.random()*10,wobbleSpeed:Math.min(.11,Math.random()*.1+.05),velocity:t.startVelocity*.5+Math.random()*t.startVelocity,angle2D:-c+(.5*u-Math.random()*u),tiltAngle:(Math.random()*.5+.25)*Math.PI,color:t.color,shape:t.shape,tick:0,totalTicks:t.ticks,decay:t.decay,drift:t.drift,random:Math.random()+2,tiltSin:0,tiltCos:0,wobbleX:0,wobbleY:0,gravity:t.gravity*3,ovalScalar:.6,scalar:t.scalar,flat:t.flat}}function updateFetti(t,c){c.x+=Math.cos(c.angle2D)*c.velocity+c.drift,c.y+=Math.sin(c.angle2D)*c.velocity+c.gravity,c.velocity*=c.decay,c.flat?(c.wobble=0,c.wobbleX=c.x+10*c.scalar,c.wobbleY=c.y+10*c.scalar,c.tiltSin=0,c.tiltCos=0,c.random=1):(c.wobble+=c.wobbleSpeed,c.wobbleX=c.x+10*c.scalar*Math.cos(c.wobble),c.wobbleY=c.y+10*c.scalar*Math.sin(c.wobble),c.tiltAngle+=.1,c.tiltSin=Math.sin(c.tiltAngle),c.tiltCos=Math.cos(c.tiltAngle),c.random=Math.random()+2);var u=c.tick++/c.totalTicks,d=c.x+c.random*c.tiltCos,m=c.y+c.random*c.tiltSin,b=c.wobbleX+c.random*c.tiltCos,C=c.wobbleY+c.random*c.tiltSin;if(t.fillStyle=`rgba(`+c.color.r+`, `+c.color.g+`, `+c.color.b+`, `+(1-u)+`)`,t.beginPath(),g&&c.shape.type===`path`&&typeof c.shape.path==`string`&&Array.isArray(c.shape.matrix))t.fill(transformPath2D(c.shape.path,c.shape.matrix,c.x,c.y,Math.abs(b-d)*.1,Math.abs(C-m)*.1,Math.PI/10*c.wobble));else if(c.shape.type===`bitmap`){var w=Math.PI/10*c.wobble,T=Math.abs(b-d)*.1,E=Math.abs(C-m)*.1,D=c.shape.bitmap.width*c.scalar,O=c.shape.bitmap.height*c.scalar,Or=new DOMMatrix([Math.cos(w)*T,Math.sin(w)*T,-Math.sin(w)*E,Math.cos(w)*E,c.x,c.y]);Or.multiplySelf(new DOMMatrix(c.shape.matrix));var kr=t.createPattern(S.transform(c.shape.bitmap),`no-repeat`);kr.setTransform(Or),t.globalAlpha=1-u,t.fillStyle=kr,t.fillRect(c.x-D/2,c.y-O/2,D,O),t.globalAlpha=1}else if(c.shape===`circle`)t.ellipse?t.ellipse(c.x,c.y,Math.abs(b-d)*c.ovalScalar,Math.abs(C-m)*c.ovalScalar,Math.PI/10*c.wobble,0,2*Math.PI):ellipse(t,c.x,c.y,Math.abs(b-d)*c.ovalScalar,Math.abs(C-m)*c.ovalScalar,Math.PI/10*c.wobble,0,2*Math.PI);else if(c.shape===`star`)for(var Ar=Math.PI/2*3,jr=4*c.scalar,Mr=8*c.scalar,Nr=c.x,Pr=c.y,Fr=5,Ir=Math.PI/Fr;Fr--;)Nr=c.x+Math.cos(Ar)*Mr,Pr=c.y+Math.sin(Ar)*Mr,t.lineTo(Nr,Pr),Ar+=Ir,Nr=c.x+Math.cos(Ar)*jr,Pr=c.y+Math.sin(Ar)*jr,t.lineTo(Nr,Pr),Ar+=Ir;else t.moveTo(Math.floor(c.x),Math.floor(c.y)),t.lineTo(Math.floor(c.wobbleX),Math.floor(m)),t.lineTo(Math.floor(b),Math.floor(C)),t.lineTo(Math.floor(d),Math.floor(c.wobbleY));return t.closePath(),t.fill(),c.tick<c.totalTicks}function animate(t,c,m,g,b){var w=c.slice(),T=t.getContext(`2d`),E,D,O=promise(function(c){function onDone(){E=D=null,T.clearRect(0,0,g.width,g.height),S.clear(),b(),c()}function update(){u&&!(g.width===d.width&&g.height===d.height)&&(g.width=t.width=d.width,g.height=t.height=d.height),!g.width&&!g.height&&(m(t),g.width=t.width,g.height=t.height),T.clearRect(0,0,g.width,g.height),w=w.filter(function(t){return updateFetti(T,t)}),w.length?E=C.frame(update):onDone()}E=C.frame(update),D=onDone});return{addFettis:function(t){return w=w.concat(t),O},canvas:t,promise:O,reset:function(){E&&C.cancel(E),D&&D()}}}function confettiCannon(c,u){var d=!c,g=!!prop(u||{},`resize`),b=!1,S=prop(u,`disableForReducedMotion`,Boolean),C=m&&prop(u||{},`useWorker`)?w():null,T=d?setCanvasWindowSize:setCanvasRectSize,E=c&&C?!!c.__confetti_initialized:!1,D=typeof matchMedia==`function`&&matchMedia(`(prefers-reduced-motion)`).matches,O;function fireLocal(t,u,d){for(var m=prop(t,`particleCount`,onlyPositiveInt),g=prop(t,`angle`,Number),b=prop(t,`spread`,Number),S=prop(t,`startVelocity`,Number),C=prop(t,`decay`,Number),w=prop(t,`gravity`,Number),E=prop(t,`drift`,Number),D=prop(t,`colors`,colorsToRgb),Or=prop(t,`ticks`,Number),kr=prop(t,`shapes`),Ar=prop(t,`scalar`),jr=!!prop(t,`flat`),Mr=getOrigin(t),Nr=m,Pr=[],Fr=c.width*Mr.x,Ir=c.height*Mr.y;Nr--;)Pr.push(randomPhysics({x:Fr,y:Ir,angle:g,spread:b,startVelocity:S,color:D[Nr%D.length],shape:kr[randomInt(0,kr.length)],ticks:Or,decay:C,gravity:w,drift:E,scalar:Ar,flat:jr}));return O?O.addFettis(Pr):(O=animate(c,Pr,T,u,d),O.promise)}function fire(u){var m=S||prop(u,`disableForReducedMotion`,Boolean),w=prop(u,`zIndex`,Number);if(m&&D)return promise(function(t){t()});d&&O?c=O.canvas:d&&!c&&(c=getCanvas(w),document.body.appendChild(c)),g&&!E&&T(c);var Or={width:c.width,height:c.height};C&&!E&&C.init(c),E=!0,C&&(c.__confetti_initialized=!0);function onResize(){if(C){var t={getBoundingClientRect:function(){if(!d)return c.getBoundingClientRect()}};T(t),C.postMessage({resize:{width:t.width,height:t.height}});return}Or.width=Or.height=null}function done(){O=null,g&&(b=!1,t.removeEventListener(`resize`,onResize)),d&&c&&(document.body.contains(c)&&document.body.removeChild(c),c=null,E=!1)}return g&&!b&&(b=!0,t.addEventListener(`resize`,onResize,!1)),C?C.fire(u,Or,done):fireLocal(u,Or,done)}return fire.reset=function(){C&&C.reset(),O&&O.reset()},fire}var E;function getDefaultFire(){return E||(E=confettiCannon(null,{useWorker:!0,resize:!0})),E}function transformPath2D(t,c,u,d,m,g,b){var S=new Path2D(t),C=new Path2D;C.addPath(S,new DOMMatrix(c));var w=new Path2D;return w.addPath(C,new DOMMatrix([Math.cos(b)*m,Math.sin(b)*m,-Math.sin(b)*g,Math.cos(b)*g,u,d])),w}function shapeFromPath(t){if(!g)throw Error(`path confetti are not supported in this browser`);var c,u;typeof t==`string`?c=t:(c=t.path,u=t.matrix);var d=new Path2D(c),m=document.createElement(`canvas`).getContext(`2d`);if(!u){for(var b=1e3,S=b,C=b,w=0,T=0,E,D,O=0;O<b;O+=2)for(var Or=0;Or<b;Or+=2)m.isPointInPath(d,O,Or,`nonzero`)&&(S=Math.min(S,O),C=Math.min(C,Or),w=Math.max(w,O),T=Math.max(T,Or));E=w-S,D=T-C;var kr=10,Ar=Math.min(kr/E,kr/D);u=[Ar,0,0,Ar,-Math.round(E/2+S)*Ar,-Math.round(D/2+C)*Ar]}return{type:`path`,path:c,matrix:u}}function shapeFromText(t){var c,u=1,d=`#000000`,m=`"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", "EmojiOne Color", "Android Emoji", "Twemoji Mozilla", "system emoji", sans-serif`;typeof t==`string`?c=t:(c=t.text,u=`scalar`in t?t.scalar:u,m=`fontFamily`in t?t.fontFamily:m,d=`color`in t?t.color:d);var g=10*u,b=``+g+`px `+m,S=new OffscreenCanvas(g,g),C=S.getContext(`2d`);C.font=b;var w=C.measureText(c),T=Math.ceil(w.actualBoundingBoxRight+w.actualBoundingBoxLeft),E=Math.ceil(w.actualBoundingBoxAscent+w.actualBoundingBoxDescent),D=2,O=w.actualBoundingBoxLeft+D,Or=w.actualBoundingBoxAscent+D;T+=D+D,E+=D+D,S=new OffscreenCanvas(T,E),C=S.getContext(`2d`),C.font=b,C.fillStyle=d,C.fillText(c,O,Or);var kr=1/u;return{type:`bitmap`,bitmap:S.transferToImageBitmap(),matrix:[kr,0,0,kr,-T*kr/2,-E*kr/2]}}c.exports=function(){return getDefaultFire().apply(this,arguments)},c.exports.reset=function(){getDefaultFire().reset()},c.exports.create=confettiCannon,c.exports.shapeFromPath=shapeFromPath,c.exports.shapeFromText=shapeFromText})((function(){return typeof window<`u`?window:typeof self<`u`?self:this||{}})(),Xt,!1),Zt=Xt.exports,Xt.exports.create,Qt=defineComponent({name:`StudySession`,ref:{},components:{CardViewer:Q$1,StudySessionTimer:st$1,SkMouseTrap:Ne$1,HeatMap:Ae$1,SessionControllerDebug:Yt},props:{sessionTimeLimit:{type:Number,required:!0},contentSources:{type:Array,required:!0},user:{type:Object,required:!0},dataLayer:{type:Object,required:!0},sessionConfig:{type:Object,default:()=>({likesConfetti:!1})},getViewComponent:{type:Function,required:!0},frameless:{type:Boolean,default:!1},hideFooter:{type:Boolean,default:!1},transitionName:{type:String,default:`component-fade`},transitionMode:{type:String,default:`out-in`}},emits:[`session-finished`,`session-started`,`card-loaded`,`card-response`,`time-changed`,`session-prepared`,`session-error`,`replan-requested`],data(){return{cardID:``,view:null,data:[],courseID:``,card_elo:1e3,courseNames:{},cardCount:1,sessionController:null,sessionPrepared:!1,sessionFinished:!1,sessionRecord:[],percentageRemaining:100,timerIsActive:!0,loading:!1,userCourseRegDoc:null,sessionContentSources:[],timeRemaining:300,replanPending:!1,replanOptions:null,deferredNextCardAction:null,intervalHandler:null,cardType:``,debugMode:window.debugMode===!0}},computed:{currentCard(){return this.sessionRecord[this.sessionRecord.length-1]}},async created(){this.userCourseRegDoc=await this.user.getCourseRegistrationsDoc(),console.log(`[StudySession] Created lifecycle hook - starting initSession`),await this.initSession(),console.log(`[StudySession] InitSession completed in created hook`)},errorCaptured(t,c,u){return console.error(`[StudySession] Card render error (${u}), skipping card "${this.cardID}":`,t),this.sessionController&&this.sessionController.nextCard().then(t=>this.loadCard(t)),!1},methods:{async handleReadyToAdvance(){let t=this.deferredNextCardAction;if(!t){console.warn(`[StudySession] ready-to-advance received but no deferred action stashed — ignoring`);return}console.log(`[StudySession] ready-to-advance received — advancing with stashed action: ${t}`),this.deferredNextCardAction=null,this.loadCard(await this.sessionController.nextCard(t))},handleReplanRequest(t){if(this.sessionController){let c=t?.label,u=c?` [${c}]`:``;console.log(`[StudySession] Replan requested by card view${u}, deferring until after response processing`),this.replanPending=!0,this.replanOptions=t??null}},user_elo(t){let c=this.userCourseRegDoc.courses.find(c=>c.courseID===t);return toCourseElo(c?c.elo:void 0)},handleClassroomMessage(){return t=>(Z$2({text:this.user?.getUsername()||`[Unknown user]`,status:Status.ok}),console.log(`[StudySession] There was a change in the classroom DB:`),console.log(`[StudySession] change: ${t}`),console.log(`[StudySession] Stringified change: ${JSON.stringify(t)}`),{})},incrementSessionClock(){let t=60*this.sessionTimeLimit-this.timeRemaining;this.sessionController.addTime(Math.min(t,60)),this.tick()},tick(){this.timeRemaining=this.sessionController.secondsRemaining,this.percentageRemaining=this.timeRemaining>60?100*(this.timeRemaining/(60*this.sessionTimeLimit)):100*(this.timeRemaining/60),this.$emit(`time-changed`,this.timeRemaining),this.timeRemaining===0&&clearInterval(this.intervalHandler)},async initSession(){let t=[];try{console.log(`[StudySession] starting study session w/ sources: ${JSON.stringify(this.contentSources)}`),console.log(`[StudySession] Beginning preparation process`),this.sessionContentSources=markRaw((await Promise.all(this.contentSources.map(async t=>{try{return await getStudySource(t,this.user)}catch(c){return console.error(`Failed to load study source: ${t.type}/${t.id}`,c),null}}))).filter(t=>t!==null)),this.timeRemaining=this.sessionTimeLimit*60,t=await Promise.all(this.contentSources.filter(t=>t.type===`classroom`).map(async t=>await this.dataLayer.getClassroomDB(t.id,`student`))),t.forEach(t=>{});let c={};this.sessionConfig?.defaultBatchLimit!==void 0&&(c.defaultBatchLimit=this.sessionConfig.defaultBatchLimit),this.sessionConfig?.initialReviewCap!==void 0&&(c.initialReviewCap=this.sessionConfig.initialReviewCap),this.sessionConfig?.outcomeObservers?.length&&(c.outcomeObservers=this.sessionConfig.outcomeObservers),this.sessionController=markRaw(new SessionController(this.sessionContentSources,60*this.sessionTimeLimit,this.dataLayer,this.getViewComponent,void 0,c)),this.sessionController.sessionRecord=this.sessionRecord,this.sessionConfig?.initHints&&(this.sessionController.setSessionHints(this.sessionConfig.initHints),console.log(`[StudySession] Applied init hints as session-durable hints`)),await this.sessionController.prepareSession(),this.intervalHandler=setInterval(this.tick,1e3),this.sessionPrepared=!0,console.log(`[StudySession] Session preparation complete, emitting session-prepared event`),this.$emit(`session-prepared`),console.log(`[StudySession] Event emission completed`)}catch(t){console.error(`[StudySession] Error during session preparation:`,t),this.$emit(`session-error`,{message:`Failed to prepare study session`,error:t})}try{this.contentSources.filter(t=>t.type===`course`).forEach(async t=>this.courseNames[t.id]=(await this.dataLayer.getCoursesDB().getCourseConfig(t.id)).name),console.log(`[StudySession] Session created:
479
479
  ${this.sessionController?.toString()||`Session controller not initialized`}
480
480
  User courses: ${this.contentSources.filter(t=>t.type===`course`).map(t=>t.id).toString()}
481
481
  User classrooms: ${t.map(t=>t._id).toString()||`No classrooms`}
482
- `)}catch(t){console.error(`[StudySession] Error during final session setup:`,t)}if(this.sessionController)try{this.$emit(`session-started`),this.loadCard(await this.sessionController.nextCard())}catch(t){console.error(`[StudySession] Error loading next card:`,t),this.$emit(`session-error`,{message:`Failed to load study card`,error:t})}else console.error(`[StudySession] Cannot load card: session controller not initialized`),this.$emit(`session-error`,{message:`Study session initialization failed`})},countCardViews(t,c){return this.sessionRecord.filter(u=>u.card.course_id===t&&u.card.card_id===c).length},async processResponse(t){this.$emit(`card-response`,t),this.timerIsActive=!0,t.cardID=this.cardID,t.courseID=this.courseID,this.currentCard.records.push(t),console.log(`[StudySession] StudySession.processResponse is running...`);let c=this.logCardRecord(t).catch(t=>{throw console.error(`[StudySession] putCardRecord failed:`,t),t}),u=1,d=1;if(isQuestionView(this.$refs.cardViewer?.$refs.activeView)){let t=this.$refs.cardViewer.$refs.activeView;u=t.maxAttemptsPerView,d=t.maxSessionViews}let m=this.countCardViews(this.courseID,this.cardID),g=await this.sessionController.submitResponse(t,c,this.userCourseRegDoc,this.currentCard,this.courseID,this.cardID,u,d,m);try{this.handleUIFeedback(g)}catch(t){console.error(`[StudySession] Error handling UI feedback: ${t}.\n\nResult: ${JSON.stringify(g)}`)}if(this.replanPending){let t=this.replanOptions,c=t?.label,u=c?` [${c}]`:``;console.log(`[StudySession] Firing deferred replan (post-submitResponse)${u}`),this.replanPending=!1,this.replanOptions=null,this.sessionController.requestReplan(t??void 0),this.$emit(`replan-requested`)}g.deferred?(console.log(`[StudySession] Deferred advance — stashing action: ${g.nextCardAction}`),this.deferredNextCardAction=g.nextCardAction):g.shouldLoadNextCard&&this.loadCard(await this.sessionController.nextCard(g.nextCardAction)),g.shouldClearFeedbackShadow&&this.clearFeedbackShadow()},handleUIFeedback(t){if(t.isCorrect){if(!this.frameless)try{this.$refs.shadowWrapper&&t.performanceScore!==void 0&&(this.$refs.shadowWrapper.setAttribute(`style`,`--r: ${255*(1-t.performanceScore)}; --g:255`),this.$refs.shadowWrapper.classList.add(`correct`))}catch(t){console.warn(`[StudySession] Error setting shadowWrapper style: ${t}`)}this.sessionConfig.likesConfetti&&Zt({origin:{y:1,x:.25+.5*Math.random()},disableForReducedMotion:!0,angle:60+60*Math.random()})}else if(!this.frameless)try{this.$refs.shadowWrapper&&this.$refs.shadowWrapper.classList.add(`incorrect`)}catch(t){console.warn(`[StudySession] Error setting shadowWrapper style: ${t}`)}},clearFeedbackShadow(){this.frameless||setTimeout(()=>{try{this.$refs.shadowWrapper&&this.$refs.shadowWrapper.classList.remove(`correct`,`incorrect`)}catch(t){console.warn(`[StudySession] Error clearing shadowWrapper style: ${t}`)}},1250)},async logCardRecord(t){console.log(`[StudySession] About to call user.putCardRecord...`);let c=await this.user.putCardRecord(t);return console.log(`[StudySession] user.putCardRecord completed`),c},async loadCard(t){if(this.loading){console.warn(`Attempted to load card while loading another...`);return}if(console.log(`[StudySession] loading: ${JSON.stringify(t)}`),t===null){this.sessionFinished=!0,this.$emit(`session-finished`,this.sessionRecord);return}this.cardType=t.item.status,this.loading=!0,this.cardCount++,this.data=t.data,this.view=markRaw(t.view),this.cardID=t.item.cardID,this.courseID=t.item.courseID,this.card_elo=t.item.elo||1e3,this.sessionRecord.push({card:{course_id:t.item.courseID,card_id:t.item.cardID,card_elo:this.card_elo,tags:t.tags??[]},item:t.item,records:[]}),this.$emit(`card-loaded`,{courseID:t.item.courseID,cardID:t.item.cardID,cardCount:this.cardCount}),this.loading=!1}}}),$t={key:0,class:`StudySession`},en={key:2},tn={key:3},nn={class:`text-h4`},rn={key:0},an={key:4,ref:`shadowWrapper`,class:`card-transition-container`},on={key:0},__name(_sfc_render$15,`_sfc_render`),sn=F$3(Qt,[[`render`,_sfc_render$15],[`__scopeId`,`data-v-bc445530`]]),cn=defineComponent({name:`MultipleChoiceOption`,components:{MarkdownRenderer:defineAsyncComponent(()=>Promise.resolve().then(()=>(init_MarkdownRenderer_DoVbFpA6(),MarkdownRenderer_DoVbFpA6_exports)).then(t=>t.n))},props:{content:{type:String,required:!0},selected:{type:Boolean,required:!0},number:{type:Number,required:!0},setSelection:{type:Function,required:!0},submit:{type:Function,required:!0},markedWrong:{type:Boolean,required:!0}},computed:{className(){let t;switch(this.number){case 0:t=`bg-red`;break;case 1:t=`bg-purple`;break;case 2:t=`bg-indigo`;break;case 3:t=`bg-light-blue`;break;case 4:t=`bg-teal`;break;case 5:t=`bg-deep-orange`;break;default:t=`bg-grey`;break}if(this.selected&&!this.markedWrong)return`choice selected ${t} lighten-3 elevation-8`;if(!this.selected&&!this.markedWrong)return`choice not-selected ${t} lighten-4 elevation-1`;if(this.selected&&this.markedWrong)return`choice selected grey lighten-2 elevation-8`;if(!this.selected&&this.markedWrong)return`choice not-selected grey lighten-2 elevation-0`;throw Error(`'selected' and 'markedWrong' props in MultipleChoiceOption are in an impossible configuration.`)}},methods:{select(){this.setSelection(this.number)},submitThisOption(){this.markedWrong||(this.select(),this.submit())}}}),__name(_sfc_render$14,`_sfc_render`),ln=F$3(cn,[[`render`,_sfc_render$14],[`__scopeId`,`data-v-96de7172`]]),un=defineComponent({name:`RadioMultipleChoice`,components:{MultipleChoiceOption:ln},extends:se$3,props:{choiceList:{type:Array,required:!0}},data(){return{currentSelection:-1,incorrectSelections:[],containerRef:null,_registeredHotkeys:[]}},watch:{choiceList:{immediate:!0,handler(t){t?.length&&this.bindKeys()}}},mounted(){this.containerRef&&this.containerRef.focus()},unmounted(){this.unbindKeys()},methods:{forwardSelection(){if(!this.choiceIsWrong(this.choiceList[this.currentSelection])&&this.currentSelection!==-1){let t={choiceList:this.choiceList,selection:this.currentSelection};this.submitAnswer(t).isCorrect||this.incorrectSelections.push(this.currentSelection)}},setSelection(t){t<this.choiceList.length&&(this.currentSelection=t)},incrementSelection(){this.currentSelection===-1?this.currentSelection=Math.ceil(this.choiceList.length/2):this.currentSelection=Math.min(this.choiceList.length-1,this.currentSelection+1)},decrementSelection(){this.currentSelection===-1?this.currentSelection=Math.floor(this.choiceList.length/2-1):this.currentSelection=Math.max(0,this.currentSelection-1)},choiceIsWrong(t){let c=!1;return this.incorrectSelections.forEach(u=>{this.choiceList[u]===t&&(c=!0)}),c},bindKeys(){let t=[{hotkey:`left`,callback:this.decrementSelection,command:`Move selection left`},{hotkey:`right`,callback:this.incrementSelection,command:`Move selection right`},{hotkey:`enter`,callback:this.forwardSelection,command:`Submit selection`},...Array.from({length:this.choiceList.length},(t,c)=>({hotkey:(c+1).toString(),callback:()=>this.setSelection(c),command:`Select ${(t=>{switch(t){case 0:return`first`;case 1:return`second`;case 2:return`third`;case 3:return`fourth`;case 4:return`fifth`;case 5:return`sixth`;default:return`${t+1}th`}})(c)} option`}))];I$2.addBinding(t),this._registeredHotkeys=t.map(t=>t.hotkey)},unbindKeys(){this._registeredHotkeys&&this._registeredHotkeys.forEach(t=>{I$2.removeBinding(t)})}}}),dn={ref:`containerRef`,class:`multipleChoice`},__name(_sfc_render$13,`_sfc_render`),fn=F$3(un,[[`render`,_sfc_render$13]]),pn=defineComponent({name:`TrueFalse`,components:{RadioMultipleChoice:fn},props:{MouseTrap:{type:Object,required:!0},submit:{type:Function,required:!0}}}),mn={"data-viewable":`TrueFalse`},__name(_sfc_render$12,`_sfc_render`),hn=F$3(pn,[[`render`,_sfc_render$12]]),gn=defineComponent({name:`UserInputNumber`,ref:{},extends:se$3,methods:{mounted(){this.$refs.input.focus()},isNumeric(t){return!isNaN(Number.parseFloat(t))},makeNumeric(t){if(typeof t==`string`)return Number.parseFloat(t);throw Error(`Expected a string, got `+typeof t)}}}),__name(_sfc_render$11,`_sfc_render`),_n=F$3(gn,[[`render`,_sfc_render$11],[`__scopeId`,`data-v-a56dcd1c`]]),vn=defineComponent({name:`CardLoader`,components:{CardViewer:Q$1},props:{sessionOrder:{type:Number,required:!1,default:0},qualified_id:{type:Object,required:!0},viewLookup:{type:Function,required:!0}},data(){return{loading:!0,view:null,data:[],courseID:``,cardID:``}},created(){this.loadCard()},watch:{qualified_id:{immediate:!0,handler(){this.loadCard()}}},methods:{processResponse(t){log$2(`
482
+ `)}catch(t){console.error(`[StudySession] Error during final session setup:`,t)}if(this.sessionController)try{this.$emit(`session-started`),this.loadCard(await this.sessionController.nextCard())}catch(t){console.error(`[StudySession] Error loading next card:`,t),this.$emit(`session-error`,{message:`Failed to load study card`,error:t})}else console.error(`[StudySession] Cannot load card: session controller not initialized`),this.$emit(`session-error`,{message:`Study session initialization failed`})},countCardViews(t,c){return this.sessionRecord.filter(u=>u.card.course_id===t&&u.card.card_id===c).length},async processResponse(t){this.$emit(`card-response`,t),this.timerIsActive=!0,t.cardID=this.cardID,t.courseID=this.courseID,this.currentCard.records.push(t),console.log(`[StudySession] StudySession.processResponse is running...`);let c=this.logCardRecord(t).catch(t=>{throw console.error(`[StudySession] putCardRecord failed:`,t),t}),u=1,d=1;if(isQuestionView(this.$refs.cardViewer?.$refs.activeView)){let t=this.$refs.cardViewer.$refs.activeView;u=t.maxAttemptsPerView,d=t.maxSessionViews}let m=this.countCardViews(this.courseID,this.cardID),g=await this.sessionController.submitResponse(t,c,this.userCourseRegDoc,this.currentCard,this.courseID,this.cardID,u,d,m);try{this.handleUIFeedback(g)}catch(t){console.error(`[StudySession] Error handling UI feedback: ${t}.\n\nResult: ${JSON.stringify(g)}`)}if(this.replanPending){let t=this.replanOptions,c=t?.label,u=c?` [${c}]`:``;console.log(`[StudySession] Firing deferred replan (post-submitResponse)${u}`),this.replanPending=!1,this.replanOptions=null,this.sessionController.requestReplan(t??void 0),this.$emit(`replan-requested`)}g.deferred?(console.log(`[StudySession] Deferred advance — stashing action: ${g.nextCardAction}`),this.deferredNextCardAction=g.nextCardAction):g.shouldLoadNextCard&&this.loadCard(await this.sessionController.nextCard(g.nextCardAction)),g.shouldClearFeedbackShadow&&this.clearFeedbackShadow()},handleUIFeedback(t){if(t.isCorrect){if(!this.frameless)try{this.$refs.shadowWrapper&&t.performanceScore!==void 0&&(this.$refs.shadowWrapper.setAttribute(`style`,`--r: ${255*(1-t.performanceScore)}; --g:255`),this.$refs.shadowWrapper.classList.add(`correct`))}catch(t){console.warn(`[StudySession] Error setting shadowWrapper style: ${t}`)}this.sessionConfig.likesConfetti&&Zt({origin:{y:1,x:.25+.5*Math.random()},disableForReducedMotion:!0,angle:60+60*Math.random()})}else if(!this.frameless)try{this.$refs.shadowWrapper&&this.$refs.shadowWrapper.classList.add(`incorrect`)}catch(t){console.warn(`[StudySession] Error setting shadowWrapper style: ${t}`)}},clearFeedbackShadow(){this.frameless||setTimeout(()=>{try{this.$refs.shadowWrapper&&this.$refs.shadowWrapper.classList.remove(`correct`,`incorrect`)}catch(t){console.warn(`[StudySession] Error clearing shadowWrapper style: ${t}`)}},1250)},async logCardRecord(t){console.log(`[StudySession] About to call user.putCardRecord...`);let c=await this.user.putCardRecord(t);return console.log(`[StudySession] user.putCardRecord completed`),c},async loadCard(t){if(this.loading){console.warn(`Attempted to load card while loading another...`);return}if(console.log(`[StudySession] loading: ${JSON.stringify(t)}`),t===null){this.sessionFinished=!0,this.$emit(`session-finished`,this.sessionRecord);return}this.cardType=t.item.status,this.loading=!0,this.cardCount++,this.data=t.data,this.view=markRaw(t.view),this.cardID=t.item.cardID,this.courseID=t.item.courseID,this.card_elo=t.item.elo||1e3,this.sessionRecord.push({card:{course_id:t.item.courseID,card_id:t.item.cardID,card_elo:this.card_elo,tags:t.tags??[]},item:t.item,records:[]}),this.$emit(`card-loaded`,{courseID:t.item.courseID,cardID:t.item.cardID,cardCount:this.cardCount}),this.loading=!1}}}),$t={key:0,class:`StudySession`},en={key:2},tn={key:3},nn={class:`text-h4`},rn={key:0},an={key:4,ref:`shadowWrapper`,class:`card-transition-container`},on={key:0},__name(_sfc_render$15,`_sfc_render`),sn=F$3(Qt,[[`render`,_sfc_render$15],[`__scopeId`,`data-v-de7119e9`]]),cn=defineComponent({name:`MultipleChoiceOption`,components:{MarkdownRenderer:defineAsyncComponent(()=>Promise.resolve().then(()=>(init_MarkdownRenderer_DoVbFpA6(),MarkdownRenderer_DoVbFpA6_exports)).then(t=>t.n))},props:{content:{type:String,required:!0},selected:{type:Boolean,required:!0},number:{type:Number,required:!0},setSelection:{type:Function,required:!0},submit:{type:Function,required:!0},markedWrong:{type:Boolean,required:!0}},computed:{className(){let t;switch(this.number){case 0:t=`bg-red`;break;case 1:t=`bg-purple`;break;case 2:t=`bg-indigo`;break;case 3:t=`bg-light-blue`;break;case 4:t=`bg-teal`;break;case 5:t=`bg-deep-orange`;break;default:t=`bg-grey`;break}if(this.selected&&!this.markedWrong)return`choice selected ${t} lighten-3 elevation-8`;if(!this.selected&&!this.markedWrong)return`choice not-selected ${t} lighten-4 elevation-1`;if(this.selected&&this.markedWrong)return`choice selected grey lighten-2 elevation-8`;if(!this.selected&&this.markedWrong)return`choice not-selected grey lighten-2 elevation-0`;throw Error(`'selected' and 'markedWrong' props in MultipleChoiceOption are in an impossible configuration.`)}},methods:{select(){this.setSelection(this.number)},submitThisOption(){this.markedWrong||(this.select(),this.submit())}}}),__name(_sfc_render$14,`_sfc_render`),ln=F$3(cn,[[`render`,_sfc_render$14],[`__scopeId`,`data-v-96de7172`]]),un=defineComponent({name:`RadioMultipleChoice`,components:{MultipleChoiceOption:ln},extends:se$3,props:{choiceList:{type:Array,required:!0}},data(){return{currentSelection:-1,incorrectSelections:[],containerRef:null,_registeredHotkeys:[]}},watch:{choiceList:{immediate:!0,handler(t){t?.length&&this.bindKeys()}}},mounted(){this.containerRef&&this.containerRef.focus()},unmounted(){this.unbindKeys()},methods:{forwardSelection(){if(!this.choiceIsWrong(this.choiceList[this.currentSelection])&&this.currentSelection!==-1){let t={choiceList:this.choiceList,selection:this.currentSelection};this.submitAnswer(t).isCorrect||this.incorrectSelections.push(this.currentSelection)}},setSelection(t){t<this.choiceList.length&&(this.currentSelection=t)},incrementSelection(){this.currentSelection===-1?this.currentSelection=Math.ceil(this.choiceList.length/2):this.currentSelection=Math.min(this.choiceList.length-1,this.currentSelection+1)},decrementSelection(){this.currentSelection===-1?this.currentSelection=Math.floor(this.choiceList.length/2-1):this.currentSelection=Math.max(0,this.currentSelection-1)},choiceIsWrong(t){let c=!1;return this.incorrectSelections.forEach(u=>{this.choiceList[u]===t&&(c=!0)}),c},bindKeys(){let t=[{hotkey:`left`,callback:this.decrementSelection,command:`Move selection left`},{hotkey:`right`,callback:this.incrementSelection,command:`Move selection right`},{hotkey:`enter`,callback:this.forwardSelection,command:`Submit selection`},...Array.from({length:this.choiceList.length},(t,c)=>({hotkey:(c+1).toString(),callback:()=>this.setSelection(c),command:`Select ${(t=>{switch(t){case 0:return`first`;case 1:return`second`;case 2:return`third`;case 3:return`fourth`;case 4:return`fifth`;case 5:return`sixth`;default:return`${t+1}th`}})(c)} option`}))];I$2.addBinding(t),this._registeredHotkeys=t.map(t=>t.hotkey)},unbindKeys(){this._registeredHotkeys&&this._registeredHotkeys.forEach(t=>{I$2.removeBinding(t)})}}}),dn={ref:`containerRef`,class:`multipleChoice`},__name(_sfc_render$13,`_sfc_render`),fn=F$3(un,[[`render`,_sfc_render$13]]),pn=defineComponent({name:`TrueFalse`,components:{RadioMultipleChoice:fn},props:{MouseTrap:{type:Object,required:!0},submit:{type:Function,required:!0}}}),mn={"data-viewable":`TrueFalse`},__name(_sfc_render$12,`_sfc_render`),hn=F$3(pn,[[`render`,_sfc_render$12]]),gn=defineComponent({name:`UserInputNumber`,ref:{},extends:se$3,methods:{mounted(){this.$refs.input.focus()},isNumeric(t){return!isNaN(Number.parseFloat(t))},makeNumeric(t){if(typeof t==`string`)return Number.parseFloat(t);throw Error(`Expected a string, got `+typeof t)}}}),__name(_sfc_render$11,`_sfc_render`),_n=F$3(gn,[[`render`,_sfc_render$11],[`__scopeId`,`data-v-a56dcd1c`]]),vn=defineComponent({name:`CardLoader`,components:{CardViewer:Q$1},props:{sessionOrder:{type:Number,required:!1,default:0},qualified_id:{type:Object,required:!0},viewLookup:{type:Function,required:!0}},data(){return{loading:!0,view:null,data:[],courseID:``,cardID:``}},created(){this.loadCard()},watch:{qualified_id:{immediate:!0,handler(){this.loadCard()}}},methods:{processResponse(t){log$2(`
483
483
  Card was displayed at ${t.timeStamp}
484
484
  User spent ${t.timeSpent} milliseconds with the card.
485
485
  `),this.$emit(`emitResponse`,t)},async loadCard(){let t=this.qualified_id;console.log(`Card Loader displaying: ${t.courseID}::${t.cardID}`),this.loading=!0;let c=t.courseID,u=t.cardID,d=getDataLayer().getCourseDB(c);try{let t=await d.getCourseDoc(u),m=this.viewLookup(t.id_view),g=t.id_displayable_data.map(t=>d.getCourseDoc(t,{attachments:!0,binary:!0})),b=[];for(let t of g){let c=await t;b.unshift(displayableDataToViewData(c))}this.data=b,this.view=markRaw(m),this.cardID=u,this.courseID=c}catch(t){throw Error(`[CardLoader] Error loading card: ${JSON.stringify(t)}, ${t}`)}finally{this.loading=!1,this.$emit(`card-loaded`)}}}}),__name(_sfc_render$10,`_sfc_render`),yn=F$3(vn,[[`render`,_sfc_render$10],[`__scopeId`,`data-v-93f758b5`]]),useAuthStore=()=>{let t=getPinia();return t&&setActivePinia(t),defineStore(`auth`,{state:()=>({_user:void 0,loginAndRegistration:{init:!1,loggedIn:!1,regDialogOpen:!1,loginDialogOpen:!1},onLoadComplete:!1}),actions:{async init(){try{this._user=getDataLayer().getUserDB(),this.loginAndRegistration.loggedIn=this._user?this._user.isLoggedIn():!1,this.onLoadComplete=!0,this.loginAndRegistration.init=!0}catch(t){console.error(`Failed to initialize auth store:`,t),this.loginAndRegistration.loggedIn=!1,this.onLoadComplete=!0,this.loginAndRegistration.init=!0}},setLoginDialog(t){this.loginAndRegistration.loginDialogOpen=t},setRegDialog(t){this.loginAndRegistration.regDialogOpen=t},async resetUserData(){try{if(!this._user)throw Error(`No user available for data reset`);let t=await this._user.resetUserData();if(t.status!==`ok`)throw Error(t.error||`Reset failed`);return console.log(`User data reset successfully`),t}catch(t){throw console.error(`Failed to reset user data:`,t),t}}},getters:{currentUser:async()=>getCurrentUser(),isLoggedIn:t=>t.loginAndRegistration.loggedIn,isInitialized:t=>t.loginAndRegistration.init,status:t=>({loggedIn:t.loginAndRegistration.loggedIn,init:t.loginAndRegistration.init,user:t._user})}})()},useConfigStore=()=>{let t=getPinia();return t&&setActivePinia(t),defineStore(`config`,{state:()=>({config:{darkMode:!1,likesConfetti:!1,sessionTimeLimit:5}}),actions:{updateConfig(t){this.config=t},async updateDarkMode(t){this.config.darkMode=t;let c=await getCurrentUser();c&&await c.setConfig({darkMode:t})},async updateLikesConfetti(t){this.config.likesConfetti=t;let c=await getCurrentUser();c&&await c.setConfig({likesConfetti:t})},async updateSessionTimeLimit(t){this.config.sessionTimeLimit=t;let c=await getCurrentUser();c&&await c.setConfig({sessionTimeLimit:t})},async hydrate(){try{let t=await getCurrentUser();if(t){let c=await t.getConfig();console.log(`user config: ${JSON.stringify(c)}`),this.updateConfig(c)}else console.log(`No user logged in, using default config`)}catch(t){console.warn(`Failed to hydrate config store, using defaults:`,t)}},async init(){await this.hydrate()},resetDefaults(){this.config={darkMode:!1,likesConfetti:!1,sessionTimeLimit:5}}}})()},bn=F$3(defineComponent({__name:`UserChip`,props:{showLoginButton:{type:Boolean},redirectToPath:{}},setup(t){let c=useRouter(),u=useAuthStore(),d=useConfigStore(),m=useAuthUI(),g=ref(``),b=ref([]),S=ref(!1),C=ref(``),w=computed(()=>C.value===`reset`),resetDialogState=()=>{C.value=``,S.value=!1},T=computed(()=>b.value.length>0),E=computed(()=>m.config.value||{showLoginRegistration:!0,showLogout:!0,showResetData:!1,logoutLabel:`Log out`,resetLabel:``});onMounted(async()=>{g.value=(await getCurrentUser()).getUsername(),await m.detectSyncStrategy()});let gotoSettings=async()=>{c.push(`/u/${(await getCurrentUser()).getUsername()}`)},gotoStats=async()=>{c.push(`/u/${(await getCurrentUser()).getUsername()}/stats`)},dismiss=t=>{let c=b.value.indexOf(t);b.value.splice(c,1)},logout=async()=>{(await u._user.logout()).ok&&(u.loginAndRegistration={init:!0,loggedIn:!1,regDialogOpen:!1,loginDialogOpen:!1},d.resetDefaults(),c.push(`/home`))},executeReset=async()=>{try{await u.resetUserData(),d.resetDefaults(),resetDialogState(),c.push(`/home`)}catch(t){console.error(`Failed to reset user data:`,t)}};return(t,c)=>{let u=resolveComponent(`v-icon`),d=resolveComponent(`v-avatar`),m=resolveComponent(`v-chip`),D=resolveComponent(`v-list-item-title`),O=resolveComponent(`v-list-item`),Or=resolveComponent(`v-divider`),kr=resolveComponent(`v-list`),Ar=resolveComponent(`v-menu`),jr=resolveComponent(`v-badge`),Mr=resolveComponent(`v-card-title`),Nr=resolveComponent(`v-text-field`),Pr=resolveComponent(`v-card-text`),Fr=resolveComponent(`v-spacer`),Ir=resolveComponent(`v-btn`),Lr=resolveComponent(`v-card-actions`),Rr=resolveComponent(`v-card`),zr=resolveComponent(`v-dialog`);return openBlock(),createElementBlock(Fragment,null,[createVNode(jr,{content:b.value.length,"model-value":T.value,color:`accent`,location:`end top`},{default:withCtx(()=>[createVNode(Ar,{location:`bottom end`,transition:`scale-transition`},{activator:withCtx(({props:t})=>[createVNode(m,mergeProps(t,{class:`ma-2`}),{default:withCtx(()=>[createVNode(d,{start:``,class:`bg-primary`},{default:withCtx(()=>[createVNode(u,null,{default:withCtx(()=>c[4]||(c[4]=[createTextVNode(`mdi-school`)])),_:1})]),_:1}),createTextVNode(` `+toDisplayString(g.value),1)]),_:2},1040)]),default:withCtx(()=>[createVNode(kr,null,{default:withCtx(()=>[(openBlock(!0),createElementBlock(Fragment,null,renderList(b.value,t=>(openBlock(),createBlock(O,{key:t,onClick:c=>dismiss(t)},{default:withCtx(()=>[createVNode(D,null,{default:withCtx(()=>[createTextVNode(toDisplayString(t),1)]),_:2},1024)]),_:2},1032,[`onClick`]))),128)),b.value.length?(openBlock(),createBlock(Or,{key:0})):createCommentVNode(``,!0),createVNode(O,{onClick:gotoStats},{prepend:withCtx(()=>[createVNode(u,null,{default:withCtx(()=>c[5]||(c[5]=[createTextVNode(`mdi-trending-up`)])),_:1})]),default:withCtx(()=>[createVNode(D,null,{default:withCtx(()=>c[6]||(c[6]=[createTextVNode(`Stats`)])),_:1})]),_:1}),createVNode(O,{onClick:gotoSettings},{prepend:withCtx(()=>[createVNode(u,null,{default:withCtx(()=>c[7]||(c[7]=[createTextVNode(`mdi-cog`)])),_:1})]),default:withCtx(()=>[createVNode(D,null,{default:withCtx(()=>c[8]||(c[8]=[createTextVNode(`Settings`)])),_:1})]),_:1}),E.value.showLogout?(openBlock(),createBlock(O,{key:1,onClick:logout},{prepend:withCtx(()=>[createVNode(u,null,{default:withCtx(()=>c[9]||(c[9]=[createTextVNode(`mdi-logout`)])),_:1})]),default:withCtx(()=>[createVNode(D,null,{default:withCtx(()=>[createTextVNode(toDisplayString(E.value.logoutLabel),1)]),_:1})]),_:1})):createCommentVNode(``,!0),E.value.showResetData?(openBlock(),createBlock(O,{key:2,onClick:c[0]||(c[0]=t=>S.value=!0)},{prepend:withCtx(()=>[createVNode(u,null,{default:withCtx(()=>c[10]||(c[10]=[createTextVNode(`mdi-delete-sweep`)])),_:1})]),default:withCtx(()=>[createVNode(D,null,{default:withCtx(()=>[createTextVNode(toDisplayString(E.value.resetLabel),1)]),_:1})]),_:1})):createCommentVNode(``,!0)]),_:1})]),_:1})]),_:1},8,[`content`,`model-value`]),createVNode(zr,{modelValue:S.value,"onUpdate:modelValue":c[3]||(c[3]=t=>S.value=t),"max-width":`500px`,persistent:``},{default:withCtx(()=>[createVNode(Rr,null,{default:withCtx(()=>[createVNode(Mr,{class:`text-h5 d-flex align-center`},{default:withCtx(()=>[createVNode(u,{color:`warning`,class:`mr-3`},{default:withCtx(()=>c[11]||(c[11]=[createTextVNode(`mdi-alert-circle`)])),_:1}),c[12]||(c[12]=createTextVNode(` Reset All User Data `))]),_:1}),createVNode(Pr,null,{default:withCtx(()=>[c[13]||(c[13]=createBaseVNode(`p`,{class:`mb-4`},`This will permanently delete:`,-1)),c[14]||(c[14]=createBaseVNode(`ul`,{class:`mb-4`},[createBaseVNode(`li`,null,`All course progress and history`),createBaseVNode(`li`,null,`Scheduled card reviews`),createBaseVNode(`li`,null,`Course registrations`),createBaseVNode(`li`,null,`User preferences`)],-1)),c[15]||(c[15]=createBaseVNode(`p`,{class:`mb-4 text-error font-weight-bold`},`This cannot be undone.`,-1)),createVNode(Nr,{modelValue:C.value,"onUpdate:modelValue":c[1]||(c[1]=t=>C.value=t),label:`Type "reset" to confirm`,outlined:``,dense:``,onKeyup:c[2]||(c[2]=withKeys(t=>w.value&&executeReset(),[`enter`]))},null,8,[`modelValue`])]),_:1}),createVNode(Lr,null,{default:withCtx(()=>[createVNode(Fr),createVNode(Ir,{text:``,onClick:resetDialogState},{default:withCtx(()=>c[16]||(c[16]=[createTextVNode(`Cancel`)])),_:1}),createVNode(Ir,{color:`error`,disabled:!w.value,onClick:executeReset},{default:withCtx(()=>c[17]||(c[17]=[createTextVNode(` Reset All Data `)])),_:1},8,[`disabled`])]),_:1})]),_:1})]),_:1},8,[`modelValue`])],64)}}}),[[`__scopeId`,`data-v-9a38a213`]]),xn={class:`d-flex flex-column align-start`},Sn={class:`mb-2`},Cn=F$3(defineComponent({__name:`UserLogin`,props:{redirectTo:{default:`/study`}},emits:[`toggle`,`loginSuccess`,`forgotPassword`],setup(t,{emit:c}){let u=t,d=c,m=useRouter(),g=useRoute(),b=useAuthStore(),S=useConfigStore(),C=ref(``),w=ref(``),T=ref(!1),E=ref(!1),D=ref(!1),O=ref(7e3),Or=ref(void 0),kr=computed(()=>g.name===`login`),Ar=computed(()=>({color:D.value?`error`:`success`,text:D.value?`Try again`:`Log In`})),initBadLogin=()=>{D.value=!0,Z$2({text:`Username or password was incorrect.`,status:Status.error,timeout:O.value}),setTimeout(()=>{D.value=!1},O.value)},login=async()=>{E.value=!0,log$2(`Starting login attempt`),log$2(`Login attempt for username: ${C.value}`);try{log$2(`Attempting to get User instance`),Or.value=await getCurrentUser(),log$2(`Got User instance, attempting login`),await Or.value.login(C.value,w.value),log$2(`Login successful`),log$2(`Initializing user config`),S.init(),log$2(`User config initialized`),log$2(`Setting authentication state`),b.loginAndRegistration.loggedIn=!0,log$2(`Authentication state set, redirecting to: ${u.redirectTo}`),d(`loginSuccess`,u.redirectTo),m.push(u.redirectTo),log$2(`Login and redirect complete`)}catch(t){log$2(`Login attempt failed`),log$2(`Login error details: ${JSON.stringify(t)}`),console.log(`login error: ${JSON.stringify(t)}`),log$2(`Initiating bad login feedback`),initBadLogin()}log$2(`Resetting awaiting response state`),E.value=!1},toggle=()=>{log$2(`Toggling registration / login forms.`),d(`toggle`)},handleForgotPassword=()=>{log$2(`Forgot password clicked`),kr.value?m.push(`/request-reset`):d(`forgotPassword`)};return(t,c)=>{let u=resolveComponent(`v-card-title`),d=resolveComponent(`v-text-field`),m=resolveComponent(`v-btn`),g=resolveComponent(`v-snackbar`),b=resolveComponent(`v-icon`),S=resolveComponent(`router-link`),Or=resolveComponent(`v-form`),jr=resolveComponent(`v-card-text`),Mr=resolveComponent(`v-card`);return openBlock(),createBlock(Mr,null,{default:withCtx(()=>[kr.value?createCommentVNode(``,!0):(openBlock(),createBlock(u,{key:0,class:`text-h5 bg-grey-lighten-2`},{default:withCtx(()=>c[5]||(c[5]=[createTextVNode(`Log In`)])),_:1})),createVNode(jr,null,{default:withCtx(()=>[createVNode(Or,{onsubmit:`return false;`,onSubmit:withModifiers(login,[`prevent`])},{default:withCtx(()=>[createVNode(d,{id:``,modelValue:C.value,"onUpdate:modelValue":c[0]||(c[0]=t=>C.value=t),autofocus:``,name:`username`,label:`Username`,"prepend-icon":`mdi-account-circle`},null,8,[`modelValue`]),createVNode(d,{modelValue:w.value,"onUpdate:modelValue":c[1]||(c[1]=t=>w.value=t),"prepend-icon":`mdi-lock`,name:`password`,hover:`Show password input`,label:`Enter your password`,hint:``,min:`0`,"append-icon":T.value?`mdi-eye-off`:`mdi-eye`,type:T.value?`text`:`password`,"onClick:append":c[2]||(c[2]=()=>T.value=!T.value)},null,8,[`modelValue`,`append-icon`,`type`]),createVNode(g,{modelValue:D.value,"onUpdate:modelValue":c[4]||(c[4]=t=>D.value=t),location:`bottom right`,timeout:O.value},{default:withCtx(()=>[c[7]||(c[7]=createTextVNode(` Username or password was incorrect. `)),createVNode(m,{color:`pink`,variant:`text`,onClick:c[3]||(c[3]=t=>D.value=!1)},{default:withCtx(()=>c[6]||(c[6]=[createTextVNode(`Close`)])),_:1})]),_:1},8,[`modelValue`,`timeout`]),createBaseVNode(`div`,xn,[createBaseVNode(`div`,Sn,[createVNode(m,{class:`mr-2`,type:`submit`,loading:E.value,color:Ar.value.color},{default:withCtx(()=>[createVNode(b,{start:``},{default:withCtx(()=>c[8]||(c[8]=[createTextVNode(`mdi-lock-open`)])),_:1}),c[9]||(c[9]=createTextVNode(` Log In `))]),_:1},8,[`loading`,`color`]),kr.value?(openBlock(),createBlock(S,{key:0,to:`signup`},{default:withCtx(()=>[createVNode(m,{variant:`text`},{default:withCtx(()=>c[10]||(c[10]=[createTextVNode(`Create New Account`)])),_:1})]),_:1})):(openBlock(),createBlock(m,{key:1,variant:`text`,onClick:toggle},{default:withCtx(()=>c[11]||(c[11]=[createTextVNode(`Create New Account`)])),_:1}))]),renderSlot(t.$slots,`forgot-password`,{},()=>[createBaseVNode(`a`,{href:`#`,class:`text-caption text-decoration-none`,onClick:withModifiers(handleForgotPassword,[`prevent`])},` Forgot password? `)],!0)])]),_:3})]),_:3})]),_:3})}}}),[[`__scopeId`,`data-v-563b0048`]]),wn=defineComponent({name:`UserRegistration`,props:{onSignupSuccess:{type:Function,required:!1}},emits:[`toggle`,`signup-success`],data(){return{email:``,username:``,password:``,retypedPassword:``,passwordVisible:!1,emailError:!1,emailHint:``,usernameValidationInProgress:!1,usernameError:!1,usernameHint:``,awaitingResponse:!1,badLoginAttempt:!1,userSecret:``,secret:`goons`,user:null,roles:[`Student`,`Teacher`,`Author`],student:!0,teacher:!1,author:!1,authStore:useAuthStore()}},computed:{registrationRoute(){return typeof this.$route.name==`string`&&this.$route.name.toLowerCase()===`signup`},buttonStatus(){return{color:this.badLoginAttempt?`error`:`success`,text:this.badLoginAttempt?`Try again`:`Log In`}},passwordError(){return validatePassword(this.password)},passwordRetypeError(){return console.log(`[RTE]`),this.password===this.retypedPassword?``:`Passwords must match.`}},async created(){this.user=await getCurrentUser()},methods:{toggle(){log$2(`Toggling registration / login forms.`),this.$emit(`toggle`)},validateEmail(){this.emailError=!1,this.email&&!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.email)?(this.emailError=!0,this.emailHint=`Please enter a valid email address`):this.emailHint=``},validateUsername(){this.usernameError=!1},async createUser(){if(this.awaitingResponse=!0,this.passwordError){Z$2({text:this.passwordError,status:Status.error}),this.awaitingResponse=!1;return}if(log$2(`