diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 948cab8c..6dc56d0e 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -49,7 +49,9 @@ "Bash(npm run test:coverage:integration:*)", "Bash(git revert:*)", "Bash(npm run test:e2e:headed:*)", - "Bash(git stash:*)" + "Bash(git stash:*)", + "Bash(git rebase:*)", + "Bash(git -C /Users/ian/git/BrowserTest status)" ], "deny": [], "ask": [] diff --git a/CLAUDE.md b/CLAUDE.md index f59382d0..807ee069 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -568,6 +568,8 @@ function getStorageKey(release: ReleaseId, serviceId: ServiceId): string { - N/A (development tooling - outputs to coverage/ directory and markdown reports) (006-test-coverage-gaps) - TypeScript 5.x / ES2020+ + Lit 3.x (existing), Vitest 2.x (existing) (007-lit-component-refactor) - N/A (no data model changes—internal refactor only) (007-lit-component-refactor) +- TypeScript 5.x / ES2020+ with Lit 3.x + Lit 3.0 (Web Components), existing qd-modal base componen (008-user-guidance-popups) +- N/A (no data persistence - content from DITA config only) (008-user-guidance-popups) ## Recent Changes - 001-security-refactor: Added TypeScript 5.x / JavaScript ES2020+ + Lit 3.0 (Web Components), Vite 5.x (build), Vitest (testing) diff --git a/dita-demo/oxygen-webhelp/template/resources/sonar-quiz.iife.js b/dita-demo/oxygen-webhelp/template/resources/sonar-quiz.iife.js index 18f87beb..c6c032f2 100644 --- a/dita-demo/oxygen-webhelp/template/resources/sonar-quiz.iife.js +++ b/dita-demo/oxygen-webhelp/template/resources/sonar-quiz.iife.js @@ -1,20 +1,20 @@ -var SonarQuiz=function(t){"use strict";function s(t){if(t.length<2)return"**";if(2===t.length)return t;return t.slice(0,2)+"*".repeat(t.length-2)}function n(t){if(null===t||"object"!=typeof t)return t;const o={};for(const[r,a]of Object.entries(t))"name"!==r&&"passwordHash"!==r&&(o[r]="serviceId"!==r||"string"!=typeof a?"object"!=typeof a||null===a?a:n(a):s(a));return o}function o(t,s){}function r(t,s){if(s instanceof Error){const n={name:s.name,message:s.message};console.error(`[ERROR] ${t}`,n)}else void 0!==s?console.error(`[ERROR] ${t}`,n(s)):console.error(`[ERROR] ${t}`)}function a(t,s){void 0!==s?console.warn(`[WARN] ${t}`,n(s)):console.warn(`[WARN] ${t}`)}function c(t){const s=[],n=[];if(!t.classList.contains("qd-quiz"))return s.push('Table must have class "qd-quiz"'),{element:t,questions:n,errors:s};const o=Array.from(t.querySelectorAll("tbody tr"));return 0===o.length?(s.push("Quiz table has no data rows"),{element:t,questions:n,errors:s}):(o.forEach((t,o)=>{const r=Array.from(t.querySelectorAll("td"));if(3!==r.length)return void s.push(`Row ${o+1} has ${r.length} columns, expected 3 (Question | Answer | Detail)`);const a=r[0],c=r[1],d=r[2];if(!a||!c||!d)return;const l=a.textContent?.trim()||"";if(!l)return void s.push(`Row ${o+1} has empty question text`);const u=c.textContent?.trim()||"";if(!u)return void s.push(`Row ${o+1} has empty answer`);const h=d.querySelector("ol");if(h){const t=(p=h,Array.from(p.querySelectorAll("li")).map(t=>t.textContent?.trim()||"").filter(t=>t.length>0));if(0===t.length)return void s.push(`Row ${o+1} MCQ has no options in
    `);n.push({text:l,kind:"mcq",correctAnswer:u,options:t})}else{const t=d.textContent?.trim()||"",r=parseFloat(t);if(isNaN(r))return void s.push(`Row ${o+1} appears to be numeric but has invalid tolerance: "${t}"`);n.push({text:l,kind:"numeric",correctAnswer:u,tolerance:r})}var p}),{element:t,questions:n,errors:s.length>0?s:void 0})}function d(t,s){if(!s||""===s.trim())return!1;const n=s.trim();if("mcq"===t.kind)return n===t.correctAnswer;{const s=parseFloat(n),o=parseFloat(t.correctAnswer);if(isNaN(s)||isNaN(o))return!1;const r=t.tolerance??0;return Math.abs(s-o)<=r}}const l=18e5,u={SESSION:"qd/session",CACHE:"qd/state",INSTRUCTOR:"qd/instructor",PIN_ATTEMPTS:"qd:pin-attempts"},h=3,p=3e4;class SessionService{createSession(t,s,n){const o=new Date,r=o.toISOString(),a={serviceId:t,name:s,release:n,loginTime:r,lastActivity:r,expiresAt:new Date(o.getTime()+l).toISOString(),instructorUnlocked:!1};return this.saveSession(a),this.emitEvent("qd:login",{serviceId:t,name:s,release:n,loginTime:r}),a}getSession(){try{const t=sessionStorage.getItem(u.SESSION);if(!t)return null;const s=JSON.parse(t);return s.serviceId&&s.release&&s.expiresAt?s:(a("Invalid session data, missing required fields"),null)}catch(t){return r("Failed to parse session data",t),null}}updateActivity(){const t=this.getSession();if(!t)return;const s=new Date;t.lastActivity=s.toISOString(),t.expiresAt=new Date(s.getTime()+l).toISOString(),this.saveSession(t)}isExpired(){const t=this.getSession();return!t||function(t,s=new Date){const n=new Date(t);return!!isNaN(n.getTime())||s>=n}(t.expiresAt)}clearSession(){const t=this.getSession();sessionStorage.removeItem(u.SESSION),sessionStorage.removeItem(u.CACHE),sessionStorage.removeItem(u.INSTRUCTOR),sessionStorage.removeItem("qd/instructor/showAnswers"),t&&(t.serviceId,this.emitEvent("qd:logout",{serviceId:t.serviceId,timestamp:(new Date).toISOString()}))}unlockInstructor(){const t=this.getSession();t&&(t.instructorUnlocked=!0,t.unlockTime=(new Date).toISOString(),this.saveSession(t),this.emitEvent("qd:instructor-unlock",{timestamp:t.unlockTime}))}lockInstructor(){const t=this.getSession();t&&(t.instructorUnlocked=!1,delete t.unlockTime,this.saveSession(t),this.emitEvent("qd:instructor-lock",{timestamp:(new Date).toISOString()}))}isInstructorUnlocked(){const t=this.getSession();return!0===t?.instructorUnlocked}getCache(){try{const t=sessionStorage.getItem(u.CACHE);return t?JSON.parse(t):null}catch(t){return r("Failed to parse cache data",t),null}}saveCache(t){try{sessionStorage.setItem(u.CACHE,JSON.stringify(t))}catch(s){r("Failed to save cache",s)}}clearCache(){sessionStorage.removeItem(u.CACHE)}saveSession(t){try{sessionStorage.setItem(u.SESSION,JSON.stringify(t))}catch(s){r("Failed to save session",s)}}emitEvent(t,s){try{const n=new CustomEvent(t,{detail:s,bubbles:!0});document.dispatchEvent(n)}catch(n){r(`Failed to emit event ${t}`,n)}}}function g(t,s){const n=s.answers.length,o=s.answers.filter(t=>""!==t.answer.trim()).length,r=s.answers.filter(t=>t.success).length;return{state:s.state,total:n,answered:o,correct:r,last:s.lastAttempted,answers:s.answers,analysis:s.analysis}}function m(t){return function(t,s="display"){if(null==t)return console.warn("Invalid date provided to formatTimestamp:",t),"Invalid Date";const n="string"==typeof t?new Date(t):t;return isNaN(n.getTime())?(console.warn("Invalid date provided to formatTimestamp:",t),"Invalid Date"):"csv"===s?function(t){return t.toISOString()}(n):function(t){return`${t.toLocaleDateString("en-US",{month:"short"})} ${t.getDate()} ${t.getHours().toString().padStart(2,"0")}:${t.getMinutes().toString().padStart(2,"0")}`}(n)}(t,"display")}class Debouncer{constructor(){this.timers=new Map}debounce(t,s,n=200){const o=this.timers.get(t);void 0!==o&&clearTimeout(o);const r=setTimeout(()=>{this.timers.delete(t),s()},n);this.timers.set(t,r)}cancel(t){const s=this.timers.get(t);return void 0!==s&&(clearTimeout(s),this.timers.delete(t),!0)}cancelAll(){let t=0;for(const s of this.timers.values())clearTimeout(s),t++;return this.timers.clear(),t}isPending(t){return this.timers.has(t)}getPendingCount(){return this.timers.size}}function f(t){const s=t.querySelector("tbody");return s?Array.from(s.querySelectorAll("tr")):[]}function b(t){return Array.from(t.cells)}function v(t){return t&&t.textContent?.trim()||""}function w(t,s,n){return document.createElement(t)}function y(t,...s){t.classList.add(...s)}function S(t,...s){t.classList.remove(...s)}function x(t,s,n){const o=new CustomEvent(t,{detail:s,bubbles:!0,composed:!0,cancelable:!1});return document.dispatchEvent(o)}function E(t,s,n,o){const r=new CustomEvent(s,{detail:n,bubbles:!0,composed:!0,cancelable:!1});return t.dispatchEvent(r)}function $(t){try{const s=sessionStorage.getItem(t);return s?JSON.parse(s):null}catch(s){return a(`Failed to parse JSON from sessionStorage key: ${t}`,s),null}}function C(t,s){try{const n=JSON.stringify(s);return sessionStorage.setItem(t,n),!0}catch(n){return a(`Failed to store JSON in sessionStorage key: ${t}`,n),!1}}function q(){const t=[];for(let s=0;s{let n,o=!1;const c=()=>{n&&(clearTimeout(n),n=void 0)};n=window.setTimeout(()=>{if(o)return;o=!0,this.initPromise=null,a("IndexedDB open timed out after 5000ms - attempting recovery");const n=indexedDB.deleteDatabase(this.dbName);n.onsuccess=()=>{this.init().then(t).catch(s)},n.onerror=()=>{s(new StorageError(`Database "${this.dbName}" appears corrupted. Please clear site data in browser settings.`,"init"))},n.onblocked=()=>{s(new StorageError("Cannot recover database - close all other tabs with this site and reload.","init"))}},5e3);const d=indexedDB.open(this.dbName,3);d.onerror=()=>{o||(o=!0,c(),r(`IndexedDB open error: ${d.error?.message||"unknown"}`),this.initPromise=null,s(new StorageError("Failed to open database","init",d.error)))},d.onblocked=()=>{a("IndexedDB open blocked - close other tabs with this database")},d.onsuccess=()=>{if(!o){if(o=!0,c(),this.db=d.result,!this.db.objectStoreNames.contains(T)||!this.db.objectStoreNames.contains(_)||!this.db.objectStoreNames.contains(O)){a(`Database corrupted (missing stores). Found: [${Array.from(this.db.objectStoreNames).join(", ")}]`),this.db.close(),this.db=null;const n=indexedDB.deleteDatabase(this.dbName);return n.onsuccess=()=>{this.initPromise=null,this.init().then(t).catch(s)},void(n.onerror=()=>{this.initPromise=null,s(new StorageError("Failed to delete corrupted database","init",n.error))})}this.initPromise=null,t()}},d.onupgradeneeded=t=>{const s=t.target.result,n=t.target.transaction;n&&(n.onerror=()=>{r(`Upgrade transaction error: ${n.error?.message||"unknown"}`)},n.onabort=()=>{r(`Upgrade transaction aborted: ${n.error?.message||"unknown"}`)});try{if(!s.objectStoreNames.contains(T)){const t=s.createObjectStore(T,{keyPath:null});t.createIndex("by-release","release",{unique:!1}),t.createIndex("by-service-id","serviceId",{unique:!1})}if(!s.objectStoreNames.contains(_)){const t=s.createObjectStore(_,{keyPath:null});t.createIndex("by-original-key","originalKey",{unique:!1}),t.createIndex("by-timestamp","timestamp",{unique:!1})}if(!s.objectStoreNames.contains(O)){const t=s.createObjectStore(O,{keyPath:"eventId"});t.createIndex("by-service-id","serviceId",{unique:!1}),t.createIndex("by-reset-at","resetAt",{unique:!1})}}catch(o){throw r("Error during database upgrade",o),o}}}),this.initPromise)}ensureInitialized(){if(!this.db)throw new StorageNotInitializedError("ensureInitialized");return this.db}async getStudent(t,s){const n=this.ensureInitialized(),o=A(t,s);return new Promise((t,s)=>{try{const r=n.transaction(T,"readonly"),a=r.objectStore(T).get(o);a.onsuccess=()=>{t(a.result||null)},a.onerror=()=>{s(new StorageError("Failed to get student record","getStudent",a.error))}}catch(r){s(new StorageError("Failed to get student record","getStudent",r))}})}async saveStudent(t){const s=this.ensureInitialized(),n=A(t.release,t.serviceId);return new Promise((o,r)=>{try{const a=s.transaction(T,"readwrite"),c=a.objectStore(T).put(t,n);c.onsuccess=()=>{o()},c.onerror=()=>{"QuotaExceededError"===c.error?.name?r(new StorageQuotaError("saveStudent")):r(new StorageError("Failed to save student record","saveStudent",c.error))},a.onerror=()=>{r(new StorageError("Transaction failed while saving student","saveStudent",a.error))}}catch(a){r(new StorageError("Failed to save student record","saveStudent",a))}})}async getStudentsByRelease(t){const s=this.ensureInitialized();return new Promise((n,o)=>{try{const r=s.transaction(T,"readonly").objectStore(T),a=r.index("by-release").getAll(t);a.onsuccess=()=>{n(a.result||[])},a.onerror=()=>{o(new StorageError("Failed to get students by release","getStudentsByRelease",a.error))}}catch(r){o(new StorageError("Failed to get students by release","getStudentsByRelease",r))}})}async clearAll(){const t=this.ensureInitialized();return new Promise((s,n)=>{try{const o=t.transaction([T,_,O],"readwrite"),r=o.objectStore(T),a=o.objectStore(_),c=o.objectStore(O),d=r.clear(),l=a.clear(),u=c.clear();let h=!1,p=!1,g=!1;d.onsuccess=()=>{h=!0,p&&g&&s()},l.onsuccess=()=>{p=!0,h&&g&&s()},u.onsuccess=()=>{g=!0,h&&p&&s()},d.onerror=()=>{n(new StorageError("Failed to clear students","clearAll",d.error))},l.onerror=()=>{n(new StorageError("Failed to clear backups","clearAll",l.error))},u.onerror=()=>{n(new StorageError("Failed to clear audit log","clearAll",u.error))},o.onerror=()=>{n(new StorageError("Transaction failed during clearAll","clearAll",o.error))}}catch(o){n(new StorageError("Failed to clear all data","clearAll",o))}})}async backup(t){const s=this.ensureInitialized(),n=(new Date).toISOString(),o=`backup_${n}_${t.serviceId}`,r=A(t.release,t.serviceId),a={...t,originalKey:r,timestamp:n};return new Promise((t,n)=>{try{const r=s.transaction(_,"readwrite"),c=r.objectStore(_).put(a,o);c.onsuccess=()=>{t()},c.onerror=()=>{"QuotaExceededError"===c.error?.name?n(new StorageQuotaError("backup")):n(new StorageError("Failed to create backup","backup",c.error))},r.onerror=()=>{n(new StorageError("Transaction failed during backup","backup",r.error))}}catch(r){n(new StorageError("Failed to create backup","backup",r))}})}async saveAuditEvent(t){const s=this.ensureInitialized();return new Promise((n,o)=>{try{const r=s.transaction(O,"readwrite"),a=r.objectStore(O).add(t);a.onsuccess=()=>{n()},a.onerror=()=>{o(new StorageError("Failed to save audit event","saveAuditEvent",a.error))}}catch(r){o(new StorageError("Failed to save audit event","saveAuditEvent",r))}})}close(){this.db&&(this.db.close(),this.db=null,this.initPromise=null)}}let P=null,D=null;function U(t){if(!t)throw new Error("FATAL: dbName is required for getStorageAdapter()");return P&&D!==t&&(P.close(),P=null),P||(P=new IndexedDBStorageAdapter(t),D=t),P}function j(t,s){return 0===s||function(t){return 0===t.length}(t)?"unstarted":function(t,s){if(t.length!==s)return!1;return t.every(t=>!0===t.success)}(t,s)?"complete":"incomplete"}class StorageService{constructor(t){if(!t)throw new Error("FATAL: dbName is required for StorageService");this.dbName=t,this.adapter=U(t)}async init(){try{await this.adapter.init(),this.dbName}catch(t){throw r("Failed to initialize storage service",t),t}}async loadStudentRecord(t){try{const s=await this.adapter.getStudent(t.release,t.serviceId);if(s)return t.serviceId,s;const n={schema:1,docId:t.release,release:t.release,serviceId:t.serviceId,name:t.name,attempted:0,correct:0,updated:(new Date).toISOString(),pages:{}};return t.serviceId,n}catch(s){a(`IndexedDB error, creating new record: ${s.message}`);return{schema:1,docId:t.release,release:t.release,serviceId:t.serviceId,name:t.name,attempted:0,correct:0,updated:(new Date).toISOString(),pages:{}}}}async saveStudentRecord(t){try{t.updated=(new Date).toISOString();const s=function(t){let s=0,n=0;for(const o in t){const r=t[o];if(r&&r.answers&&Array.isArray(r.answers)){const t=r.answers.filter(t=>""!==t.answer.trim());s+=t.length,n+=t.filter(t=>t.success).length}}return{attempted:s,correct:n}}(t.pages);t.attempted=s.attempted,t.correct=s.correct,await this.adapter.saveStudent(t),t.serviceId}catch(s){throw r("Failed to save student record",s),s}}updateRecordWithAnswer(t,s,n,o,r){const a=t.pages[s]||{answers:[],state:"unstarted"};for(;a.answers.length<=n;)a.answers.push({answer:"",success:!1,timestamp:(new Date).toISOString()});a.answers[n]=o;const c=(new Date).toISOString();return a.firstAttempted||(a.firstAttempted=c),a.lastAttempted=c,a.state=j(a.answers,r),{...t,pages:{...t.pages,[s]:a}}}buildCache(t){return function(t){const s={totals:{total:0,answered:0,correct:0},pages:{}};for(const[n,o]of Object.entries(t.pages)){const t=g(0,o);s.pages[n]=t,s.totals.total+=t.total,s.totals.answered+=t.answered,s.totals.correct+=t.correct}return s}(t)}async getStudentsByRelease(t){try{return await this.adapter.getStudentsByRelease(t)}catch(s){throw r("Failed to get students by release",s),s}}async clearAll(){try{await this.adapter.clearAll()}catch(t){throw r("Failed to clear all data",t),t}}async backup(t){try{await this.adapter.backup(t),t.serviceId}catch(s){a(`Failed to create backup for ${t.serviceId}`,s)}}}let B=null,F=null;function V(t){if(B&&!t)return B;if(B&&t&&F!==t)return a(`Storage service already initialized with dbName="${F}", ignoring new dbName="${t}"`),B;if(!B){if(!t)throw new Error("FATAL: dbName is required for first getStorageService() call");B=new StorageService(t),F=t}return B}const Q=new WeakMap;function K(t,s){const n=Q.get(t);let o;if(n){if(n.interactive||!s.interactive)return!0;o=n.parsed}else o=c(t),o.errors&&o.errors.length>0&&r("Quiz table has validation errors:",o.errors);const l={parsed:o,interactive:s.interactive,pageId:s.pageId};if(s.interactive){if(!s.pageId)return r("Interactive mode requires pageId option"),!1;s.pageId,l.debouncer=new Debouncer,l.inputs=[]}if(Q.set(t,l),s.interactive){const s=function(t,s){const{parsed:n,pageId:o,debouncer:c}=s;if(!o||!c)return r("Interactive mode requires pageId and debouncer"),!1;(function(t){const s=t.querySelectorAll("thead th, thead td");s[1]&&S(s[1],"qd-hidden");const n=t.querySelectorAll("tbody tr");n.forEach(t=>{const s=t.querySelectorAll("td");s[1]&&S(s[1],"qd-hidden")})})(t),Y(t);if(!$(u.SESSION))return r("No active session found"),!1;let l=$(u.CACHE);l?(l.totals.total,Object.keys(l.pages).length):l={totals:{total:0,answered:0,correct:0},pages:{}};const h=n.questions.length;l=function(t,s,n){const o=t.pages[s];if(o&&o.total>=n)return t;const r=n-(o?.total||0),a={state:o?.state||"unstarted",total:n,answered:o?.answered||0,correct:o?.correct||0,last:o?.last,answers:o?.answers,analysis:o?.analysis};return{totals:{total:t.totals.total+r,answered:t.totals.answered,correct:t.totals.correct},pages:{...t.pages,[s]:a}}}(l,o,h),C(u.CACHE,l);const p=l?.pages[o],g=p?.answers||[];g.length;const m=t.querySelector("tbody");if(!m)return r("Quiz table has no tbody element"),!1;const f=Array.from(m.querySelectorAll("tr")),b=[];n.questions.forEach((n,o)=>{const c=f[o];if(!c)return;const l=Array.from(c.querySelectorAll("td"));if(3!==l.length)return;const h=l[0],p=l[1];if(!h||!p)return;const m=g[o];m&&m.answer&&(m.answer,m.success);const v=function(t,s){const n=function(t,s){if("mcq"===t.kind){const n=(t.options||[]).map((t,s)=>({value:String(s+1),text:`${s+1}. ${t}`}));return{type:"select",className:"qd-quiz-input",placeholder:"Select an answer...",value:s?.answer||"",options:n}}return{type:"text",className:"qd-quiz-input",placeholder:"Enter value",value:s?.answer||""}}(t,s);if("select"===n.type){const t=w("select");t.className=n.className;const s=w("option");return s.value="",s.textContent=n.placeholder,s.disabled=!0,t.appendChild(s),n.options&&n.options.forEach(s=>{const n=w("option");n.value=s.value,n.textContent=s.text,t.appendChild(n)}),t.value=n.value,t}{const t=w("input");return t.type=n.type,t.className=n.className,t.placeholder=n.placeholder,t.value=n.value,t}}(n,m);b.push(v),p.textContent="",p.appendChild(v),m&&W(p,m.success);const y="SELECT"===v.tagName?"change":"input";v.addEventListener(y,()=>{!function(t,s,n,o){const{debouncer:c,pageId:l,parsed:h}=s;if(!c||!l)return;const p=h.questions[n];if(!p)return;c.debounce(`save-answer-${n}`,()=>{!async function(t,s,n,o){const{pageId:c,parsed:l,inputs:h}=s;if(!c||!h)return;const p=l.questions[n];if(!p)return;const g=$(u.SESSION);if(!g)return void r("No active session found");const m=d(p,o),f={answer:o.trim(),success:m,timestamp:(new Date).toISOString()},b=V();let v;try{v=await b.loadStudentRecord(g)}catch(A){return void a("Failed to load student record, answer not saved",A)}const w=l.questions.length,y=b.updateRecordWithAnswer(v,c,n,f,w);try{await b.saveStudentRecord(y)}catch(A){a("Failed to save student record to IndexedDB",A)}const S=b.buildCache(y);C(u.CACHE,S);const E=t.querySelector(`tbody tr:nth-child(${n+1})`);if(E){const t=E.querySelector("td:nth-child(2)");t&&W(t,m)}x("qd:answer-saved",{pageId:c,answer:f});const q=y.pages[c];q&&x("qd:state-changed",{pageId:c,state:q.state})}(t,s,n,o)},200)}(t,s,o,v.value)})}),s.inputs=b;const v=()=>{Z(t,s)},E=()=>{X(t)};document.addEventListener("qd:instructor-show-answers",v),document.addEventListener("qd:instructor-hide-answers",E);const q="true"===sessionStorage.getItem(u.INSTRUCTOR),A="true"===sessionStorage.getItem("qd/instructor/showAnswers");q&&A&&Z(t,s);const T=()=>{t.querySelectorAll("td.qd-answer-correct, td.qd-answer-incorrect").forEach(t=>{S(t,"qd-answer-correct","qd-answer-incorrect")}),X(t)};return document.addEventListener("qd:logout",T),s.cleanupInstructorListeners=()=>{document.removeEventListener("qd:instructor-show-answers",v),document.removeEventListener("qd:instructor-hide-answers",E),document.removeEventListener("qd:logout",T)},y(t,"qd-quiz-interactive"),!0}(t,l);return s?o.questions.length:r("Interactive enhancement failed"),s}return function(t){return function(t){const s=t.querySelector("colgroup");s&&s.remove()}(t),J(t),Y(t),y(t,"qd-quiz-non-interactive"),!0}(t)}function W(t,s){S(t,"qd-answer-correct","qd-answer-incorrect"),y(t,s?"qd-answer-correct":"qd-answer-incorrect")}function J(t){const s=t.querySelectorAll("thead th, thead td");s[1]&&y(s[1],"qd-hidden");t.querySelectorAll("tbody tr").forEach(t=>{const s=t.querySelectorAll("td");s[1]&&(y(s[1],"qd-hidden"),s[1].textContent="")})}function Y(t){const s=t.querySelectorAll("thead th, thead td");s[2]&&y(s[2],"qd-hidden");t.querySelectorAll("tbody tr").forEach(t=>{const s=t.querySelectorAll("td");s[2]&&y(s[2],"qd-hidden")})}function G(t){return Q.get(t)}async function Z(t,s){const{pageId:n,parsed:o}=s;if(!n)return;const a=$(u.SESSION);if(!a)return;const c=V();try{const s=await c.getStudentsByRelease(a.release);if(0===s.length)return void alert("No student data available for this release. Students need to log in and answer questions first.");const r=t.querySelector("tbody");if(!r)return;const d=Array.from(r.querySelectorAll("tr"));o.questions.forEach((t,o)=>{const r=d[o];if(!r)return;const a=Array.from(r.querySelectorAll("td"))[1];if(!a)return;const c=a.querySelector(".qd-student-answers");c&&c.remove();const l=function(t,s,n){const o=[];for(const r of t){const t=r.pages[s];if(!t||!t.answers)continue;const a=t.answers[n];a&&o.push({name:r.name,maskedServiceId:r.serviceId.slice(-4),answer:a.answer,success:a.success,formattedTimestamp:m(a.timestamp),cssClass:a.success?"qd-correct":"qd-incorrect"})}return o}(s,n,o);if(l.length>0){const t=document.createElement("div");t.className="qd-student-answers",l.forEach(s=>{const n=document.createElement("div");n.className=`qd-student-answer ${s.cssClass}`,n.innerHTML=`\n ${s.name} (${s.maskedServiceId}):\n ${s.answer}\n ${s.formattedTimestamp}\n `,t.appendChild(n)}),a.appendChild(t)}}),s.length}catch(d){r("Failed to load student answers",d)}}function X(t){t.querySelectorAll(".qd-student-answers").forEach(t=>t.remove())}function tt(t,s=16){let n=5381;for(let r=0;r{b(t).forEach((t,n)=>{if(nt(t)){const o=v(t),a=st(s,n,o);r.push({row:s,col:n,key:a})}})}),{element:t,tableId:o,editableCells:r,errors:s.length>0?s:void 0}}const rt=new WeakMap;function it(t,s){const n=ot(t);n.errors&&n.errors.length>0&&r("Analysis table has validation errors:",n.errors);const o={parsed:n,interactive:s.interactive,pageId:s.pageId};if(s.interactive){if(!s.pageId)return r("Interactive mode requires pageId option"),!1;o.debouncer=new Debouncer,o.cellKeyMap=new Map}return rt.set(t,o),s.interactive?function(t,s){const{parsed:n,pageId:o,debouncer:c,cellKeyMap:d}=s;if(!o||!c||!d)return r("Interactive mode requires pageId, debouncer, and cellKeyMap"),!1;if(!$(u.SESSION))return r("No active session found"),!1;const l=$(u.CACHE),h=l?.pages[o],p=h?.analysis,g=p?.cells||{},m=f(t);return n.editableCells.forEach(({row:t,col:n,key:o})=>{const c=m[t];if(!c)return;const l=b(c)[n];l&&(nt(l)?(d.set(l,o),g[o]&&(l.textContent=g[o]),l.contentEditable="true",y(l,"qd-editable"),l.addEventListener("input",()=>{!function(t,s,n){const{debouncer:o,pageId:c}=t;if(!o||!c)return;const d=v(s);o.debounce(`save-cell-${n}`,()=>{!async function(t,s,n){const{pageId:o,parsed:c}=t;if(!o)return;const d=$(u.SESSION);if(!d)return void r("No active session found");const l=V();let h;try{h=await l.loadStudentRecord(d)}catch(b){return void a("Failed to load student record, analysis not saved",b)}const p=h.pages[o]||{answers:[],state:"unstarted"},g=p.analysis||{tableId:c.tableId,cells:{}};g.cells[s]=n;const m=(new Date).toISOString();g.firstEdited||(g.firstEdited=m);g.lastEdited=m,p.analysis=g,h.pages[o]=p,h.updated=m;try{await l.saveStudentRecord(h)}catch(b){a("Failed to save student record to IndexedDB",b)}const f=l.buildCache(h);C(u.CACHE,f),x("qd:analysis-saved",{pageId:o,tableId:c.tableId,cellKey:s,content:n})}(t,n,d)},500)}(s,l,o)})):r(`Cell at R${t}C${n} is no longer editable`))}),y(t,"qd-analysis-interactive"),!0}(t,o):function(t){y(t,"qd-analysis-non-interactive");const s=()=>{!async function(t){const s=rt.get(t);if(!s)return void a("Cannot show student entries: table not enhanced");const n=s.pageId||function(){const t=document.body.dataset.pageId;if(t)return t;const s=window.location.pathname,n=(s.split("/").pop()||"").replace(".html","");return n||void 0}();if(!n)return void a("Cannot show student entries: page ID not found");const o=$(u.SESSION);if(!o)return void a("Cannot show student entries: no active session");const c=V();let d;try{d=await c.getStudentsByRelease(o.release)}catch(g){return void r("Failed to load students for instructor view:",g)}const l=function(t,s){const n={};return t.forEach(t=>{const o=t.pages[s];if(!o||!o.analysis)return;const{cells:r}=o.analysis,a=o.analysis.lastEdited||t.updated;Object.entries(r).forEach(([s,o])=>{n[s]||(n[s]=[]),n[s].push({serviceId:t.serviceId,name:t.name,content:o,timestamp:a})})}),n}(d,n),{editableCells:h}=s.parsed,p=f(t);h.forEach(({row:t,col:s,key:n})=>{const o=p[t];if(!o)return;const r=b(o)[s];if(!r)return;const a=function(t){const s=document.createElement("div");if(s.className="qd-student-entries",0===t.length)return s.className+=" qd-no-entries",s.textContent="(No entries yet)",s.style.cssText="color: #9ca3af; font-style: italic; font-size: 13px; padding: 8px 0;",s;const n=function(t){return[...t].sort((t,s)=>{const n=new Date(t.timestamp).getTime();return new Date(s.timestamp).getTime()-n})}(t);return n.forEach(t=>{const n=document.createElement("div");n.className="qd-entry",n.style.cssText="padding: 8px 0; border-bottom: 1px solid #e5e7eb; font-size: 13px; color: #1f2937;";const o=t.serviceId.slice(-4),r=m(t.timestamp),a=document.createElement("span");a.style.cssText="font-weight: 600; color: #374151;",a.textContent=`${t.name} (${o}) • ${r}: `;const c=document.createElement("span");c.style.cssText="white-space: pre-wrap;",c.textContent=t.content,n.appendChild(a),n.appendChild(c),s.appendChild(n)}),s.style.cssText="margin-top: 12px; padding-top: 8px; border-top: 2px solid #3b82f6;",s}(l[n]||[]);a.setAttribute("data-qd-student-entries","true");const c=r.querySelector("[data-qd-student-entries]");c&&c.remove(),r.appendChild(a)}),h.length}(t)},n=()=>{at(t)};return document.addEventListener("qd:instructor-show-answers",s),document.addEventListener("qd:instructor-hide-answers",n),!0}(t)}function at(t){t.querySelectorAll("[data-qd-student-entries]").forEach(t=>t.remove())}class EventCoordinator{constructor(){this.listeners=new Map}initialize(){this.registerLoginHandlers(),this.registerLogoutHandlers(),this.registerAnswerHandlers(),this.registerStateHandlers(),this.registerInstructorHandlers(),this.registerDataHandlers()}registerLoginHandlers(){this.addEventListener("qd:login",t=>{(async()=>{const s=t.detail;if(s.serviceId,s.name,"INSTRUCTOR"===s.serviceId)return;const n=$(u.SESSION);if(!n)return;const o=V();let r,a;try{r=await o.loadStudentRecord(n),await o.saveStudentRecord(r),a=o.buildCache(r),C(u.CACHE,a),a.totals.total}catch{C(u.CACHE,{totals:{total:0,answered:0,correct:0},pages:{}})}this.dispatchEvent("qd:cache-rebuild",{}),this.upgradeTablesAfterLogin()})()})}upgradeTablesAfterLogin(){const t=window.location.pathname,s=t.substring(t.lastIndexOf("/")+1).replace(/\.html?$/i,"");if(!s)return;if("true"===sessionStorage.getItem(u.INSTRUCTOR)){return void document.querySelectorAll("table.qd-quiz").forEach(t=>{const n=G(t);if(!n)return;n.pageId=s;t.querySelectorAll("td:nth-child(2), th:nth-child(2)").forEach(t=>{t.classList.remove("qd-hidden")});t.querySelectorAll("tbody td:nth-child(2)").forEach((t,s)=>{const o=n.parsed.questions[s];o&&t instanceof HTMLTableCellElement&&(t.textContent=o.correctAnswer)});t.querySelectorAll("td:nth-child(3), th:nth-child(3)").forEach(t=>t.classList.remove("qd-hidden"));const o=()=>{Z(t,n)};document.addEventListener("qd:instructor-show-answers",o),document.addEventListener("qd:instructor-hide-answers",()=>{X(t)});"true"===sessionStorage.getItem("qd/instructor/showAnswers")&&o()})}const n=document.querySelectorAll("table.qd-quiz");n.length>0&&(n.length,n.forEach(t=>{K(t,{interactive:!0,pageId:s})}));const o=document.querySelectorAll("table.qd-analysis");o.length>0&&(o.length,o.forEach(t=>{it(t,{interactive:!0,pageId:s})}))}registerLogoutHandlers(){this.addEventListener("qd:logout",t=>{t.detail.serviceId;document.querySelectorAll("table.qd-quiz").forEach(t=>{!function(t){const s=Q.get(t);s&&(s.interactive=!1,s.pageId=void 0,s.inputs=void 0,s.cleanupInstructorListeners?.(),s.cleanupInstructorListeners=void 0,J(t),Y(t),S(t,"qd-quiz-interactive"))}(t)});document.querySelectorAll("table.qd-analysis").forEach(t=>{!function(t){const s=rt.get(t);s&&(at(t),s.interactive&&(t.querySelectorAll(".qd-editable").forEach(t=>{t instanceof HTMLTableCellElement&&(t.contentEditable="false",t.classList.remove("qd-editable"),t.textContent="")}),t.classList.remove("qd-analysis-interactive"),s.debouncer?.cancelAll()),s.interactive=!1,s.pageId=void 0,s.debouncer=void 0,s.cellKeyMap=void 0)}(t)}),this.dispatchEvent("qd:cache-clear",{})})}registerAnswerHandlers(){this.addEventListener("qd:answer-saved",t=>{const s=t.detail;s.pageId,s.questionIndex,s.answer,s.success,this.dispatchEvent("qd:cache-update",{pageId:s.pageId})})}registerStateHandlers(){this.addEventListener("qd:state-changed",t=>{const s=t.detail;s.pageId,s.state,this.dispatchEvent("qd:badge-update",{pageId:s.pageId,state:s.state})})}registerInstructorHandlers(){this.addEventListener("qd:instructor-unlock",t=>{t.detail.unlockTime}),this.addEventListener("qd:instructor-lock",()=>{})}registerDataHandlers(){this.addEventListener("qd:data-cleared",t=>{t.detail.timestamp,this.dispatchEvent("qd:cache-clear",{})})}addEventListener(t,s){document.addEventListener(t,s);const n=this.listeners.get(t)||[];n.push(s),this.listeners.set(t,n)}dispatchEvent(t,s){const n=new CustomEvent(t,{detail:s,bubbles:!0,composed:!0});document.dispatchEvent(n)}cleanup(){for(const[t,s]of this.listeners)for(const n of s)document.removeEventListener(t,n);this.listeners.clear()}}class SessionCoordinator{constructor(){this.sessionService=new SessionService}initialize(){const t=this.sessionService.getSession();if(t){if(t.serviceId,this.sessionService.isExpired())return a("Session expired, clearing"),void this.sessionService.clearSession();this.scheduleExpiryCheck(t),this.setupActivityTracking()}}scheduleExpiryCheck(t){void 0!==this.expiryTimeoutId&&window.clearTimeout(this.expiryTimeoutId);const s=(new Date).getTime(),n=new Date(t.expiresAt).getTime()-s;n<=0?this.sessionService.clearSession():this.expiryTimeoutId=window.setTimeout(()=>{this.sessionService.clearSession()},n)}setupActivityTracking(){const t=()=>{if(!this.sessionService.getSession())return;this.sessionService.updateActivity();const t=this.sessionService.getSession();t&&this.scheduleExpiryCheck(t)};let s;const n=()=>{void 0!==s&&window.clearTimeout(s),s=window.setTimeout(()=>{t()},5e3)};["click","keydown","scroll","mousemove"].forEach(t=>{document.addEventListener(t,n,{passive:!0})})}cleanup(){void 0!==this.expiryTimeoutId&&window.clearTimeout(this.expiryTimeoutId)}getSessionService(){return this.sessionService}} +var SonarQuiz=function(t){"use strict";function s(t){if(t.length<2)return"**";if(2===t.length)return t;return t.slice(0,2)+"*".repeat(t.length-2)}function n(t){if(null===t||"object"!=typeof t)return t;const o={};for(const[r,a]of Object.entries(t))"name"!==r&&"passwordHash"!==r&&(o[r]="serviceId"!==r||"string"!=typeof a?"object"!=typeof a||null===a?a:n(a):s(a));return o}function o(t,s){}function r(t,s){if(s instanceof Error){const n={name:s.name,message:s.message};console.error(`[ERROR] ${t}`,n)}else void 0!==s?console.error(`[ERROR] ${t}`,n(s)):console.error(`[ERROR] ${t}`)}function a(t,s){void 0!==s?console.warn(`[WARN] ${t}`,n(s)):console.warn(`[WARN] ${t}`)}function c(t){const s=[],n=[];if(!t.classList.contains("qd-quiz"))return s.push('Table must have class "qd-quiz"'),{element:t,questions:n,errors:s};const o=Array.from(t.querySelectorAll("tbody tr"));return 0===o.length?(s.push("Quiz table has no data rows"),{element:t,questions:n,errors:s}):(o.forEach((t,o)=>{const r=Array.from(t.querySelectorAll("td"));if(3!==r.length)return void s.push(`Row ${o+1} has ${r.length} columns, expected 3 (Question | Answer | Detail)`);const a=r[0],c=r[1],d=r[2];if(!a||!c||!d)return;const l=a.textContent?.trim()||"";if(!l)return void s.push(`Row ${o+1} has empty question text`);const u=c.textContent?.trim()||"";if(!u)return void s.push(`Row ${o+1} has empty answer`);const h=d.querySelector("ol");if(h){const t=(p=h,Array.from(p.querySelectorAll("li")).map(t=>t.textContent?.trim()||"").filter(t=>t.length>0));if(0===t.length)return void s.push(`Row ${o+1} MCQ has no options in
      `);n.push({text:l,kind:"mcq",correctAnswer:u,options:t})}else{const t=d.textContent?.trim()||"",r=parseFloat(t);if(isNaN(r))return void s.push(`Row ${o+1} appears to be numeric but has invalid tolerance: "${t}"`);n.push({text:l,kind:"numeric",correctAnswer:u,tolerance:r})}var p}),{element:t,questions:n,errors:s.length>0?s:void 0})}function d(t,s){if(!s||""===s.trim())return!1;const n=s.trim();if("mcq"===t.kind)return n===t.correctAnswer;{const s=parseFloat(n),o=parseFloat(t.correctAnswer);if(isNaN(s)||isNaN(o))return!1;const r=t.tolerance??0;return Math.abs(s-o)<=r}}const l=18e5,u={SESSION:"qd/session",CACHE:"qd/state",INSTRUCTOR:"qd/instructor",PIN_ATTEMPTS:"qd:pin-attempts"},h=3,p=3e4;class SessionService{createSession(t,s,n){const o=new Date,r=o.toISOString(),a={serviceId:t,name:s,release:n,loginTime:r,lastActivity:r,expiresAt:new Date(o.getTime()+l).toISOString(),instructorUnlocked:!1};return this.saveSession(a),this.emitEvent("qd:login",{serviceId:t,name:s,release:n,loginTime:r}),a}getSession(){try{const t=sessionStorage.getItem(u.SESSION);if(!t)return null;const s=JSON.parse(t);return s.serviceId&&s.release&&s.expiresAt?s:(a("Invalid session data, missing required fields"),null)}catch(t){return r("Failed to parse session data",t),null}}updateActivity(){const t=this.getSession();if(!t)return;const s=new Date;t.lastActivity=s.toISOString(),t.expiresAt=new Date(s.getTime()+l).toISOString(),this.saveSession(t)}isExpired(){const t=this.getSession();return!t||function(t,s=new Date){const n=new Date(t);return!!isNaN(n.getTime())||s>=n}(t.expiresAt)}clearSession(){const t=this.getSession();sessionStorage.removeItem(u.SESSION),sessionStorage.removeItem(u.CACHE),sessionStorage.removeItem(u.INSTRUCTOR),sessionStorage.removeItem("qd/instructor/showAnswers"),t&&(t.serviceId,this.emitEvent("qd:logout",{serviceId:t.serviceId,timestamp:(new Date).toISOString()}))}unlockInstructor(){const t=this.getSession();t&&(t.instructorUnlocked=!0,t.unlockTime=(new Date).toISOString(),this.saveSession(t),this.emitEvent("qd:instructor-unlock",{timestamp:t.unlockTime}))}lockInstructor(){const t=this.getSession();t&&(t.instructorUnlocked=!1,delete t.unlockTime,this.saveSession(t),this.emitEvent("qd:instructor-lock",{timestamp:(new Date).toISOString()}))}isInstructorUnlocked(){const t=this.getSession();return!0===t?.instructorUnlocked}getCache(){try{const t=sessionStorage.getItem(u.CACHE);return t?JSON.parse(t):null}catch(t){return r("Failed to parse cache data",t),null}}saveCache(t){try{sessionStorage.setItem(u.CACHE,JSON.stringify(t))}catch(s){r("Failed to save cache",s)}}clearCache(){sessionStorage.removeItem(u.CACHE)}saveSession(t){try{sessionStorage.setItem(u.SESSION,JSON.stringify(t))}catch(s){r("Failed to save session",s)}}emitEvent(t,s){try{const n=new CustomEvent(t,{detail:s,bubbles:!0});document.dispatchEvent(n)}catch(n){r(`Failed to emit event ${t}`,n)}}}function g(t,s){const n=s.answers.length,o=s.answers.filter(t=>""!==t.answer.trim()).length,r=s.answers.filter(t=>t.success).length;return{state:s.state,total:n,answered:o,correct:r,last:s.lastAttempted,answers:s.answers,analysis:s.analysis}}function m(t){return function(t,s="display"){if(null==t)return console.warn("Invalid date provided to formatTimestamp:",t),"Invalid Date";const n="string"==typeof t?new Date(t):t;return isNaN(n.getTime())?(console.warn("Invalid date provided to formatTimestamp:",t),"Invalid Date"):"csv"===s?function(t){return t.toISOString()}(n):function(t){return`${t.toLocaleDateString("en-US",{month:"short"})} ${t.getDate()} ${t.getHours().toString().padStart(2,"0")}:${t.getMinutes().toString().padStart(2,"0")}`}(n)}(t,"display")}class Debouncer{constructor(){this.timers=new Map}debounce(t,s,n=200){const o=this.timers.get(t);void 0!==o&&clearTimeout(o);const r=setTimeout(()=>{this.timers.delete(t),s()},n);this.timers.set(t,r)}cancel(t){const s=this.timers.get(t);return void 0!==s&&(clearTimeout(s),this.timers.delete(t),!0)}cancelAll(){let t=0;for(const s of this.timers.values())clearTimeout(s),t++;return this.timers.clear(),t}isPending(t){return this.timers.has(t)}getPendingCount(){return this.timers.size}}function f(t){const s=t.querySelector("tbody");return s?Array.from(s.querySelectorAll("tr")):[]}function b(t){return Array.from(t.cells)}function y(t){return t&&t.textContent?.trim()||""}function v(t,s,n){return document.createElement(t)}function w(t,...s){t.classList.add(...s)}function S(t,...s){t.classList.remove(...s)}function x(t,s,n){const o=new CustomEvent(t,{detail:s,bubbles:!0,composed:!0,cancelable:!1});return document.dispatchEvent(o)}function E(t,s,n,o){const r=new CustomEvent(s,{detail:n,bubbles:!0,composed:!0,cancelable:!1});return t.dispatchEvent(r)}function $(t){try{const s=sessionStorage.getItem(t);return s?JSON.parse(s):null}catch(s){return a(`Failed to parse JSON from sessionStorage key: ${t}`,s),null}}function C(t,s){try{const n=JSON.stringify(s);return sessionStorage.setItem(t,n),!0}catch(n){return a(`Failed to store JSON in sessionStorage key: ${t}`,n),!1}}function q(){const t=[];for(let s=0;s{let n,o=!1;const c=()=>{n&&(clearTimeout(n),n=void 0)};n=window.setTimeout(()=>{if(o)return;o=!0,this.initPromise=null,a("IndexedDB open timed out after 5000ms - attempting recovery");const n=indexedDB.deleteDatabase(this.dbName);n.onsuccess=()=>{this.init().then(t).catch(s)},n.onerror=()=>{s(new StorageError(`Database "${this.dbName}" appears corrupted. Please clear site data in browser settings.`,"init"))},n.onblocked=()=>{s(new StorageError("Cannot recover database - close all other tabs with this site and reload.","init"))}},5e3);const d=indexedDB.open(this.dbName,3);d.onerror=()=>{o||(o=!0,c(),r(`IndexedDB open error: ${d.error?.message||"unknown"}`),this.initPromise=null,s(new StorageError("Failed to open database","init",d.error)))},d.onblocked=()=>{a("IndexedDB open blocked - close other tabs with this database")},d.onsuccess=()=>{if(!o){if(o=!0,c(),this.db=d.result,!this.db.objectStoreNames.contains(T)||!this.db.objectStoreNames.contains(O)||!this.db.objectStoreNames.contains(_)){a(`Database corrupted (missing stores). Found: [${Array.from(this.db.objectStoreNames).join(", ")}]`),this.db.close(),this.db=null;const n=indexedDB.deleteDatabase(this.dbName);return n.onsuccess=()=>{this.initPromise=null,this.init().then(t).catch(s)},void(n.onerror=()=>{this.initPromise=null,s(new StorageError("Failed to delete corrupted database","init",n.error))})}this.initPromise=null,t()}},d.onupgradeneeded=t=>{const s=t.target.result,n=t.target.transaction;n&&(n.onerror=()=>{r(`Upgrade transaction error: ${n.error?.message||"unknown"}`)},n.onabort=()=>{r(`Upgrade transaction aborted: ${n.error?.message||"unknown"}`)});try{if(!s.objectStoreNames.contains(T)){const t=s.createObjectStore(T,{keyPath:null});t.createIndex("by-release","release",{unique:!1}),t.createIndex("by-service-id","serviceId",{unique:!1})}if(!s.objectStoreNames.contains(O)){const t=s.createObjectStore(O,{keyPath:null});t.createIndex("by-original-key","originalKey",{unique:!1}),t.createIndex("by-timestamp","timestamp",{unique:!1})}if(!s.objectStoreNames.contains(_)){const t=s.createObjectStore(_,{keyPath:"eventId"});t.createIndex("by-service-id","serviceId",{unique:!1}),t.createIndex("by-reset-at","resetAt",{unique:!1})}}catch(o){throw r("Error during database upgrade",o),o}}}),this.initPromise)}ensureInitialized(){if(!this.db)throw new StorageNotInitializedError("ensureInitialized");return this.db}async getStudent(t,s){const n=this.ensureInitialized(),o=A(t,s);return new Promise((t,s)=>{try{const r=n.transaction(T,"readonly"),a=r.objectStore(T).get(o);a.onsuccess=()=>{t(a.result||null)},a.onerror=()=>{s(new StorageError("Failed to get student record","getStudent",a.error))}}catch(r){s(new StorageError("Failed to get student record","getStudent",r))}})}async saveStudent(t){const s=this.ensureInitialized(),n=A(t.release,t.serviceId);return new Promise((o,r)=>{try{const a=s.transaction(T,"readwrite"),c=a.objectStore(T).put(t,n);c.onsuccess=()=>{o()},c.onerror=()=>{"QuotaExceededError"===c.error?.name?r(new StorageQuotaError("saveStudent")):r(new StorageError("Failed to save student record","saveStudent",c.error))},a.onerror=()=>{r(new StorageError("Transaction failed while saving student","saveStudent",a.error))}}catch(a){r(new StorageError("Failed to save student record","saveStudent",a))}})}async getStudentsByRelease(t){const s=this.ensureInitialized();return new Promise((n,o)=>{try{const r=s.transaction(T,"readonly").objectStore(T),a=r.index("by-release").getAll(t);a.onsuccess=()=>{n(a.result||[])},a.onerror=()=>{o(new StorageError("Failed to get students by release","getStudentsByRelease",a.error))}}catch(r){o(new StorageError("Failed to get students by release","getStudentsByRelease",r))}})}async clearAll(){const t=this.ensureInitialized();return new Promise((s,n)=>{try{const o=t.transaction([T,O,_],"readwrite"),r=o.objectStore(T),a=o.objectStore(O),c=o.objectStore(_),d=r.clear(),l=a.clear(),u=c.clear();let h=!1,p=!1,g=!1;d.onsuccess=()=>{h=!0,p&&g&&s()},l.onsuccess=()=>{p=!0,h&&g&&s()},u.onsuccess=()=>{g=!0,h&&p&&s()},d.onerror=()=>{n(new StorageError("Failed to clear students","clearAll",d.error))},l.onerror=()=>{n(new StorageError("Failed to clear backups","clearAll",l.error))},u.onerror=()=>{n(new StorageError("Failed to clear audit log","clearAll",u.error))},o.onerror=()=>{n(new StorageError("Transaction failed during clearAll","clearAll",o.error))}}catch(o){n(new StorageError("Failed to clear all data","clearAll",o))}})}async backup(t){const s=this.ensureInitialized(),n=(new Date).toISOString(),o=`backup_${n}_${t.serviceId}`,r=A(t.release,t.serviceId),a={...t,originalKey:r,timestamp:n};return new Promise((t,n)=>{try{const r=s.transaction(O,"readwrite"),c=r.objectStore(O).put(a,o);c.onsuccess=()=>{t()},c.onerror=()=>{"QuotaExceededError"===c.error?.name?n(new StorageQuotaError("backup")):n(new StorageError("Failed to create backup","backup",c.error))},r.onerror=()=>{n(new StorageError("Transaction failed during backup","backup",r.error))}}catch(r){n(new StorageError("Failed to create backup","backup",r))}})}async saveAuditEvent(t){const s=this.ensureInitialized();return new Promise((n,o)=>{try{const r=s.transaction(_,"readwrite"),a=r.objectStore(_).add(t);a.onsuccess=()=>{n()},a.onerror=()=>{o(new StorageError("Failed to save audit event","saveAuditEvent",a.error))}}catch(r){o(new StorageError("Failed to save audit event","saveAuditEvent",r))}})}close(){this.db&&(this.db.close(),this.db=null,this.initPromise=null)}}let P=null,D=null;function U(t){if(!t)throw new Error("FATAL: dbName is required for getStorageAdapter()");return P&&D!==t&&(P.close(),P=null),P||(P=new IndexedDBStorageAdapter(t),D=t),P}function j(t,s){return 0===s||function(t){return 0===t.length}(t)?"unstarted":function(t,s){if(t.length!==s)return!1;return t.every(t=>!0===t.success)}(t,s)?"complete":"incomplete"}class StorageService{constructor(t){if(!t)throw new Error("FATAL: dbName is required for StorageService");this.dbName=t,this.adapter=U(t)}async init(){try{await this.adapter.init(),this.dbName}catch(t){throw r("Failed to initialize storage service",t),t}}async loadStudentRecord(t){try{const s=await this.adapter.getStudent(t.release,t.serviceId);if(s)return t.serviceId,s;const n={schema:1,docId:t.release,release:t.release,serviceId:t.serviceId,name:t.name,attempted:0,correct:0,updated:(new Date).toISOString(),pages:{}};return t.serviceId,n}catch(s){a(`IndexedDB error, creating new record: ${s.message}`);return{schema:1,docId:t.release,release:t.release,serviceId:t.serviceId,name:t.name,attempted:0,correct:0,updated:(new Date).toISOString(),pages:{}}}}async saveStudentRecord(t){try{t.updated=(new Date).toISOString();const s=function(t){let s=0,n=0;for(const o in t){const r=t[o];if(r&&r.answers&&Array.isArray(r.answers)){const t=r.answers.filter(t=>""!==t.answer.trim());s+=t.length,n+=t.filter(t=>t.success).length}}return{attempted:s,correct:n}}(t.pages);t.attempted=s.attempted,t.correct=s.correct,await this.adapter.saveStudent(t),t.serviceId}catch(s){throw r("Failed to save student record",s),s}}updateRecordWithAnswer(t,s,n,o,r){const a=t.pages[s]||{answers:[],state:"unstarted"};for(;a.answers.length<=n;)a.answers.push({answer:"",success:!1,timestamp:(new Date).toISOString()});a.answers[n]=o;const c=(new Date).toISOString();return a.firstAttempted||(a.firstAttempted=c),a.lastAttempted=c,a.state=j(a.answers,r),{...t,pages:{...t.pages,[s]:a}}}buildCache(t){return function(t){const s={totals:{total:0,answered:0,correct:0},pages:{}};for(const[n,o]of Object.entries(t.pages)){const t=g(0,o);s.pages[n]=t,s.totals.total+=t.total,s.totals.answered+=t.answered,s.totals.correct+=t.correct}return s}(t)}async getStudentsByRelease(t){try{return await this.adapter.getStudentsByRelease(t)}catch(s){throw r("Failed to get students by release",s),s}}async clearAll(){try{await this.adapter.clearAll()}catch(t){throw r("Failed to clear all data",t),t}}async backup(t){try{await this.adapter.backup(t),t.serviceId}catch(s){a(`Failed to create backup for ${t.serviceId}`,s)}}}let B=null,F=null;function V(t){if(B&&!t)return B;if(B&&t&&F!==t)return a(`Storage service already initialized with dbName="${F}", ignoring new dbName="${t}"`),B;if(!B){if(!t)throw new Error("FATAL: dbName is required for first getStorageService() call");B=new StorageService(t),F=t}return B}const Q=new WeakMap;function K(t,s){const n=Q.get(t);let o;if(n){if(n.interactive||!s.interactive)return!0;o=n.parsed}else o=c(t),o.errors&&o.errors.length>0&&r("Quiz table has validation errors:",o.errors);const l={parsed:o,interactive:s.interactive,pageId:s.pageId};if(s.interactive){if(!s.pageId)return r("Interactive mode requires pageId option"),!1;s.pageId,l.debouncer=new Debouncer,l.inputs=[]}if(Q.set(t,l),s.interactive){const s=function(t,s){const{parsed:n,pageId:o,debouncer:c}=s;if(!o||!c)return r("Interactive mode requires pageId and debouncer"),!1;(function(t){const s=t.querySelectorAll("thead th, thead td");s[1]&&S(s[1],"qd-hidden");const n=t.querySelectorAll("tbody tr");n.forEach(t=>{const s=t.querySelectorAll("td");s[1]&&S(s[1],"qd-hidden")})})(t),Y(t);if(!$(u.SESSION))return r("No active session found"),!1;let l=$(u.CACHE);l?(l.totals.total,Object.keys(l.pages).length):l={totals:{total:0,answered:0,correct:0},pages:{}};const h=n.questions.length;l=function(t,s,n){const o=t.pages[s];if(o&&o.total>=n)return t;const r=n-(o?.total||0),a={state:o?.state||"unstarted",total:n,answered:o?.answered||0,correct:o?.correct||0,last:o?.last,answers:o?.answers,analysis:o?.analysis};return{totals:{total:t.totals.total+r,answered:t.totals.answered,correct:t.totals.correct},pages:{...t.pages,[s]:a}}}(l,o,h),C(u.CACHE,l);const p=l?.pages[o],g=p?.answers||[];g.length;const m=t.querySelector("tbody");if(!m)return r("Quiz table has no tbody element"),!1;const f=Array.from(m.querySelectorAll("tr")),b=[];n.questions.forEach((n,o)=>{const c=f[o];if(!c)return;const l=Array.from(c.querySelectorAll("td"));if(3!==l.length)return;const h=l[0],p=l[1];if(!h||!p)return;const m=g[o];m&&m.answer&&(m.answer,m.success);const y=function(t,s){const n=function(t,s){if("mcq"===t.kind){const n=(t.options||[]).map((t,s)=>({value:String(s+1),text:`${s+1}. ${t}`}));return{type:"select",className:"qd-quiz-input",placeholder:"Select an answer...",value:s?.answer||"",options:n}}return{type:"text",className:"qd-quiz-input",placeholder:"Enter value",value:s?.answer||""}}(t,s);if("select"===n.type){const t=v("select");t.className=n.className;const s=v("option");return s.value="",s.textContent=n.placeholder,s.disabled=!0,t.appendChild(s),n.options&&n.options.forEach(s=>{const n=v("option");n.value=s.value,n.textContent=s.text,t.appendChild(n)}),t.value=n.value,t}{const t=v("input");return t.type=n.type,t.className=n.className,t.placeholder=n.placeholder,t.value=n.value,t}}(n,m);b.push(y),p.textContent="",p.appendChild(y),m&&W(p,m.success);const w="SELECT"===y.tagName?"change":"input";y.addEventListener(w,()=>{!function(t,s,n,o){const{debouncer:c,pageId:l,parsed:h}=s;if(!c||!l)return;const p=h.questions[n];if(!p)return;c.debounce(`save-answer-${n}`,()=>{!async function(t,s,n,o){const{pageId:c,parsed:l,inputs:h}=s;if(!c||!h)return;const p=l.questions[n];if(!p)return;const g=$(u.SESSION);if(!g)return void r("No active session found");const m=d(p,o),f={answer:o.trim(),success:m,timestamp:(new Date).toISOString()},b=V();let y;try{y=await b.loadStudentRecord(g)}catch(A){return void a("Failed to load student record, answer not saved",A)}const v=l.questions.length,w=b.updateRecordWithAnswer(y,c,n,f,v);try{await b.saveStudentRecord(w)}catch(A){a("Failed to save student record to IndexedDB",A)}const S=b.buildCache(w);C(u.CACHE,S);const E=t.querySelector(`tbody tr:nth-child(${n+1})`);if(E){const t=E.querySelector("td:nth-child(2)");t&&W(t,m)}x("qd:answer-saved",{pageId:c,answer:f});const q=w.pages[c];q&&x("qd:state-changed",{pageId:c,state:q.state})}(t,s,n,o)},200)}(t,s,o,y.value)})}),s.inputs=b;const y=()=>{Z(t,s)},E=()=>{X(t)};document.addEventListener("qd:instructor-show-answers",y),document.addEventListener("qd:instructor-hide-answers",E);const q="true"===sessionStorage.getItem(u.INSTRUCTOR),A="true"===sessionStorage.getItem("qd/instructor/showAnswers");q&&A&&Z(t,s);const T=()=>{t.querySelectorAll("td.qd-answer-correct, td.qd-answer-incorrect").forEach(t=>{S(t,"qd-answer-correct","qd-answer-incorrect")}),X(t)};return document.addEventListener("qd:logout",T),s.cleanupInstructorListeners=()=>{document.removeEventListener("qd:instructor-show-answers",y),document.removeEventListener("qd:instructor-hide-answers",E),document.removeEventListener("qd:logout",T)},w(t,"qd-quiz-interactive"),!0}(t,l);return s?o.questions.length:r("Interactive enhancement failed"),s}return function(t){return function(t){const s=t.querySelector("colgroup");s&&s.remove()}(t),J(t),Y(t),w(t,"qd-quiz-non-interactive"),!0}(t)}function W(t,s){S(t,"qd-answer-correct","qd-answer-incorrect"),w(t,s?"qd-answer-correct":"qd-answer-incorrect")}function J(t){const s=t.querySelectorAll("thead th, thead td");s[1]&&w(s[1],"qd-hidden");t.querySelectorAll("tbody tr").forEach(t=>{const s=t.querySelectorAll("td");s[1]&&(w(s[1],"qd-hidden"),s[1].textContent="")})}function Y(t){const s=t.querySelectorAll("thead th, thead td");s[2]&&w(s[2],"qd-hidden");t.querySelectorAll("tbody tr").forEach(t=>{const s=t.querySelectorAll("td");s[2]&&w(s[2],"qd-hidden")})}function G(t){return Q.get(t)}async function Z(t,s){const{pageId:n,parsed:o}=s;if(!n)return;const a=$(u.SESSION);if(!a)return;const c=V();try{const s=await c.getStudentsByRelease(a.release);if(0===s.length)return void alert("No student data available for this release. Students need to log in and answer questions first.");const r=t.querySelector("tbody");if(!r)return;const d=Array.from(r.querySelectorAll("tr"));o.questions.forEach((t,o)=>{const r=d[o];if(!r)return;const a=Array.from(r.querySelectorAll("td"))[1];if(!a)return;const c=a.querySelector(".qd-student-answers");c&&c.remove();const l=function(t,s,n){const o=[];for(const r of t){const t=r.pages[s];if(!t||!t.answers)continue;const a=t.answers[n];a&&o.push({name:r.name,maskedServiceId:r.serviceId.slice(-4),answer:a.answer,success:a.success,formattedTimestamp:m(a.timestamp),cssClass:a.success?"qd-correct":"qd-incorrect"})}return o}(s,n,o);if(l.length>0){const t=document.createElement("div");t.className="qd-student-answers",l.forEach(s=>{const n=document.createElement("div");n.className=`qd-student-answer ${s.cssClass}`,n.innerHTML=`\n ${s.name} (${s.maskedServiceId}):\n ${s.answer}\n ${s.formattedTimestamp}\n `,t.appendChild(n)}),a.appendChild(t)}}),s.length}catch(d){r("Failed to load student answers",d)}}function X(t){t.querySelectorAll(".qd-student-answers").forEach(t=>t.remove())}function tt(t,s=16){let n=5381;for(let r=0;r{b(t).forEach((t,n)=>{if(nt(t)){const o=y(t),a=st(s,n,o);r.push({row:s,col:n,key:a})}})}),{element:t,tableId:o,editableCells:r,errors:s.length>0?s:void 0}}const rt=new WeakMap;function it(t,s){const n=ot(t);n.errors&&n.errors.length>0&&r("Analysis table has validation errors:",n.errors);const o={parsed:n,interactive:s.interactive,pageId:s.pageId};if(s.interactive){if(!s.pageId)return r("Interactive mode requires pageId option"),!1;o.debouncer=new Debouncer,o.cellKeyMap=new Map}return rt.set(t,o),s.interactive?function(t,s){const{parsed:n,pageId:o,debouncer:c,cellKeyMap:d}=s;if(!o||!c||!d)return r("Interactive mode requires pageId, debouncer, and cellKeyMap"),!1;if(!$(u.SESSION))return r("No active session found"),!1;const l=$(u.CACHE),h=l?.pages[o],p=h?.analysis,g=p?.cells||{},m=f(t);return n.editableCells.forEach(({row:t,col:n,key:o})=>{const c=m[t];if(!c)return;const l=b(c)[n];l&&(nt(l)?(d.set(l,o),g[o]&&(l.textContent=g[o]),l.contentEditable="true",w(l,"qd-editable"),l.addEventListener("input",()=>{!function(t,s,n){const{debouncer:o,pageId:c}=t;if(!o||!c)return;const d=y(s);o.debounce(`save-cell-${n}`,()=>{!async function(t,s,n){const{pageId:o,parsed:c}=t;if(!o)return;const d=$(u.SESSION);if(!d)return void r("No active session found");const l=V();let h;try{h=await l.loadStudentRecord(d)}catch(b){return void a("Failed to load student record, analysis not saved",b)}const p=h.pages[o]||{answers:[],state:"unstarted"},g=p.analysis||{tableId:c.tableId,cells:{}};g.cells[s]=n;const m=(new Date).toISOString();g.firstEdited||(g.firstEdited=m);g.lastEdited=m,p.analysis=g,h.pages[o]=p,h.updated=m;try{await l.saveStudentRecord(h)}catch(b){a("Failed to save student record to IndexedDB",b)}const f=l.buildCache(h);C(u.CACHE,f),x("qd:analysis-saved",{pageId:o,tableId:c.tableId,cellKey:s,content:n})}(t,n,d)},500)}(s,l,o)})):r(`Cell at R${t}C${n} is no longer editable`))}),w(t,"qd-analysis-interactive"),!0}(t,o):function(t){w(t,"qd-analysis-non-interactive");const s=()=>{!async function(t){const s=rt.get(t);if(!s)return void a("Cannot show student entries: table not enhanced");const n=s.pageId||function(){const t=document.body.dataset.pageId;if(t)return t;const s=window.location.pathname,n=(s.split("/").pop()||"").replace(".html","");return n||void 0}();if(!n)return void a("Cannot show student entries: page ID not found");const o=$(u.SESSION);if(!o)return void a("Cannot show student entries: no active session");const c=V();let d;try{d=await c.getStudentsByRelease(o.release)}catch(g){return void r("Failed to load students for instructor view:",g)}const l=function(t,s){const n={};return t.forEach(t=>{const o=t.pages[s];if(!o||!o.analysis)return;const{cells:r}=o.analysis,a=o.analysis.lastEdited||t.updated;Object.entries(r).forEach(([s,o])=>{n[s]||(n[s]=[]),n[s].push({serviceId:t.serviceId,name:t.name,content:o,timestamp:a})})}),n}(d,n),{editableCells:h}=s.parsed,p=f(t);h.forEach(({row:t,col:s,key:n})=>{const o=p[t];if(!o)return;const r=b(o)[s];if(!r)return;const a=function(t){const s=document.createElement("div");if(s.className="qd-student-entries",0===t.length)return s.className+=" qd-no-entries",s.textContent="(No entries yet)",s.style.cssText="color: #9ca3af; font-style: italic; font-size: 13px; padding: 8px 0;",s;const n=function(t){return[...t].sort((t,s)=>{const n=new Date(t.timestamp).getTime();return new Date(s.timestamp).getTime()-n})}(t);return n.forEach(t=>{const n=document.createElement("div");n.className="qd-entry",n.style.cssText="padding: 8px 0; border-bottom: 1px solid #e5e7eb; font-size: 13px; color: #1f2937;";const o=t.serviceId.slice(-4),r=m(t.timestamp),a=document.createElement("span");a.style.cssText="font-weight: 600; color: #374151;",a.textContent=`${t.name} (${o}) • ${r}: `;const c=document.createElement("span");c.style.cssText="white-space: pre-wrap;",c.textContent=t.content,n.appendChild(a),n.appendChild(c),s.appendChild(n)}),s.style.cssText="margin-top: 12px; padding-top: 8px; border-top: 2px solid #3b82f6;",s}(l[n]||[]);a.setAttribute("data-qd-student-entries","true");const c=r.querySelector("[data-qd-student-entries]");c&&c.remove(),r.appendChild(a)}),h.length}(t)},n=()=>{at(t)};return document.addEventListener("qd:instructor-show-answers",s),document.addEventListener("qd:instructor-hide-answers",n),!0}(t)}function at(t){t.querySelectorAll("[data-qd-student-entries]").forEach(t=>t.remove())}class EventCoordinator{constructor(){this.listeners=new Map}initialize(){this.registerLoginHandlers(),this.registerLogoutHandlers(),this.registerAnswerHandlers(),this.registerStateHandlers(),this.registerInstructorHandlers(),this.registerDataHandlers()}registerLoginHandlers(){this.addEventListener("qd:login",t=>{(async()=>{const s=t.detail;if(s.serviceId,s.name,"INSTRUCTOR"===s.serviceId)return;const n=$(u.SESSION);if(!n)return;const o=V();let r,a;try{r=await o.loadStudentRecord(n),await o.saveStudentRecord(r),a=o.buildCache(r),C(u.CACHE,a),a.totals.total}catch{C(u.CACHE,{totals:{total:0,answered:0,correct:0},pages:{}})}this.dispatchEvent("qd:cache-rebuild",{}),this.upgradeTablesAfterLogin()})()})}upgradeTablesAfterLogin(){const t=window.location.pathname,s=t.substring(t.lastIndexOf("/")+1).replace(/\.html?$/i,"");if(!s)return;if("true"===sessionStorage.getItem(u.INSTRUCTOR)){return void document.querySelectorAll("table.qd-quiz").forEach(t=>{const n=G(t);if(!n)return;n.pageId=s;t.querySelectorAll("td:nth-child(2), th:nth-child(2)").forEach(t=>{t.classList.remove("qd-hidden")});t.querySelectorAll("tbody td:nth-child(2)").forEach((t,s)=>{const o=n.parsed.questions[s];o&&t instanceof HTMLTableCellElement&&(t.textContent=o.correctAnswer)});t.querySelectorAll("td:nth-child(3), th:nth-child(3)").forEach(t=>t.classList.remove("qd-hidden"));const o=()=>{Z(t,n)};document.addEventListener("qd:instructor-show-answers",o),document.addEventListener("qd:instructor-hide-answers",()=>{X(t)});"true"===sessionStorage.getItem("qd/instructor/showAnswers")&&o()})}const n=document.querySelectorAll("table.qd-quiz");n.length>0&&(n.length,n.forEach(t=>{K(t,{interactive:!0,pageId:s})}));const o=document.querySelectorAll("table.qd-analysis");o.length>0&&(o.length,o.forEach(t=>{it(t,{interactive:!0,pageId:s})}))}registerLogoutHandlers(){this.addEventListener("qd:logout",t=>{t.detail.serviceId;document.querySelectorAll("table.qd-quiz").forEach(t=>{!function(t){const s=Q.get(t);s&&(s.interactive=!1,s.pageId=void 0,s.inputs=void 0,s.cleanupInstructorListeners?.(),s.cleanupInstructorListeners=void 0,J(t),Y(t),S(t,"qd-quiz-interactive"))}(t)});document.querySelectorAll("table.qd-analysis").forEach(t=>{!function(t){const s=rt.get(t);s&&(at(t),s.interactive&&(t.querySelectorAll(".qd-editable").forEach(t=>{t instanceof HTMLTableCellElement&&(t.contentEditable="false",t.classList.remove("qd-editable"),t.textContent="")}),t.classList.remove("qd-analysis-interactive"),s.debouncer?.cancelAll()),s.interactive=!1,s.pageId=void 0,s.debouncer=void 0,s.cellKeyMap=void 0)}(t)}),this.dispatchEvent("qd:cache-clear",{})})}registerAnswerHandlers(){this.addEventListener("qd:answer-saved",t=>{const s=t.detail;s.pageId,s.questionIndex,s.answer,s.success,this.dispatchEvent("qd:cache-update",{pageId:s.pageId})})}registerStateHandlers(){this.addEventListener("qd:state-changed",t=>{const s=t.detail;s.pageId,s.state,this.dispatchEvent("qd:badge-update",{pageId:s.pageId,state:s.state})})}registerInstructorHandlers(){this.addEventListener("qd:instructor-unlock",t=>{t.detail.unlockTime}),this.addEventListener("qd:instructor-lock",()=>{})}registerDataHandlers(){this.addEventListener("qd:data-cleared",t=>{t.detail.timestamp,this.dispatchEvent("qd:cache-clear",{})})}addEventListener(t,s){document.addEventListener(t,s);const n=this.listeners.get(t)||[];n.push(s),this.listeners.set(t,n)}dispatchEvent(t,s){const n=new CustomEvent(t,{detail:s,bubbles:!0,composed:!0});document.dispatchEvent(n)}cleanup(){for(const[t,s]of this.listeners)for(const n of s)document.removeEventListener(t,n);this.listeners.clear()}}class SessionCoordinator{constructor(){this.sessionService=new SessionService}initialize(){const t=this.sessionService.getSession();if(t){if(t.serviceId,this.sessionService.isExpired())return a("Session expired, clearing"),void this.sessionService.clearSession();this.scheduleExpiryCheck(t),this.setupActivityTracking()}}scheduleExpiryCheck(t){void 0!==this.expiryTimeoutId&&window.clearTimeout(this.expiryTimeoutId);const s=(new Date).getTime(),n=new Date(t.expiresAt).getTime()-s;n<=0?this.sessionService.clearSession():this.expiryTimeoutId=window.setTimeout(()=>{this.sessionService.clearSession()},n)}setupActivityTracking(){const t=()=>{if(!this.sessionService.getSession())return;this.sessionService.updateActivity();const t=this.sessionService.getSession();t&&this.scheduleExpiryCheck(t)};let s;const n=()=>{void 0!==s&&window.clearTimeout(s),s=window.setTimeout(()=>{t()},5e3)};["click","keydown","scroll","mousemove"].forEach(t=>{document.addEventListener(t,n,{passive:!0})})}cleanup(){void 0!==this.expiryTimeoutId&&window.clearTimeout(this.expiryTimeoutId)}getSessionService(){return this.sessionService}} /** * @license * Copyright 2019 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */const ct=globalThis,dt=ct.ShadowRoot&&(void 0===ct.ShadyCSS||ct.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,lt=Symbol(),ut=new WeakMap;let ht=class{constructor(t,s,n){if(this._$cssResult$=!0,n!==lt)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=s}get styleSheet(){let t=this.o;const s=this.t;if(dt&&void 0===t){const n=void 0!==s&&1===s.length;n&&(t=ut.get(s)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),n&&ut.set(s,t))}return t}toString(){return this.cssText}};const pt=(t,...s)=>{const n=1===t.length?t[0]:s.reduce((s,n,o)=>s+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(n)+t[o+1],t[0]);return new ht(n,t,lt)},gt=dt?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let s="";for(const n of t.cssRules)s+=n.cssText;return(t=>new ht("string"==typeof t?t:t+"",void 0,lt))(s)})(t):t,{is:mt,defineProperty:ft,getOwnPropertyDescriptor:bt,getOwnPropertyNames:vt,getOwnPropertySymbols:wt,getPrototypeOf:yt}=Object,St=globalThis,xt=St.trustedTypes,Et=xt?xt.emptyScript:"",$t=St.reactiveElementPolyfillSupport,Ct=(t,s)=>t,It={toAttribute(t,s){switch(s){case Boolean:t=t?Et:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,s){let n=t;switch(s){case Boolean:n=null!==t;break;case Number:n=null===t?null:Number(t);break;case Object:case Array:try{n=JSON.parse(t)}catch(o){n=null}}return n}},qt=(t,s)=>!mt(t,s),At={attribute:!0,type:String,converter:It,reflect:!1,useDefault:!1,hasChanged:qt}; + */const ct=globalThis,dt=ct.ShadowRoot&&(void 0===ct.ShadyCSS||ct.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,lt=Symbol(),ut=new WeakMap;let ht=class{constructor(t,s,n){if(this._$cssResult$=!0,n!==lt)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=s}get styleSheet(){let t=this.o;const s=this.t;if(dt&&void 0===t){const n=void 0!==s&&1===s.length;n&&(t=ut.get(s)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),n&&ut.set(s,t))}return t}toString(){return this.cssText}};const pt=(t,...s)=>{const n=1===t.length?t[0]:s.reduce((s,n,o)=>s+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(n)+t[o+1],t[0]);return new ht(n,t,lt)},gt=dt?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let s="";for(const n of t.cssRules)s+=n.cssText;return(t=>new ht("string"==typeof t?t:t+"",void 0,lt))(s)})(t):t,{is:mt,defineProperty:ft,getOwnPropertyDescriptor:bt,getOwnPropertyNames:yt,getOwnPropertySymbols:vt,getPrototypeOf:wt}=Object,St=globalThis,xt=St.trustedTypes,Et=xt?xt.emptyScript:"",$t=St.reactiveElementPolyfillSupport,Ct=(t,s)=>t,qt={toAttribute(t,s){switch(s){case Boolean:t=t?Et:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,s){let n=t;switch(s){case Boolean:n=null!==t;break;case Number:n=null===t?null:Number(t);break;case Object:case Array:try{n=JSON.parse(t)}catch(o){n=null}}return n}},It=(t,s)=>!mt(t,s),At={attribute:!0,type:String,converter:qt,reflect:!1,useDefault:!1,hasChanged:It}; /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */Symbol.metadata??=Symbol("metadata"),St.litPropertyMetadata??=new WeakMap;let kt=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,s=At){if(s.state&&(s.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((s=Object.create(s)).wrapped=!0),this.elementProperties.set(t,s),!s.noAccessor){const n=Symbol(),o=this.getPropertyDescriptor(t,n,s);void 0!==o&&ft(this.prototype,t,o)}}static getPropertyDescriptor(t,s,n){const{get:o,set:r}=bt(this.prototype,t)??{get(){return this[s]},set(t){this[s]=t}};return{get:o,set(s){const a=o?.call(this);r?.call(this,s),this.requestUpdate(t,a,n)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??At}static _$Ei(){if(this.hasOwnProperty(Ct("elementProperties")))return;const t=yt(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(Ct("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(Ct("properties"))){const t=this.properties,s=[...vt(t),...wt(t)];for(const n of s)this.createProperty(n,t[n])}const t=this[Symbol.metadata];if(null!==t){const s=litPropertyMetadata.get(t);if(void 0!==s)for(const[t,n]of s)this.elementProperties.set(t,n)}this._$Eh=new Map;for(const[s,n]of this.elementProperties){const t=this._$Eu(s,n);void 0!==t&&this._$Eh.set(t,s)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const s=[];if(Array.isArray(t)){const n=new Set(t.flat(1/0).reverse());for(const t of n)s.unshift(gt(t))}else void 0!==t&&s.push(gt(t));return s}static _$Eu(t,s){const n=s.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,s=this.constructor.elementProperties;for(const n of s.keys())this.hasOwnProperty(n)&&(t.set(n,this[n]),delete this[n]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((t,s)=>{if(dt)t.adoptedStyleSheets=s.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const n of s){const s=document.createElement("style"),o=ct.litNonce;void 0!==o&&s.setAttribute("nonce",o),s.textContent=n.cssText,t.appendChild(s)}})(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,s,n){this._$AK(t,n)}_$ET(t,s){const n=this.constructor.elementProperties.get(t),o=this.constructor._$Eu(t,n);if(void 0!==o&&!0===n.reflect){const r=(void 0!==n.converter?.toAttribute?n.converter:It).toAttribute(s,n.type);this._$Em=t,null==r?this.removeAttribute(o):this.setAttribute(o,r),this._$Em=null}}_$AK(t,s){const n=this.constructor,o=n._$Eh.get(t);if(void 0!==o&&this._$Em!==o){const t=n.getPropertyOptions(o),r="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:It;this._$Em=o;const a=r.fromAttribute(s,t.type);this[o]=a??this._$Ej?.get(o)??a,this._$Em=null}}requestUpdate(t,s,n){if(void 0!==t){const o=this.constructor,r=this[t];if(n??=o.getPropertyOptions(t),!((n.hasChanged??qt)(r,s)||n.useDefault&&n.reflect&&r===this._$Ej?.get(t)&&!this.hasAttribute(o._$Eu(t,n))))return;this.C(t,s,n)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,s,{useDefault:n,reflect:o,wrapped:r},a){n&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,a??s??this[t]),!0!==r||void 0!==a)||(this._$AL.has(t)||(this.hasUpdated||n||(s=void 0),this._$AL.set(t,s)),!0===o&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(s){Promise.reject(s)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,s]of this._$Ep)this[t]=s;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[s,n]of t){const{wrapped:t}=n,o=this[s];!0!==t||this._$AL.has(s)||void 0===o||this.C(s,void 0,n,o)}}let t=!1;const s=this._$AL;try{t=this.shouldUpdate(s),t?(this.willUpdate(s),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(s)):this._$EM()}catch(n){throw t=!1,this._$EM(),n}t&&this._$AE(s)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};kt.elementStyles=[],kt.shadowRootOptions={mode:"open"},kt[Ct("elementProperties")]=new Map,kt[Ct("finalized")]=new Map,$t?.({ReactiveElement:kt}),(St.reactiveElementVersions??=[]).push("2.1.1"); + */Symbol.metadata??=Symbol("metadata"),St.litPropertyMetadata??=new WeakMap;let kt=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,s=At){if(s.state&&(s.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((s=Object.create(s)).wrapped=!0),this.elementProperties.set(t,s),!s.noAccessor){const n=Symbol(),o=this.getPropertyDescriptor(t,n,s);void 0!==o&&ft(this.prototype,t,o)}}static getPropertyDescriptor(t,s,n){const{get:o,set:r}=bt(this.prototype,t)??{get(){return this[s]},set(t){this[s]=t}};return{get:o,set(s){const a=o?.call(this);r?.call(this,s),this.requestUpdate(t,a,n)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??At}static _$Ei(){if(this.hasOwnProperty(Ct("elementProperties")))return;const t=wt(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(Ct("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(Ct("properties"))){const t=this.properties,s=[...yt(t),...vt(t)];for(const n of s)this.createProperty(n,t[n])}const t=this[Symbol.metadata];if(null!==t){const s=litPropertyMetadata.get(t);if(void 0!==s)for(const[t,n]of s)this.elementProperties.set(t,n)}this._$Eh=new Map;for(const[s,n]of this.elementProperties){const t=this._$Eu(s,n);void 0!==t&&this._$Eh.set(t,s)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const s=[];if(Array.isArray(t)){const n=new Set(t.flat(1/0).reverse());for(const t of n)s.unshift(gt(t))}else void 0!==t&&s.push(gt(t));return s}static _$Eu(t,s){const n=s.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,s=this.constructor.elementProperties;for(const n of s.keys())this.hasOwnProperty(n)&&(t.set(n,this[n]),delete this[n]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((t,s)=>{if(dt)t.adoptedStyleSheets=s.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const n of s){const s=document.createElement("style"),o=ct.litNonce;void 0!==o&&s.setAttribute("nonce",o),s.textContent=n.cssText,t.appendChild(s)}})(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,s,n){this._$AK(t,n)}_$ET(t,s){const n=this.constructor.elementProperties.get(t),o=this.constructor._$Eu(t,n);if(void 0!==o&&!0===n.reflect){const r=(void 0!==n.converter?.toAttribute?n.converter:qt).toAttribute(s,n.type);this._$Em=t,null==r?this.removeAttribute(o):this.setAttribute(o,r),this._$Em=null}}_$AK(t,s){const n=this.constructor,o=n._$Eh.get(t);if(void 0!==o&&this._$Em!==o){const t=n.getPropertyOptions(o),r="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:qt;this._$Em=o;const a=r.fromAttribute(s,t.type);this[o]=a??this._$Ej?.get(o)??a,this._$Em=null}}requestUpdate(t,s,n){if(void 0!==t){const o=this.constructor,r=this[t];if(n??=o.getPropertyOptions(t),!((n.hasChanged??It)(r,s)||n.useDefault&&n.reflect&&r===this._$Ej?.get(t)&&!this.hasAttribute(o._$Eu(t,n))))return;this.C(t,s,n)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,s,{useDefault:n,reflect:o,wrapped:r},a){n&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,a??s??this[t]),!0!==r||void 0!==a)||(this._$AL.has(t)||(this.hasUpdated||n||(s=void 0),this._$AL.set(t,s)),!0===o&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(s){Promise.reject(s)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,s]of this._$Ep)this[t]=s;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[s,n]of t){const{wrapped:t}=n,o=this[s];!0!==t||this._$AL.has(s)||void 0===o||this.C(s,void 0,n,o)}}let t=!1;const s=this._$AL;try{t=this.shouldUpdate(s),t?(this.willUpdate(s),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(s)):this._$EM()}catch(n){throw t=!1,this._$EM(),n}t&&this._$AE(s)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};kt.elementStyles=[],kt.shadowRootOptions={mode:"open"},kt[Ct("elementProperties")]=new Map,kt[Ct("finalized")]=new Map,$t?.({ReactiveElement:kt}),(St.reactiveElementVersions??=[]).push("2.1.1"); /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -const Tt=globalThis,_t=Tt.trustedTypes,Ot=_t?_t.createPolicy("lit-html",{createHTML:t=>t}):void 0,Pt="$lit$",Nt=`lit$${Math.random().toFixed(9).slice(2)}$`,Lt="?"+Nt,Dt=`<${Lt}>`,Rt=document,zt=()=>Rt.createComment(""),Mt=t=>null===t||"object"!=typeof t&&"function"!=typeof t,Ut=Array.isArray,Ht="[ \t\n\f\r]",jt=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Bt=/-->/g,Ft=/>/g,Vt=RegExp(`>|${Ht}(?:([^\\s"'>=/]+)(${Ht}*=${Ht}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),Qt=/'/g,Kt=/"/g,Wt=/^(?:script|style|textarea|title)$/i,Jt=(te=1,(t,...s)=>({_$litType$:te,strings:t,values:s})),Yt=Symbol.for("lit-noChange"),Gt=Symbol.for("lit-nothing"),Zt=new WeakMap,Xt=Rt.createTreeWalker(Rt,129);var te;function ee(t,s){if(!Ut(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==Ot?Ot.createHTML(s):s}class N{constructor({strings:t,_$litType$:s},n){let o;this.parts=[];let r=0,a=0;const c=t.length-1,d=this.parts,[l,u]=((t,s)=>{const n=t.length-1,o=[];let r,a=2===s?"":3===s?"":"",c=jt;for(let d=0;d"===l[0]?(c=r??jt,u=-1):void 0===l[1]?u=-2:(u=c.lastIndex-l[2].length,n=l[1],c=void 0===l[3]?Vt:'"'===l[3]?Kt:Qt):c===Kt||c===Qt?c=Vt:c===Bt||c===Ft?c=jt:(c=Vt,r=void 0);const p=c===Vt&&t[d+1].startsWith("/>")?" ":"";a+=c===jt?s+Dt:u>=0?(o.push(n),s.slice(0,u)+Pt+s.slice(u)+Nt+p):s+Nt+(-2===u?d:p)}return[ee(t,a+(t[n]||"")+(2===s?"":3===s?"":"")),o]})(t,s);if(this.el=N.createElement(l,n),Xt.currentNode=this.el.content,2===s||3===s){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(o=Xt.nextNode())&&d.length0){o.textContent=_t?_t.emptyScript:"";for(let n=0;nUt(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==Gt&&Mt(this._$AH)?this._$AA.nextSibling.data=t:this.T(Rt.createTextNode(t)),this._$AH=t}$(t){const{values:s,_$litType$:n}=t,o="number"==typeof n?this._$AC(t):(void 0===n.el&&(n.el=N.createElement(ee(n.h,n.h[0]),this.options)),n);if(this._$AH?._$AD===o)this._$AH.p(s);else{const t=new M(o,this),n=t.u(this.options);t.p(s),this.T(n),this._$AH=t}}_$AC(t){let s=Zt.get(t.strings);return void 0===s&&Zt.set(t.strings,s=new N(t)),s}k(t){Ut(this._$AH)||(this._$AH=[],this._$AR());const s=this._$AH;let n,o=0;for(const r of t)o===s.length?s.push(n=new R(this.O(zt()),this.O(zt()),this,this.options)):n=s[o],n._$AI(r),o++;o2||""!==n[0]||""!==n[1]?(this._$AH=Array(n.length-1).fill(new String),this.strings=n):this._$AH=Gt}_$AI(t,s=this,n,o){const r=this.strings;let a=!1;if(void 0===r)t=se(this,t,s,0),a=!Mt(t)||t!==this._$AH&&t!==Yt,a&&(this._$AH=t);else{const o=t;let c,d;for(t=r[0],c=0;c{const o=n?.renderBefore??s;let r=o._$litPart$;if(void 0===r){const t=n?.renderBefore??null;o._$litPart$=r=new R(s.insertBefore(zt(),t),t,void 0,n??{})}return r._$AI(t),r},re=globalThis; +const Tt=globalThis,Ot=Tt.trustedTypes,_t=Ot?Ot.createPolicy("lit-html",{createHTML:t=>t}):void 0,Pt="$lit$",Nt=`lit$${Math.random().toFixed(9).slice(2)}$`,Lt="?"+Nt,Dt=`<${Lt}>`,Rt=document,zt=()=>Rt.createComment(""),Mt=t=>null===t||"object"!=typeof t&&"function"!=typeof t,Ht=Array.isArray,Ut="[ \t\n\f\r]",jt=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Bt=/-->/g,Ft=/>/g,Vt=RegExp(`>|${Ut}(?:([^\\s"'>=/]+)(${Ut}*=${Ut}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),Qt=/'/g,Kt=/"/g,Wt=/^(?:script|style|textarea|title)$/i,Jt=(te=1,(t,...s)=>({_$litType$:te,strings:t,values:s})),Yt=Symbol.for("lit-noChange"),Gt=Symbol.for("lit-nothing"),Zt=new WeakMap,Xt=Rt.createTreeWalker(Rt,129);var te;function ee(t,s){if(!Ht(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==_t?_t.createHTML(s):s}class N{constructor({strings:t,_$litType$:s},n){let o;this.parts=[];let r=0,a=0;const c=t.length-1,d=this.parts,[l,u]=((t,s)=>{const n=t.length-1,o=[];let r,a=2===s?"":3===s?"":"",c=jt;for(let d=0;d"===l[0]?(c=r??jt,u=-1):void 0===l[1]?u=-2:(u=c.lastIndex-l[2].length,n=l[1],c=void 0===l[3]?Vt:'"'===l[3]?Kt:Qt):c===Kt||c===Qt?c=Vt:c===Bt||c===Ft?c=jt:(c=Vt,r=void 0);const p=c===Vt&&t[d+1].startsWith("/>")?" ":"";a+=c===jt?s+Dt:u>=0?(o.push(n),s.slice(0,u)+Pt+s.slice(u)+Nt+p):s+Nt+(-2===u?d:p)}return[ee(t,a+(t[n]||"")+(2===s?"":3===s?"":"")),o]})(t,s);if(this.el=N.createElement(l,n),Xt.currentNode=this.el.content,2===s||3===s){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(o=Xt.nextNode())&&d.length0){o.textContent=Ot?Ot.emptyScript:"";for(let n=0;nHt(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==Gt&&Mt(this._$AH)?this._$AA.nextSibling.data=t:this.T(Rt.createTextNode(t)),this._$AH=t}$(t){const{values:s,_$litType$:n}=t,o="number"==typeof n?this._$AC(t):(void 0===n.el&&(n.el=N.createElement(ee(n.h,n.h[0]),this.options)),n);if(this._$AH?._$AD===o)this._$AH.p(s);else{const t=new M(o,this),n=t.u(this.options);t.p(s),this.T(n),this._$AH=t}}_$AC(t){let s=Zt.get(t.strings);return void 0===s&&Zt.set(t.strings,s=new N(t)),s}k(t){Ht(this._$AH)||(this._$AH=[],this._$AR());const s=this._$AH;let n,o=0;for(const r of t)o===s.length?s.push(n=new R(this.O(zt()),this.O(zt()),this,this.options)):n=s[o],n._$AI(r),o++;o2||""!==n[0]||""!==n[1]?(this._$AH=Array(n.length-1).fill(new String),this.strings=n):this._$AH=Gt}_$AI(t,s=this,n,o){const r=this.strings;let a=!1;if(void 0===r)t=se(this,t,s,0),a=!Mt(t)||t!==this._$AH&&t!==Yt,a&&(this._$AH=t);else{const o=t;let c,d;for(t=r[0],c=0;c{const o=n?.renderBefore??s;let r=o._$litPart$;if(void 0===r){const t=n?.renderBefore??null;o._$litPart$=r=new R(s.insertBefore(zt(),t),t,void 0,n??{})}return r._$AI(t),r},re=globalThis; /** * @license * Copyright 2017 Google LLC @@ -25,7 +25,7 @@ const Tt=globalThis,_t=Tt.trustedTypes,Ot=_t?_t.createPolicy("lit-html",{createH * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -const ce=t=>(s,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(t,s)}):customElements.define(t,s)},de={attribute:!0,type:String,converter:It,reflect:!1,hasChanged:qt},le=(t=de,s,n)=>{const{kind:o,metadata:r}=n;let a=globalThis.litPropertyMetadata.get(r);if(void 0===a&&globalThis.litPropertyMetadata.set(r,a=new Map),"setter"===o&&((t=Object.create(t)).wrapped=!0),a.set(n.name,t),"accessor"===o){const{name:o}=n;return{set(n){const r=s.get.call(this);s.set.call(this,n),this.requestUpdate(o,r,t)},init(s){return void 0!==s&&this.C(o,void 0,t,s),s}}}if("setter"===o){const{name:o}=n;return function(n){const r=this[o];s.call(this,n),this.requestUpdate(o,r,t)}}throw Error("Unsupported decorator location: "+o)}; +const ce=t=>(s,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(t,s)}):customElements.define(t,s)},de={attribute:!0,type:String,converter:qt,reflect:!1,hasChanged:It},le=(t=de,s,n)=>{const{kind:o,metadata:r}=n;let a=globalThis.litPropertyMetadata.get(r);if(void 0===a&&globalThis.litPropertyMetadata.set(r,a=new Map),"setter"===o&&((t=Object.create(t)).wrapped=!0),a.set(n.name,t),"accessor"===o){const{name:o}=n;return{set(n){const r=s.get.call(this);s.set.call(this,n),this.requestUpdate(o,r,t)},init(s){return void 0!==s&&this.C(o,void 0,t,s),s}}}if("setter"===o){const{name:o}=n;return function(n){const r=this[o];s.call(this,n),this.requestUpdate(o,r,t)}}throw Error("Unsupported decorator location: "+o)}; /** * @license * Copyright 2017 Google LLC @@ -40,7 +40,7 @@ const ce=t=>(s,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(t,s)} * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */const pe=".wh_top_menu_and_indexterms_link",ge=".wh_publication_title .title",me="",fe="qd-status-container",be="qd-title-selector",ve="qd-instructor-hash",we="qd-db-name";function ye(t,s){const n=document.querySelector(`#${t}`);if(!n)return s;const o=n.textContent?.trim()||"";return""===o?(a(`Config element #${t} found but empty, using default: "${s}"`),s):o}function Se(){const t=function(t){const s=document.querySelector(`#${t}`);if(!s){const s=`FATAL: Required config element #${t} not found in DOM. Processing stopped.`;throw console.error(s),new Error(s)}const n=s.textContent?.trim()||"";if(""===n){const s=`FATAL: Required config element #${t} is empty. Processing stopped.`;throw console.error(s),new Error(s)}return n}(we);return{statusPanelContainer:ye(fe,pe),titleSelector:ye(be,ge),instructorHash:ye(ve,me),dbName:t}}async function xe(t){const s=(new TextEncoder).encode(t),n=await crypto.subtle.digest("SHA-256",s);return Array.from(new Uint8Array(n)).map(t=>t.toString(16).padStart(2,"0")).join("")}function Ee(t){return`${u.PIN_ATTEMPTS}:${t}`}function $e(t){const s=Ee(t),n=sessionStorage.getItem(s);if(!n)return null;try{return JSON.parse(n)}catch{return null}}function Ce(t){const s=$e(t);if(!s||!s.lockoutUntil)return{isLocked:!1,remainingMs:0};const n=new Date(s.lockoutUntil).getTime(),o=Date.now();return n>o?{isLocked:!0,remainingMs:n-o}:(Ie(t),{isLocked:!1,remainingMs:0})}function Ie(t){const n=$e(t);n&&n.attempts>0&&(n.attempts,s(t));const o=Ee(t);sessionStorage.removeItem(o)}var qe=Object.getOwnPropertyDescriptor;let Ae=class extends ie{render(){return Jt` + */const pe=".wh_top_menu_and_indexterms_link",ge=".wh_publication_title .title",me="",fe="qd-status-container",be="qd-title-selector",ye="qd-instructor-hash",ve="qd-db-name";function we(t,s){const n=document.querySelector(`#${t}`);if(!n)return s;const o=n.textContent?.trim()||"";return""===o?(a(`Config element #${t} found but empty, using default: "${s}"`),s):o}function Se(){const t=function(t){const s=document.querySelector(`#${t}`);if(!s){const s=`FATAL: Required config element #${t} not found in DOM. Processing stopped.`;throw console.error(s),new Error(s)}const n=s.textContent?.trim()||"";if(""===n){const s=`FATAL: Required config element #${t} is empty. Processing stopped.`;throw console.error(s),new Error(s)}return n}(ve);return{statusPanelContainer:we(fe,pe),titleSelector:we(be,ge),instructorHash:we(ye,me),dbName:t}}async function xe(t){const s=(new TextEncoder).encode(t),n=await crypto.subtle.digest("SHA-256",s);return Array.from(new Uint8Array(n)).map(t=>t.toString(16).padStart(2,"0")).join("")}function Ee(t){return`${u.PIN_ATTEMPTS}:${t}`}function $e(t){const s=Ee(t),n=sessionStorage.getItem(s);if(!n)return null;try{return JSON.parse(n)}catch{return null}}function Ce(t){const s=$e(t);if(!s||!s.lockoutUntil)return{isLocked:!1,remainingMs:0};const n=new Date(s.lockoutUntil).getTime(),o=Date.now();return n>o?{isLocked:!0,remainingMs:n-o}:(qe(t),{isLocked:!1,remainingMs:0})}function qe(t){const n=$e(t);n&&n.attempts>0&&(n.attempts,s(t));const o=Ee(t);sessionStorage.removeItem(o)}var Ie=Object.getOwnPropertyDescriptor;let Ae=class extends ie{render(){return Jt` i \n \n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'qd-instructor-manage': QdInstructorManage;\n }\n}\n","/**\n * PIN Reset Dialog Component\n *\n * Modal dialog for instructors to reset student PINs.\n * Shows student list with search and reset confirmation.\n * Uses qd-modal base for consistent modal behavior.\n *\n * @element qd-pin-reset-dialog\n * @fires {CustomEvent<{serviceId: string}>} qd:pin-reset - Emitted when PIN is reset\n * @fires {CustomEvent} close - Emitted when dialog is closed\n *\n * Feature: 007-lit-component-refactor\n */\n\nimport { LitElement, html, css, nothing } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport type { StudentRecord, PinResetEvent } from '../types/contracts.js';\nimport { getStorageAdapter } from '../services/storage/indexeddb.js';\nimport { resetPin } from '../services/storage/migration.js';\nimport { CONFIG_IDS } from '../config/dom-config-reader.js';\nimport './qd-modal.js';\nimport './qd-confirm-dialog.js';\n\n@customElement('qd-pin-reset-dialog')\nexport class QdPinResetDialog extends LitElement {\n /**\n * Students available for PIN reset\n */\n @property({ type: Array })\n students: StudentRecord[] = [];\n\n /**\n * Whether dialog is visible\n */\n @property({ type: Boolean, reflect: true })\n open = false;\n\n /**\n * Search filter text\n */\n @state()\n private searchText = '';\n\n /**\n * Student being confirmed for reset\n */\n @state()\n private confirmingStudent: StudentRecord | null = null;\n\n /**\n * Whether confirmation dialog is open\n */\n @state()\n private confirmDialogOpen = false;\n\n /**\n * Error message to display\n */\n @state()\n private errorMessage = '';\n\n static styles = css`\n :host {\n display: contents;\n }\n\n .pin-reset-content {\n min-width: 400px;\n max-width: 500px;\n }\n\n .search-input {\n width: 100%;\n box-sizing: border-box;\n padding: 8px 12px;\n border: 1px solid #ccc;\n border-radius: 4px;\n margin-bottom: 12px;\n font-size: 12px;\n }\n\n .search-input:focus {\n outline: none;\n border-color: #0066cc;\n box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.1);\n }\n\n .student-table-container {\n max-height: 300px;\n overflow-y: auto;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n }\n\n .student-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 12px;\n }\n\n .student-table th {\n text-align: left;\n padding: 8px 12px;\n background: #f5f5f5;\n border-bottom: 1px solid #e0e0e0;\n font-weight: 500;\n position: sticky;\n top: 0;\n }\n\n .student-table td {\n padding: 6px 12px;\n border-bottom: 1px solid #f0f0f0;\n }\n\n .student-table tbody tr:nth-child(even) {\n background: #f8f8f8;\n }\n\n .student-table tbody tr:hover {\n background: #f0f0f0;\n }\n\n .student-table tr:last-child td {\n border-bottom: none;\n }\n\n .reset-btn {\n background: #ff5722;\n color: white;\n border: none;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 10px;\n cursor: pointer;\n }\n\n .reset-btn:hover {\n background: #e64a19;\n }\n\n .empty-message {\n padding: 16px;\n text-align: center;\n color: #666;\n font-size: 12px;\n }\n\n .error-message {\n color: #d32f2f;\n font-size: 11px;\n margin-top: 8px;\n padding: 8px;\n background: #ffebee;\n border-radius: 4px;\n }\n `;\n\n /**\n * Backward compatibility: Support both 'open' and 'showModal' props\n */\n @property({ type: Boolean })\n set showModal(value: boolean) {\n this.open = value;\n }\n get showModal(): boolean {\n return this.open;\n }\n\n private get filteredStudents(): StudentRecord[] {\n if (!this.searchText.trim()) {\n return this.students;\n }\n const search = this.searchText.toLowerCase().trim();\n return this.students.filter(\n (s) => s.name.toLowerCase().includes(search) || s.serviceId.toLowerCase().includes(search),\n );\n }\n\n /**\n * Close the modal\n */\n close(): void {\n this.open = false;\n this.confirmingStudent = null;\n this.confirmDialogOpen = false;\n this.searchText = '';\n this.errorMessage = '';\n }\n\n /**\n * Show the modal\n */\n show(): void {\n this.open = true;\n }\n\n /**\n * Handle modal close from qd-modal\n */\n private handleModalClose = (): void => {\n // Don't close main modal if confirm dialog is open\n if (this.confirmDialogOpen) {\n return;\n }\n this.close();\n this.dispatchEvent(new CustomEvent('close'));\n };\n\n /**\n * Handle search input\n */\n private handleSearchInput = (e: Event): void => {\n const input = e.target as HTMLInputElement;\n this.searchText = input.value;\n };\n\n /**\n * Show confirmation dialog for PIN reset\n */\n private handleResetClick = (student: StudentRecord): void => {\n this.confirmingStudent = student;\n this.confirmDialogOpen = true;\n };\n\n /**\n * Handle confirm button click in confirmation dialog\n */\n private handleConfirmReset = (): void => {\n if (this.confirmingStudent) {\n void this.executeReset(this.confirmingStudent);\n }\n };\n\n /**\n * Handle cancel button click in confirmation dialog\n */\n private handleCancelReset = (): void => {\n this.confirmDialogOpen = false;\n this.confirmingStudent = null;\n };\n\n private async executeReset(student: StudentRecord) {\n try {\n const dbNameElement = document.getElementById(CONFIG_IDS.dbName);\n if (!dbNameElement?.textContent?.trim()) {\n throw new Error(\n `Database name not configured. Add dbName to page.`,\n );\n }\n const dbName = dbNameElement.textContent.trim();\n const storage = getStorageAdapter(dbName);\n await storage.init();\n\n // Reset the PIN\n const updatedStudent = resetPin(student);\n await storage.saveStudent(updatedStudent);\n\n // Create audit log entry\n const auditEvent: PinResetEvent = {\n eventId: crypto.randomUUID(),\n serviceId: student.serviceId,\n resetBy: 'instructor',\n resetAt: new Date().toISOString(),\n release: student.release,\n };\n await storage.saveAuditEvent(auditEvent);\n\n // Update local data\n const index = this.students.findIndex((s) => s.serviceId === student.serviceId);\n if (index >= 0) {\n this.students[index] = updatedStudent;\n this.students = [...this.students]; // Trigger reactivity\n }\n\n // Emit event\n this.dispatchEvent(\n new CustomEvent('qd:pin-reset', {\n detail: {\n serviceId: student.serviceId,\n resetBy: 'instructor',\n timestamp: new Date().toISOString(),\n },\n bubbles: true,\n composed: true,\n }),\n );\n\n // Close confirm dialog\n this.confirmDialogOpen = false;\n this.confirmingStudent = null;\n this.errorMessage = '';\n } catch (err) {\n console.error('PIN reset error:', err);\n this.errorMessage = 'Failed to reset PIN. Please try again.';\n this.confirmDialogOpen = false;\n this.confirmingStudent = null;\n }\n }\n\n override render() {\n const student = this.confirmingStudent;\n const confirmMessage = student\n ? `Reset PIN for ${student.name} (${student.serviceId})?
      They will need to create a new PIN on next login.`\n : '';\n\n // Always render qd-modal so it can properly restore position when closing\n return html`\n \n Reset Student PIN\n\n ${this.open\n ? html`\n
      \n \n\n
      \n ${this.filteredStudents.length === 0\n ? html`
      \n ${this.searchText ? 'No matching students' : 'No students found'}\n
      `\n : html`\n \n \n \n \n \n \n \n \n \n ${this.filteredStudents.map(\n (s) => html`\n \n \n \n \n \n `,\n )}\n \n
      NameService IDReset PIN
      ${s.name}${s.serviceId}\n this.handleResetClick(s)}\n >\n Reset\n \n
      \n `}\n
      \n\n ${this.errorMessage\n ? html`
      ${this.errorMessage}
      `\n : ''}\n
      \n `\n : nothing}\n \n\n \n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'qd-pin-reset-dialog': QdPinResetDialog;\n }\n}\n","/**\n * Instructor component orchestrator\n * Delegates to sub-components based on unlock state\n */\n\nimport { LitElement, html, css } from 'lit';\nimport { customElement, state } from 'lit/decorators.js';\nimport { sharedStyles } from './shared-styles.js';\nimport type { StudentRecord, SessionData } from '../../types/contracts.js';\nimport { STORAGE_KEYS } from '../../types/contracts.js';\nimport { getJSON } from '../../utils/storage-helpers.js';\nimport { SessionService } from '../../services/session.js';\nimport { getStorageService } from '../../services/storage-service.js';\nimport './qd-instructor-unlock.js';\nimport './qd-instructor-scores.js';\nimport './qd-instructor-export.js';\nimport './qd-instructor-manage.js';\nimport '../qd-build-info.js';\nimport '../qd-pin-reset-dialog.js';\n\n/**\n * Main instructor panel orchestrating all sub-components\n *\n * State management:\n * - unlocked: false → shows unlock component\n * - unlocked: true → shows scores/export/manage controls\n *\n * @fires qd:instructor-unlock - Forwarded from unlock component\n * @fires qd:data-cleared - Forwarded from manage component\n */\n@customElement('qd-instructor')\nexport class QdInstructor extends LitElement {\n static override styles = [\n sharedStyles,\n css`\n :host {\n display: none; /* Hidden by default, shown when instructor logged in */\n }\n\n :host([data-show]) {\n display: block;\n }\n `,\n ];\n\n @state()\n private unlocked = false;\n\n @state()\n private showScores = false;\n\n @state()\n private students: StudentRecord[] = [];\n\n @state()\n private showStudentAnswers = false;\n\n @state()\n private showPinReset = false;\n\n connectedCallback() {\n super.connectedCallback();\n this.updateVisibility();\n\n // Auto-unlock if instructor is already logged in\n const isInstructor = sessionStorage.getItem(STORAGE_KEYS.INSTRUCTOR) === 'true';\n if (isInstructor) {\n this.unlock();\n // Load students data for export button\n void this.loadStudents();\n }\n\n // Restore toggle state from sessionStorage\n const savedState = sessionStorage.getItem('qd/instructor/showAnswers');\n if (savedState !== null) {\n this.showStudentAnswers = savedState === 'true';\n\n // If toggle was enabled and instructor is logged in, dispatch event to show answers\n if (this.showStudentAnswers && isInstructor) {\n // Dispatch after tables are enhanced (use setTimeout to defer)\n setTimeout(() => {\n this.dispatchEvent(\n new CustomEvent('qd:instructor-show-answers', {\n bubbles: true,\n composed: true,\n }),\n );\n }, 100);\n }\n }\n\n document.addEventListener('qd:login', this.handleLoginEvent);\n document.addEventListener('qd:logout', this.handleLogoutEvent);\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n document.removeEventListener('qd:login', this.handleLoginEvent);\n document.removeEventListener('qd:logout', this.handleLogoutEvent);\n }\n\n /**\n * Update visibility based on instructor session state\n */\n private updateVisibility(): void {\n const isInstructor = sessionStorage.getItem(STORAGE_KEYS.INSTRUCTOR) === 'true';\n if (isInstructor) {\n this.setAttribute('data-show', '');\n } else {\n this.removeAttribute('data-show');\n }\n }\n\n private handleLoginEvent = (event: Event): void => {\n const customEvent = event as CustomEvent<{ role?: string }>;\n const role = customEvent.detail?.role;\n\n this.updateVisibility();\n\n // Auto-unlock if instructor logged in\n if (role === 'instructor') {\n this.unlock();\n // Load students data for export button\n void this.loadStudents();\n }\n };\n\n private handleLogoutEvent = (): void => {\n this.updateVisibility();\n this.lock();\n };\n\n /**\n * Set student data for display\n */\n setStudents(students: StudentRecord[]): void {\n this.students = students;\n }\n\n /**\n * Load students from storage for current release\n */\n private async loadStudents(): Promise {\n const session = getJSON(STORAGE_KEYS.SESSION);\n if (!session) return;\n\n try {\n const storageService = getStorageService();\n const students = await storageService.getStudentsByRelease(session.release);\n this.students = students;\n } catch (err) {\n console.error('Failed to load students:', err);\n this.students = [];\n }\n }\n\n /**\n * Unlock instructor panel (call after successful auth)\n */\n unlock(): void {\n this.unlocked = true;\n }\n\n /**\n * Lock instructor panel (call on logout)\n */\n lock(): void {\n this.unlocked = false;\n this.showScores = false;\n this.showPinReset = false;\n }\n\n private handleResetPins = async (): Promise => {\n // Load all students for current release before showing reset dialog\n const session = getJSON(STORAGE_KEYS.SESSION);\n if (!session) return;\n\n try {\n const storageService = getStorageService();\n const students = await storageService.getStudentsByRelease(session.release);\n this.students = students;\n } catch (err) {\n console.error('Failed to load students:', err);\n this.students = [];\n }\n\n this.showPinReset = true;\n };\n\n private handleClosePinReset = (): void => {\n this.showPinReset = false;\n };\n\n private handlePinReset = (): void => {\n // Forward event to parent\n this.dispatchEvent(\n new CustomEvent('qd:pin-reset', {\n bubbles: true,\n composed: true,\n }),\n );\n };\n\n private handleUnlock = (): void => {\n this.unlocked = true;\n // Forward event to parent\n this.dispatchEvent(\n new CustomEvent('qd:instructor-unlock', {\n bubbles: true,\n composed: true,\n }),\n );\n };\n\n private handleViewScores = async (): Promise => {\n // Load all students for current release before showing scores\n const session = getJSON(STORAGE_KEYS.SESSION);\n if (!session) return;\n\n try {\n const storageService = getStorageService();\n const students = await storageService.getStudentsByRelease(session.release);\n this.students = students;\n } catch (err) {\n console.error('Failed to load students:', err);\n this.students = [];\n }\n\n this.showScores = true;\n };\n\n private handleCloseScores = (): void => {\n this.showScores = false;\n };\n\n private handleDataCleared = (): void => {\n // Forward event to parent\n this.dispatchEvent(\n new CustomEvent('qd:data-cleared', {\n bubbles: true,\n composed: true,\n }),\n );\n // Refresh students list\n this.students = [];\n };\n\n private handleLogout = (): void => {\n const session = getJSON(STORAGE_KEYS.SESSION);\n\n // Clear session from storage (this will also emit qd:logout event)\n const sessionService = new SessionService();\n sessionService.clearSession();\n\n // Dispatch event for any additional listeners\n this.dispatchEvent(\n new CustomEvent('qd:logout', {\n detail: {\n serviceId: session?.serviceId || 'unknown',\n },\n bubbles: true,\n composed: true,\n }),\n );\n };\n\n private handleToggleStudentAnswers = async (e: Event): Promise => {\n const checkbox = e.target as HTMLInputElement;\n this.showStudentAnswers = checkbox.checked;\n\n // FR-004: Load student data in fresh session when toggle is enabled\n if (this.showStudentAnswers && this.students.length === 0) {\n const session = getJSON(STORAGE_KEYS.SESSION);\n if (session) {\n try {\n const storageService = getStorageService();\n const students = await storageService.getStudentsByRelease(session.release);\n this.students = students;\n } catch (err) {\n console.error('Failed to load students for toggle:', err);\n }\n }\n }\n\n // Emit event to notify table enhancers\n const eventName = this.showStudentAnswers\n ? 'qd:instructor-show-answers'\n : 'qd:instructor-hide-answers';\n\n this.dispatchEvent(\n new CustomEvent(eventName, {\n bubbles: true,\n composed: true,\n }),\n );\n\n // Persist toggle state in sessionStorage\n sessionStorage.setItem('qd/instructor/showAnswers', String(this.showStudentAnswers));\n };\n\n override render() {\n if (!this.unlocked) {\n return html`\n \n `;\n }\n\n return html`\n
      \n
      Instructor Mode
      \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n
      \n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'qd-instructor': QdInstructor;\n }\n}\n","/**\n * Component Injector\n * Injects UI components into the DOM during initialization\n */\n\nimport '../components/qd-login.js';\nimport '../components/qd-status.js';\nimport '../components/qd-instructor/qd-instructor.js';\nimport { info } from '../utils/logger.js';\n\n/**\n * Default container selectors for component injection\n */\nexport const DEFAULT_CONTAINERS = {\n /** Where to inject status panel (Oxygen WebHelp default) */\n statusPanel: '.wh_top_menu_and_indexterms_link',\n} as const;\n\n/**\n * Configuration for component injection\n */\nexport interface ComponentInjectorConfig {\n /** Selector for status panel container */\n statusPanelContainer?: string;\n /** Database name for storage service */\n dbName?: string;\n}\n\n/**\n * Inject login component into status panel container\n */\nexport function injectLoginComponent(containerSelector: string): HTMLElement | null {\n const container = document.querySelector(containerSelector);\n if (!container) {\n info(`Login component not injected: container '${containerSelector}' not found`);\n return null;\n }\n\n const login = document.createElement('qd-login');\n container.appendChild(login);\n info('Login component injected');\n return login;\n}\n\n/**\n * Inject status component into status panel container\n */\nexport function injectStatusComponent(containerSelector: string): HTMLElement | null {\n const container = document.querySelector(containerSelector);\n if (!container) {\n info(`Status component not injected: container '${containerSelector}' not found`);\n return null;\n }\n\n const status = document.createElement('qd-status');\n container.appendChild(status);\n info('Status component injected');\n return status;\n}\n\n/**\n * Inject instructor component (shown when instructor unlocked)\n */\nexport function injectInstructorComponent(containerSelector: string): HTMLElement | null {\n const container = document.querySelector(containerSelector);\n if (!container) {\n info(`Instructor component not injected: container '${containerSelector}' not found`);\n return null;\n }\n\n const instructor = document.createElement('qd-instructor');\n container.appendChild(instructor);\n info('Instructor component injected');\n return instructor;\n}\n\n/**\n * Inject all UI components based on configuration\n */\nexport function injectComponents(config: ComponentInjectorConfig = {}): void {\n const statusPanelContainer = config.statusPanelContainer || DEFAULT_CONTAINERS.statusPanel;\n\n // Always inject login component (handles showing/hiding based on session state)\n injectLoginComponent(statusPanelContainer);\n\n // Always inject status component (handles showing/hiding based on session state)\n injectStatusComponent(statusPanelContainer);\n\n // Always inject instructor component (hidden until unlocked)\n injectInstructorComponent(statusPanelContainer);\n}\n","/**\n * Home Page Badge Enhancer\n *\n * Applies R/A/G (Red/Amber/Green) badges to navigation links based on\n * page completion states. Updates badges in real-time when states change.\n *\n * Features:\n * - Queries links with class .quizPageBtn\n * - Reads completion state from SessionCache\n * - Applies CSS classes: qd-badge-red, qd-badge-amber, qd-badge-green\n * - Listens for qd:state-changed events for real-time updates\n * - Handles missing data gracefully\n *\n * Badge Colors:\n * - Red: Unstarted (no answers provided)\n * - Amber: Incomplete (some answered OR any incorrect)\n * - Green: Complete (all answered AND all correct)\n */\n\nimport type { PageId, SessionCache, CompletionState } from '../types/contracts.js';\nimport { getJSON } from '../utils/storage-helpers.js';\nimport { STORAGE_KEYS } from '../types/contracts.js';\nimport { info } from '../utils/logger.js';\n\n/**\n * CSS class constants for badges\n */\nconst BADGE_CLASSES = {\n red: 'qd-badge-red',\n amber: 'qd-badge-amber',\n green: 'qd-badge-green',\n} as const;\n\n/**\n * Map completion states to badge colors\n */\nconst STATE_TO_BADGE: Record = {\n unstarted: 'red',\n incomplete: 'amber',\n complete: 'green',\n};\n\n/**\n * Apply badge class to a link element\n *\n * @param link - Link element to apply badge to\n * @param state - Completion state\n */\nfunction applyBadge(link: HTMLElement, state: CompletionState): void {\n // Remove all existing badge classes\n Object.values(BADGE_CLASSES).forEach((className) => {\n link.classList.remove(className);\n });\n\n // Apply new badge class based on state\n const badgeColor = STATE_TO_BADGE[state];\n const badgeClass = BADGE_CLASSES[badgeColor];\n link.classList.add(badgeClass);\n}\n\n/**\n * Get completion state for a page from session cache\n *\n * @param pageId - Page ID to look up\n * @param cache - Session cache\n * @returns Completion state (defaults to 'unstarted' if not found)\n */\nfunction getPageState(pageId: PageId | null, cache: SessionCache | null): CompletionState {\n if (!pageId || !cache?.pages) {\n return 'unstarted';\n }\n\n const pageData = cache.pages[pageId];\n return pageData?.state ?? 'unstarted';\n}\n\n/**\n * Update badge for a single link\n *\n * @param link - Link element with data-page-id attribute\n */\nfunction updateLinkBadge(link: HTMLElement): void {\n const pageId = link.getAttribute('data-page-id');\n const cache = getJSON(STORAGE_KEYS.CACHE);\n const state = getPageState(pageId, cache);\n\n applyBadge(link, state);\n}\n\n/**\n * Update all badges from current session cache\n * If no session exists, remove all badges\n */\nfunction updateAllBadges(): void {\n const links = document.querySelectorAll('.quizPageBtn');\n const cache = getJSON(STORAGE_KEYS.CACHE);\n const isInstructor = sessionStorage.getItem(STORAGE_KEYS.INSTRUCTOR) === 'true';\n\n // If instructor mode OR no cache, remove all badge styling\n if (!cache || isInstructor) {\n links.forEach((link) => {\n Object.values(BADGE_CLASSES).forEach((className) => {\n link.classList.remove(className);\n });\n });\n if (isInstructor) {\n info(`Removed badge styling from ${links.length} page links (instructor mode)`);\n } else {\n info(`Removed badge styling from ${links.length} page links (no session)`);\n }\n return;\n }\n\n // Cache exists and not instructor, apply badges based on state\n links.forEach((link) => {\n updateLinkBadge(link);\n });\n\n info(`Updated ${links.length} page badges`);\n}\n\n/**\n * Handle qd:state-changed event\n *\n * @param event - Custom event with pageId and state\n */\nfunction handleStateChanged(event: Event): void {\n const customEvent = event as CustomEvent<{ pageId: PageId; state: CompletionState }>;\n const { pageId } = customEvent.detail;\n\n // Find link with matching pageId\n const link = document.querySelector(`[data-page-id=\"${pageId}\"]`);\n\n if (link && link.classList.contains('quizPageBtn')) {\n updateLinkBadge(link);\n info(`Updated badge for page ${pageId}`);\n }\n}\n\n/**\n * Handle qd:cache-rebuild event - refresh all badges after cache is ready\n */\nfunction handleCacheRebuild(): void {\n info('Cache rebuilt, refreshing all badges');\n updateAllBadges();\n}\n\n/**\n * Handle qd:logout event - remove all badge styling\n */\nfunction handleLogout(): void {\n info('Logout detected, removing all badge styling');\n const links = document.querySelectorAll('.quizPageBtn');\n\n links.forEach((link) => {\n // Remove all badge classes to revert to native button styling\n Object.values(BADGE_CLASSES).forEach((className) => {\n link.classList.remove(className);\n });\n });\n\n info(`Removed badge styling from ${links.length} page links`);\n}\n\n/**\n * Extract pageId from link href attribute\n *\n * @param link - Link element with href\n * @returns PageId extracted from href, or null if invalid\n *\n * @example\n * href=\"Pages/quiz-mcq.html\" → \"quiz-mcq\"\n * href=\"gram-1.html\" → \"gram-1\"\n */\nfunction extractPageIdFromHref(link: HTMLAnchorElement): PageId | null {\n const href = link.getAttribute('href');\n if (!href) {\n return null;\n }\n\n // Extract filename from href (last segment after /)\n const filename = href.substring(href.lastIndexOf('/') + 1);\n\n // Remove .html or .htm extension\n const pageId = filename.replace(/\\.html?$/i, '');\n\n return pageId || null;\n}\n\n/**\n * Enhance home page with R/A/G badges on navigation links\n *\n * This function:\n * 1. Queries all links with class .quizPageBtn\n * 2. Extracts pageId from href attribute and sets data-page-id\n * 3. Reads SessionCache to determine page completion states\n * 4. Applies appropriate badge CSS classes\n * 5. Sets up event listener for real-time updates\n *\n * @example\n * ```html\n * MCQ Questions\n * ```\n *\n * After enhancement:\n * - data-page-id attribute set: data-page-id=\"quiz-mcq\"\n * - Unstarted pages: class=\"quizPageBtn qd-badge-red\"\n * - Incomplete pages: class=\"quizPageBtn qd-badge-amber\"\n * - Complete pages: class=\"quizPageBtn qd-badge-green\"\n */\nexport function enhanceHomeBadges(): void {\n // Find all navigation links\n const links = document.querySelectorAll('.quizPageBtn');\n\n // Extract pageId from href and set data-page-id attribute\n links.forEach((link) => {\n const pageId = extractPageIdFromHref(link);\n if (pageId) {\n link.setAttribute('data-page-id', pageId);\n info(`Set data-page-id=\"${pageId}\" for link: ${link.textContent?.trim()}`);\n } else {\n info(`Failed to extract pageId from href: ${link.getAttribute('href')}`);\n }\n });\n\n // Apply initial badges\n updateAllBadges();\n\n // Listen for state changes and update badges in real-time\n document.addEventListener('qd:state-changed', handleStateChanged);\n\n // Listen for cache rebuild (after login) to refresh badges\n document.addEventListener('qd:cache-rebuild', handleCacheRebuild);\n\n // Listen for logout events to reset badges\n document.addEventListener('qd:logout', handleLogout);\n\n info('Home page badges enhanced with event listeners');\n}\n","/**\n * Bootstrap Module\n * Main initialization logic for the Sonar Quiz System\n */\n\nimport { info, warn } from '../utils/logger.js';\nimport { EventCoordinator } from './event-coordinator.js';\nimport { SessionCoordinator } from './session-coordinator.js';\nimport { injectComponents, type ComponentInjectorConfig } from './component-injector.js';\nimport {\n enhanceQuizTable,\n getQuizTableMetadata,\n showStudentAnswersForTable,\n hideStudentAnswersForTable,\n} from '../enhancers/quiz-table.js';\nimport { enhanceAnalysisTable } from '../enhancers/analysis-table.js';\nimport { enhanceHomeBadges } from '../enhancers/home-badges.js';\nimport { getStorageService } from '../services/storage-service.js';\nimport { getJSON, setJSON } from '../utils/storage-helpers.js';\nimport { STORAGE_KEYS, type SessionData, type SessionCache } from '../types/contracts.js';\n\n/**\n * Inject global CSS styles required by the quiz system\n * Must be called before any table enhancement\n */\nfunction injectGlobalStyles(): void {\n // Check if styles already injected\n if (document.getElementById('qd-global-styles')) {\n return;\n }\n\n const style = document.createElement('style');\n style.id = 'qd-global-styles';\n style.textContent = `\n /* Sonar Quiz System - Global Styles */\n .qd-hidden {\n display: none !important;\n }\n\n /* Quiz table interactive mode styles */\n .qd-quiz-interactive .qd-quiz-input {\n width: 100%;\n padding: 0.5rem;\n font-size: inherit;\n border: 1px solid #ccc;\n border-radius: 4px;\n }\n\n /* Ensure select elements inherit font properly */\n .qd-quiz-interactive select.qd-quiz-input {\n font-family: inherit;\n font-size: inherit;\n }\n\n /* Validation styling for answer cells */\n .qd-quiz-interactive .qd-answer-correct {\n background-color: #d4edda !important;\n border-color: #28a745 !important;\n }\n\n .qd-quiz-interactive .qd-answer-incorrect {\n background-color: #f8d7da !important;\n border-color: #dc3545 !important;\n }\n\n /* Home page badge styles (R/A/G indicators) */\n .qd-badge-red {\n border-left: 4px solid #d32f2f !important;\n background-color: #ffebee !important;\n }\n\n .qd-badge-amber {\n border-left: 4px solid #ff9800 !important;\n background-color: #fff3e0 !important;\n }\n\n .qd-badge-green {\n border-left: 4px solid #4caf50 !important;\n background-color: #e8f5e9 !important;\n }\n\n /* Instructor mode: Student answers display */\n .qd-student-answers {\n margin-top: 12px;\n padding: 8px;\n background: #f8f9fa;\n border-radius: 4px;\n border: 1px solid #dee2e6;\n }\n\n .qd-student-answer {\n font-size: 12px;\n padding: 4px 0;\n line-height: 1.4;\n }\n\n .qd-student-answer.qd-correct {\n color: #28a745;\n }\n\n .qd-student-answer.qd-incorrect {\n color: #dc3545;\n }\n\n .qd-student-name {\n font-weight: 600;\n }\n\n .qd-student-answer-text {\n margin: 0 4px;\n }\n\n .qd-timestamp {\n color: #6c757d;\n font-size: 11px;\n margin-left: 8px;\n }\n\n /* Modal error message styles (needed because qd-modal moves to body) */\n .error-message {\n color: #d32f2f;\n font-size: 12px;\n padding: 8px;\n background: #ffebee;\n border-radius: 4px;\n border-left: 3px solid #d32f2f;\n }\n `;\n\n document.head.appendChild(style);\n info('Global styles injected');\n}\n\n/**\n * Bootstrap configuration options\n */\nexport interface BootstrapConfig extends ComponentInjectorConfig {\n /** Auto-enhance quiz tables on init */\n autoEnhanceQuizTables?: boolean;\n /** Auto-enhance analysis tables on init */\n autoEnhanceAnalysisTables?: boolean;\n /** Auto-enhance home page badges on init */\n autoEnhanceHomeBadges?: boolean;\n}\n\n/**\n * Bootstrap state\n */\ninterface BootstrapState {\n initialized: boolean;\n eventCoordinator?: EventCoordinator;\n sessionCoordinator?: SessionCoordinator;\n}\n\nconst state: BootstrapState = {\n initialized: false,\n};\n\n/**\n * Initialize the Sonar Quiz System\n *\n * @param config - Bootstrap configuration\n */\nexport async function bootstrap(config: BootstrapConfig = {}): Promise {\n if (state.initialized) {\n warn('Bootstrap already initialized, skipping');\n return;\n }\n\n info('Bootstrapping Sonar Quiz System...');\n\n // 0. Inject required global styles\n injectGlobalStyles();\n\n // 1. Initialize storage service (IndexedDB)\n // dbName is REQUIRED - readDOMConfig() throws if missing\n if (!config.dbName) {\n const msg = 'FATAL: dbName not provided in bootstrap config. Processing stopped.';\n console.error(msg);\n throw new Error(msg);\n }\n const storageService = getStorageService(config.dbName);\n await storageService.init();\n\n // 2. Initialize event coordinator\n const eventCoordinator = new EventCoordinator();\n eventCoordinator.initialize();\n state.eventCoordinator = eventCoordinator;\n\n // 3. Initialize session coordinator\n const sessionCoordinator = new SessionCoordinator();\n sessionCoordinator.initialize();\n state.sessionCoordinator = sessionCoordinator;\n\n // 4. Inject UI components\n injectComponents({\n statusPanelContainer: config.statusPanelContainer,\n dbName: config.dbName,\n });\n\n // 5. Auto-enhance tables if enabled\n if (config.autoEnhanceQuizTables !== false) {\n enhanceAllQuizTables();\n }\n\n if (config.autoEnhanceAnalysisTables !== false) {\n enhanceAllAnalysisTables();\n }\n\n if (config.autoEnhanceHomeBadges !== false) {\n enhanceHomeBadgesIfPresent();\n }\n\n // 6. Check for existing session and upgrade tables if logged in\n await checkExistingSessionAndUpgradeTables();\n\n // 7. Listen for instructor login events to dynamically reveal answers\n // qd:login with role='instructor' is dispatched by qd-login component\n document.addEventListener('qd:login', (event) => {\n const detail = (event as CustomEvent<{ role?: string }>).detail;\n if (detail?.role === 'instructor') {\n info('Instructor login event received, revealing quiz answers');\n revealQuizAnswersForInstructor();\n }\n });\n\n state.initialized = true;\n info('Bootstrap complete');\n}\n\n/**\n * Enhance all quiz tables found in the document\n * Initially enhances in non-interactive mode (hide answers for security)\n * Upgraded to interactive mode after login via event coordinator\n */\nfunction enhanceAllQuizTables(): void {\n const tables = document.querySelectorAll('table.qd-quiz');\n\n if (tables.length === 0) {\n info('No quiz tables found to enhance');\n return;\n }\n\n info(`Enhancing ${tables.length} quiz table(s) in non-interactive mode...`);\n\n let enhanced = 0;\n for (const table of Array.from(tables)) {\n try {\n enhanceQuizTable(table, { interactive: false });\n enhanced++;\n } catch (err) {\n warn(`Failed to enhance quiz table: ${(err as Error).message}`);\n }\n }\n\n info(`Enhanced ${enhanced} of ${tables.length} quiz table(s) (non-interactive)`);\n}\n\n/**\n * Enhance all analysis tables found in the document\n * Initially enhances in non-interactive mode (read-only)\n * Upgraded to interactive mode after login via event coordinator\n */\nfunction enhanceAllAnalysisTables(): void {\n const tables = document.querySelectorAll('table.qd-analysis');\n\n if (tables.length === 0) {\n info('No analysis tables found to enhance');\n return;\n }\n\n info(`Enhancing ${tables.length} analysis table(s) in non-interactive mode...`);\n\n let enhanced = 0;\n for (const table of Array.from(tables)) {\n try {\n enhanceAnalysisTable(table, { interactive: false });\n enhanced++;\n } catch (err) {\n warn(`Failed to enhance analysis table: ${(err as Error).message}`);\n }\n }\n\n info(`Enhanced ${enhanced} of ${tables.length} analysis table(s) (non-interactive)`);\n}\n\n/**\n * Enhance home page badges if .quizPageBtn links exist\n */\nfunction enhanceHomeBadgesIfPresent(): void {\n const links = document.querySelectorAll('.quizPageBtn');\n\n if (links.length === 0) {\n info('No .quizPageBtn links found, skipping badge enhancement');\n return;\n }\n\n info(`Enhancing home page badges for ${links.length} link(s)...`);\n\n try {\n enhanceHomeBadges();\n info('Home page badges enhanced');\n } catch (err) {\n warn(`Failed to enhance home badges: ${(err as Error).message}`);\n }\n}\n\n/**\n * Reveal quiz answers for instructor mode\n * Called when instructor logs in (either on page load or dynamically via event)\n * Shows answer and detail columns that were hidden for security\n */\nfunction revealQuizAnswersForInstructor(): void {\n // Extract pageId from URL\n const pathname = window.location.pathname;\n const filename = pathname.substring(pathname.lastIndexOf('/') + 1);\n const pageId = filename.replace(/\\.html?$/i, '');\n\n // Reveal answer and detail columns for instructor (they're hidden by default in non-interactive mode)\n const quizTables = document.querySelectorAll('table.qd-quiz');\n\n if (quizTables.length === 0) {\n info('No quiz tables found to reveal answers for');\n return;\n }\n\n quizTables.forEach((table) => {\n // Get parsed metadata (contains correct answers)\n const metadata = getQuizTableMetadata(table);\n if (!metadata) return;\n\n // Update metadata with pageId\n metadata.pageId = pageId;\n\n // Remove qd-hidden class from answer column (column 1)\n const answerCells = table.querySelectorAll('td:nth-child(2), th:nth-child(2)');\n answerCells.forEach((cell) => {\n cell.classList.remove('qd-hidden');\n });\n\n // Restore answer text to data cells only (not header)\n const answerDataCells = table.querySelectorAll('tbody td:nth-child(2)');\n answerDataCells.forEach((cell, index) => {\n const question = metadata.parsed.questions[index];\n if (question && cell instanceof HTMLTableCellElement) {\n cell.textContent = question.correctAnswer;\n }\n });\n\n // Remove qd-hidden class from detail column (column 2)\n const detailCells = table.querySelectorAll('td:nth-child(3), th:nth-child(3)');\n detailCells.forEach((cell) => cell.classList.remove('qd-hidden'));\n\n // Set up instructor toggle event listeners (since table is non-interactive)\n const showAnswersHandler = () => {\n void showStudentAnswersForTable(table, metadata);\n };\n const hideAnswersHandler = () => {\n hideStudentAnswersForTable(table);\n };\n\n document.addEventListener('qd:instructor-show-answers', showAnswersHandler);\n document.addEventListener('qd:instructor-hide-answers', hideAnswersHandler);\n\n // Check if toggle already enabled\n const showAnswers = sessionStorage.getItem('qd/instructor/showAnswers') === 'true';\n if (showAnswers) {\n void showAnswersHandler();\n }\n });\n\n info(`Revealed answers for instructor on ${quizTables.length} quiz table(s)`);\n}\n\n/**\n * Check for existing session and upgrade tables to interactive mode\n * Called during bootstrap to handle page navigation with active session\n */\nasync function checkExistingSessionAndUpgradeTables(): Promise {\n // Check if session exists\n const session = getJSON(STORAGE_KEYS.SESSION);\n if (!session) {\n info('No existing session, tables remain in non-interactive mode');\n return;\n }\n\n // Check if instructor mode - instructors don't need interactive tables\n const isInstructor = sessionStorage.getItem(STORAGE_KEYS.INSTRUCTOR) === 'true';\n if (isInstructor) {\n info('Instructor session detected, revealing answers in non-interactive tables');\n revealQuizAnswersForInstructor();\n return;\n }\n\n info(`Existing session detected for ${session.serviceId}, upgrading tables to interactive mode`);\n\n // Load or rebuild cache from IndexedDB\n const storageService = getStorageService();\n let cache = getJSON(STORAGE_KEYS.CACHE);\n\n if (!cache) {\n info('Cache not found, rebuilding from IndexedDB...');\n try {\n const studentRecord = await storageService.loadStudentRecord(session);\n cache = storageService.buildCache(studentRecord);\n setJSON(STORAGE_KEYS.CACHE, cache);\n info(`Cache rebuilt from IndexedDB: ${cache.totals.total} total questions`);\n } catch {\n warn('Failed to rebuild cache from IndexedDB, using empty cache');\n cache = {\n totals: { total: 0, answered: 0, correct: 0 },\n pages: {},\n };\n setJSON(STORAGE_KEYS.CACHE, cache);\n }\n }\n\n // Extract pageId from URL filename\n const pathname = window.location.pathname;\n const filename = pathname.substring(pathname.lastIndexOf('/') + 1);\n const pageId = filename.replace(/\\.html?$/i, '');\n\n if (!pageId) {\n info('No pageId found, skipping table upgrade');\n return;\n }\n\n // Upgrade quiz tables to interactive mode\n const quizTables = document.querySelectorAll('table.qd-quiz');\n if (quizTables.length > 0) {\n info(`Upgrading ${quizTables.length} quiz table(s) to interactive mode...`);\n quizTables.forEach((table) => {\n enhanceQuizTable(table, { interactive: true, pageId });\n });\n }\n\n // Upgrade analysis tables to interactive mode\n const analysisTables = document.querySelectorAll('table.qd-analysis');\n if (analysisTables.length > 0) {\n info(`Upgrading ${analysisTables.length} analysis table(s) to interactive mode...`);\n analysisTables.forEach((table) => {\n enhanceAnalysisTable(table, { interactive: true, pageId });\n });\n }\n}\n\n/**\n * Cleanup bootstrap resources\n */\nexport function cleanup(): void {\n if (!state.initialized) {\n warn('Bootstrap not initialized, nothing to cleanup');\n return;\n }\n\n info('Cleaning up bootstrap resources...');\n\n state.eventCoordinator?.cleanup();\n state.sessionCoordinator?.cleanup();\n\n state.initialized = false;\n state.eventCoordinator = undefined;\n state.sessionCoordinator = undefined;\n\n info('Bootstrap cleanup complete');\n}\n\n/**\n * Check if bootstrap is initialized\n */\nexport function isInitialized(): boolean {\n return state.initialized;\n}\n\n/**\n * Get the event coordinator instance\n */\nexport function getEventCoordinator(): EventCoordinator | undefined {\n return state.eventCoordinator;\n}\n\n/**\n * Get the session coordinator instance\n */\nexport function getSessionCoordinator(): SessionCoordinator | undefined {\n return state.sessionCoordinator;\n}\n","/**\n * Sonar Quiz System - Entry Point\n *\n * Offline-first interactive quiz and analysis platform for DITA-published content.\n *\n * @packageDocumentation\n */\n\nimport { bootstrap } from './init/bootstrap.js';\nimport { info } from './utils/logger.js';\nimport { readDOMConfig } from './config/dom-config-reader.js';\n\n// Export quiz table enhancer (Phase 2.1)\nexport {\n enhanceQuizTable,\n getQuizTableMetadata,\n isQuizTableEnhanced,\n} from './enhancers/quiz-table.js';\nexport type { EnhanceQuizTableOptions } from './enhancers/quiz-table.js';\n\n// Export analysis table enhancer (Phase 2.2)\nexport {\n enhanceAnalysisTable,\n getAnalysisTableMetadata,\n isAnalysisTableEnhanced,\n} from './enhancers/analysis-table.js';\nexport type { EnhanceAnalysisTableOptions } from './enhancers/analysis-table.js';\n\n// Export types\nexport type {\n ParsedQuizTable,\n QuizQuestion,\n AnswerRecord,\n CompletionState,\n PageId,\n SessionData,\n SessionCache,\n StudentRecord,\n PageData,\n ReleaseId,\n ServiceId,\n TableId,\n CellKey,\n QuestionKind,\n} from './types/contracts.js';\n\n// Export constants\nexport { STORAGE_KEYS, SCHEMA_VERSION, SESSION_TIMEOUT_MS } from './types/contracts.js';\n\n// Export services\nexport { parseQuizTable, validateAnswer } from './services/quiz-parser.js';\nexport {\n parseAnalysisTable,\n generateTableId,\n generateCellKey,\n isCellEditable,\n} from './services/analysis-parser.js';\nexport { calculateCompletionState } from './services/state-calculator.js';\n\n// Export utilities\nexport { Debouncer } from './utils/debouncer.js';\nexport { getJSON, setJSON, clearQuizData } from './utils/storage-helpers.js';\nexport { info, warn, error } from './utils/logger.js';\n\n// Export bootstrap (Phase 3)\nexport { bootstrap, cleanup, isInitialized } from './init/bootstrap.js';\nexport type { BootstrapConfig } from './init/bootstrap.js';\n\n// Export component injector\nexport { injectComponents, DEFAULT_CONTAINERS } from './init/component-injector.js';\nexport type { ComponentInjectorConfig } from './init/component-injector.js';\n\n/**\n * Version information\n */\nexport const VERSION = '0.1.0-phase3.1';\nexport const BUILD_DATE = typeof __BUILD_DATE__ !== 'undefined' ? __BUILD_DATE__ : 'development';\n\n// Declare global for build date injection\ndeclare const __BUILD_DATE__: string;\n\n/**\n * Auto-initialize on DOMContentLoaded\n *\n * System always initializes when script loads. Configuration is read from\n * hidden DOM elements injected by DITA publishing (see dom-config-reader.ts).\n */\nif (typeof window !== 'undefined') {\n const init = () => {\n info('Auto-initializing Sonar Quiz System');\n\n // Read configuration from hidden DOM elements\n const domConfig = readDOMConfig();\n\n // Bootstrap with DOM config\n bootstrap({\n dbName: domConfig.dbName,\n statusPanelContainer: domConfig.statusPanelContainer,\n autoEnhanceQuizTables: true,\n autoEnhanceAnalysisTables: true,\n autoEnhanceHomeBadges: true,\n }).catch((err) => {\n console.error('[FATAL] Bootstrap failed:', err);\n });\n };\n\n // Initialize when DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => void init());\n } else {\n // DOM already loaded\n void init();\n }\n}\n"],"names":["maskServiceId","serviceId","length","slice","repeat","sanitize","obj","sanitized","key","value","Object","entries","info","message","data","error","Error","errorObj","name","console","warn","parseQuizTable","table","errors","questions","classList","contains","push","element","rows","Array","from","querySelectorAll","forEach","row","index","cells","questionCell","answerCell","detailCell","questionText","textContent","trim","correctAnswer","olElement","querySelector","options","ol","map","li","filter","text","kind","toleranceText","tolerance","parseFloat","isNaN","validateAnswer","question","answer","trimmedAnswer","userValue","correctValue","Math","abs","SESSION_TIMEOUT_MS","STORAGE_KEYS","SESSION","CACHE","INSTRUCTOR","PIN_ATTEMPTS","PIN_CONSTANTS","SessionService","createSession","release","now","Date","loginTime","toISOString","session","lastActivity","expiresAt","getTime","instructorUnlocked","this","saveSession","emitEvent","getSession","sessionData","sessionStorage","getItem","JSON","parse","err","updateActivity","isExpired","expiryDate","isSessionExpired","clearSession","removeItem","timestamp","unlockInstructor","unlockTime","lockInstructor","isInstructorUnlocked","getCache","cacheData","saveCache","cache","setItem","stringify","clearCache","eventName","detail","event","CustomEvent","bubbles","document","dispatchEvent","buildPageCache","_pageId","pageData","total","answers","answered","a","correct","success","state","last","lastAttempted","analysis","formatStoredTimestamp","isoString","date","format","dateObj","formatCSVTimestamp","toLocaleDateString","month","getDate","getHours","toString","padStart","getMinutes","formatDisplayTimestamp","formatTimestamp","Debouncer","constructor","timers","Map","debounce","fn","delay","existing","get","clearTimeout","timer","setTimeout","delete","set","cancel","cancelAll","count","values","clear","isPending","has","getPendingCount","size","getTableRows","tbody","getRowCells","getTextContent","createElement","tag","className","addClass","classNames","add","removeClass","remove","emitCustomEvent","composed","cancelable","dispatchEventOn","getJSON","setJSON","json","clearQuizData","keysToRemove","i","startsWith","getStorageKey","StorageError","operation","cause","super","logError","StorageNotInitializedError","StorageQuotaError","STORE_STUDENTS","STORE_BACKUPS","STORE_AUDIT_LOG","IndexedDBStorageAdapter","dbName","db","initPromise","init","Promise","resolve","reject","timeoutId","resolved","cleanup","window","logWarn","deleteReq","indexedDB","deleteDatabase","onsuccess","then","catch","onerror","onblocked","request","open","result","objectStoreNames","join","close","deleteRequest","onupgradeneeded","target","transaction","onabort","studentsStore","createObjectStore","keyPath","createIndex","unique","backupsStore","auditStore","ensureInitialized","getStudent","objectStore","saveStudent","record","put","getStudentsByRelease","store","getAll","clearAll","clearStudentsRequest","clearBackupsRequest","clearAuditRequest","studentsCleared","backupsCleared","auditCleared","backup","backupKey","originalKey","backupRecord","saveAuditEvent","storageInstance","currentDbName","getStorageAdapter","calculateCompletionState","totalQuestions","isPageUnstarted","every","isPageComplete","StorageService","adapter","loadStudentRecord","newRecord","schema","docId","attempted","updated","pages","saveStudentRecord","totals","pageId","isArray","recalculateTotalsFromPages","updateRecordWithAnswer","questionIndex","firstAttempted","buildCache","pageCache","buildCacheFromRecord","storageServiceInstance","currentServiceDbName","getStorageService","tableMetadata","WeakMap","enhanceQuizTable","parsed","interactive","metadata","debouncer","inputs","headerCells","showAnswerColumn","hideDetailColumn","keys","existingPage","delta","updatedPage","registerPageQuestions","existingAnswers","existingAnswer","input","spec","optionText","String","type","placeholder","getQuestionInputSpec","select","placeholderOption","disabled","appendChild","opt","option","createQuestionInput","applyValidationStyling","eventType","tagName","addEventListener","async","answerRecord","storageService","studentRecord","updatedRecord","saveAnswer","handleAnswerInput","showAnswersHandler","showStudentAnswersForTable","hideAnswersHandler","hideStudentAnswersForTable","isInstructor","showAnswers","logoutHandler","cell","cleanupInstructorListeners","removeEventListener","enhanceInteractive","colgroup","removeColgroup","hideAnswerColumn","enhanceNonInteractive","getQuizTableMetadata","students","alert","_question","existingDisplay","studentAnswers","student","maskedServiceId","formattedTimestamp","cssClass","formatStudentAnswersForDisplay","display","sa","answerDiv","innerHTML","hashString","hash","charCodeAt","hexHash","ceil","substring","generateTableId","firstRow","cols","generateCellKey","col","content","replace","isCellEditable","parseAnalysisTable","tableId","editableCells","rowIndex","colIndex","enhanceAnalysisTable","cellKeyMap","existingAnalysis","existingCells","rowElement","contentEditable","cellKey","analysisData","firstEdited","lastEdited","saveCellData","handleCellEdit","showHandler","bodyPageId","body","dataset","path","location","pathname","split","pop","getCurrentPageId","grouped","groupEntriesByCell","displayElement","container","style","cssText","sortedEntries","sort","b","dateA","sortByTimestamp","entry","entryDiv","last4","nameSpan","contentSpan","createStudentEntriesDisplay","setAttribute","showStudentEntriesForTable","hideHandler","hideStudentEntriesForTable","EventCoordinator","listeners","initialize","registerLoginHandlers","registerLogoutHandlers","registerAnswerHandlers","registerStateHandlers","registerInstructorHandlers","registerDataHandlers","upgradeTablesAfterLogin","lastIndexOf","HTMLTableCellElement","quizTables","analysisTables","resetQuizTableToNonInteractive","resetAnalysisTableToNonInteractive","handler","handlers","SessionCoordinator","sessionService","scheduleExpiryCheck","setupActivityTracking","expiryTimeoutId","timeUntilExpiry","activityHandler","updatedSession","activityDebounceTimeout","debouncedHandler","passive","getSessionService","t","globalThis","e","ShadowRoot","ShadyCSS","nativeShadow","Document","prototype","CSSStyleSheet","s","Symbol","o","n$3","_$cssResult$","styleSheet","replaceSync","reduce","n","c","cssRules","r","is","defineProperty","getOwnPropertyDescriptor","h","getOwnPropertyNames","getOwnPropertySymbols","getPrototypeOf","trustedTypes","l","emptyScript","p","reactiveElementPolyfillSupport","d","u","toAttribute","Boolean","fromAttribute","Number","f","attribute","converter","reflect","useDefault","hasChanged","litPropertyMetadata","HTMLElement","addInitializer","_$Ei","observedAttributes","finalize","_$Eh","createProperty","hasOwnProperty","create","wrapped","elementProperties","noAccessor","getPropertyDescriptor","call","requestUpdate","configurable","enumerable","getPropertyOptions","finalized","properties","_$Eu","elementStyles","finalizeStyles","styles","Set","flat","reverse","unshift","toLowerCase","_$Ep","isUpdatePending","hasUpdated","_$Em","_$Ev","_$ES","enableUpdating","_$AL","_$E_","addController","_$EO","renderRoot","isConnected","hostConnected","removeController","createRenderRoot","shadowRoot","attachShadow","shadowRootOptions","adoptedStyleSheets","litNonce","connectedCallback","disconnectedCallback","hostDisconnected","attributeChangedCallback","_$AK","_$ET","removeAttribute","_$Ej","hasAttribute","C","_$EP","_$Eq","scheduleUpdate","performUpdate","shouldUpdate","willUpdate","hostUpdate","update","_$EM","_$AE","hostUpdated","firstUpdated","updateComplete","getUpdateComplete","y","mode","ReactiveElement","reactiveElementVersions","createPolicy","createHTML","random","toFixed","createComment","v","_","m","RegExp","g","$","x","_$litType$","strings","T","for","E","A","createTreeWalker","P","N","parts","lastIndex","exec","test","V","el","currentNode","firstChild","replaceWith","childNodes","nextNode","nodeType","hasAttributes","getAttributeNames","endsWith","getAttribute","ctor","H","I","L","k","append","indexOf","S","_$Co","_$Cl","_$litDirective$","_$AO","_$AT","_$AS","M","_$AV","_$AN","_$AD","_$AM","parentNode","_$AU","creationScope","importNode","R","nextSibling","z","_$AI","_$Cv","_$AH","_$AA","_$AB","startNode","endNode","_$AR","iterator","O","insertBefore","createTextNode","_$AC","_$AP","setConnected","fill","j","arguments","toggleAttribute","capture","once","handleEvent","host","litHtmlPolyfillSupport","litHtmlVersions","B","renderBefore","_$litPart$","renderOptions","_$Do","render","_$litElement$","litElementHydrateSupport","LitElement","litElementPolyfillSupport","litElementVersions","customElements","define","DEFAULT_CONFIG","CONFIG_IDS","readConfigElement","elementId","defaultValue","readDOMConfig","msg","readRequiredConfigElement","statusPanelContainer","titleSelector","instructorHash","hashPin","pin","TextEncoder","encode","hashBuffer","crypto","subtle","digest","Uint8Array","getAttemptKey","getAttemptState","checkLockout","lockoutUntil","isLocked","remainingMs","lockoutTime","clearAttemptState","attempts","QdBuildInfo","html","css","__decorateClass","customElement","MODAL_STATE_KEY","getCurrentModal","setCurrentModal","modal","QdModal","closable","previouslyFocused","originalParent","originalNextSibling","isInBody","handleKeyDown","emitCloseEvent","handleBackdropClick","handleCloseClick","stopPropagation","changedProperties","handleOpen","handleClose","moveToBody","restorePosition","show","currentModal","activeElement","requestAnimationFrame","focusFirstElement","focus","slot","assignedElements","flatten","focusable","matches","closeBtn","property","QdPasswordModal","title","password","handleModalClose","handleInput","handleSubmit","preventDefault","handleCancel","changedProps","passwordInput","nothing","Reflect","decorate","_$Ct","_$Ci","it","directiveName","_t","raw","resultType","QdConfirmDialog","confirmText","cancelText","destructive","handleConfirm","unsafeHTML","QdLogin","showInstructorModal","instructorError","errorMessage","isSubmitting","lockoutSeconds","showPinConfirmation","lockoutInterval","handleLogoutEvent","clearInterval","updateVisibility","handleInstructorPasswordSubmit","handleInstructorLogin","handleInstructorModalClose","handlePinConfirmationDismiss","handleStudentLogin","handleNameInput","handleServiceIdInput","handlePinInput","isValid","openInstructorModal","sanitizePinInput","validateStudentForm","getRelease","selectorElement","getElementById","selector","titleElement","lockout","startLockoutCountdown","dbNameElement","storage","existingStudent","pinHash","newStudent","pinCreatedAt","showPinStoredConfirmation","completeLogin","hasPinSet","updatedStudent","completePinSetup","storedHash","constantTimeCompare","verifyPin","lastAttempt","recordFailedAttempt","remaining","max","getRemainingAttempts","lockoutMs","setInterval","role","hashPassword","getExpectedHash","hashElement","passwordHash","expectedHash","QdStatus","percentage","statusColor","handleStateChanged","loadCache","handleLogin","handleCacheRebuild","handleLogout","calculatePercentage","calculateStatusColor","round","calculateStatusIndicator","sharedStyles","RateLimiter","failureCount","attempt","recordFailure","delays","min","reset","getRemainingSeconds","isLockedOut","PASSWORD_HASH_ELEMENT_ID","QdInstructorUnlock","remainingSeconds","rateLimiter","handlePasswordInput","startCountdown","errorMsg","getInstructorPasswordHash","actualHash","valid","encoder","aBuffer","bBuffer","importKey","signature","sign","expectedKey","expectedSignature","byteLength","sigView","expView","countdownInterval","QdScoresModal","renderScoresTable","sortedStudents","localeCompare","renderStudentRow","summary","calculateSummary","getScoreClass","idx","QdInstructorScores","showModal","QdInstructorExport","handleExport","csv","generateCSV","blob","Blob","url","URL","createObjectURL","link","href","download","click","removeChild","revokeObjectURL","escapeCSVField","field","str","includes","hasData","some","tooltip","QdInstructorManage","showConfirmDialog","modalContainer","handleClearRequest","handleCancelClear","handleConfirmInput","handleConfirmClear","removeModalFromBody","renderModalToBody","renderConfirmDialog","currentTarget","QdPinResetDialog","searchText","confirmingStudent","confirmDialogOpen","handleSearchInput","handleResetClick","handleConfirmReset","executeReset","handleCancelReset","filteredStudents","search","pinResetAt","auditEvent","eventId","randomUUID","resetBy","resetAt","findIndex","confirmMessage","QdInstructor","unlocked","showScores","showStudentAnswers","showPinReset","handleLoginEvent","customEvent","unlock","loadStudents","lock","handleResetPins","handleClosePinReset","handlePinReset","handleUnlock","handleViewScores","handleCloseScores","handleDataCleared","handleToggleStudentAnswers","checkbox","checked","savedState","setStudents","DEFAULT_CONTAINERS","statusPanel","injectComponents","config","containerSelector","login","injectLoginComponent","status","injectStatusComponent","instructor","injectInstructorComponent","BADGE_CLASSES","red","amber","green","STATE_TO_BADGE","unstarted","incomplete","complete","updateLinkBadge","getPageState","badgeClass","applyBadge","updateAllBadges","links","initialized","bootstrap","id","head","injectGlobalStyles","eventCoordinator","sessionCoordinator","autoEnhanceQuizTables","tables","enhanceAllQuizTables","autoEnhanceAnalysisTables","enhanceAllAnalysisTables","autoEnhanceHomeBadges","extractPageIdFromHref","enhanceHomeBadgesIfPresent","revealQuizAnswersForInstructor","checkExistingSessionAndUpgradeTables","domConfig","readyState"],"mappings":"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","x_google_ignoreList":[21,22,23,24,25,26,27,34,35,36,37]} \ No newline at end of file +{"version":3,"file":"sonar-quiz.iife.js","sources":["../src/utils/logger.ts","../src/services/quiz-parser.ts","../src/types/contracts.ts","../src/services/session.ts","../src/utils/calculation-helpers.ts","../src/utils/date-helpers.ts","../src/utils/debouncer.ts","../src/utils/dom-helpers.ts","../src/utils/event-helpers.ts","../src/utils/storage-helpers.ts","../src/services/storage/adapter-utils.ts","../src/services/storage/indexeddb.ts","../src/services/state-calculator.ts","../src/services/storage-service.ts","../src/enhancers/quiz-table.ts","../src/services/question-input.ts","../src/services/answer-display.ts","../src/services/analysis-parser.ts","../src/enhancers/analysis-table.ts","../src/init/event-coordinator.ts","../src/init/session-coordinator.ts","../node_modules/@lit/reactive-element/css-tag.js","../node_modules/@lit/reactive-element/reactive-element.js","../node_modules/lit-html/lit-html.js","../node_modules/lit-element/lit-element.js","../node_modules/@lit/reactive-element/decorators/custom-element.js","../node_modules/@lit/reactive-element/decorators/property.js","../node_modules/@lit/reactive-element/decorators/state.js","../src/config/dom-config-reader.ts","../src/services/auth/pin-service.ts","../src/services/auth/rate-limiter.ts","../src/components/qd-build-info.ts","../src/components/qd-modal.ts","../src/components/qd-password-modal.ts","../node_modules/@lit/reactive-element/decorators/query.js","../node_modules/@lit/reactive-element/decorators/base.js","../node_modules/lit-html/directive.js","../node_modules/lit-html/directives/unsafe-html.js","../src/components/qd-confirm-dialog.ts","../src/components/qd-help-trigger.ts","../src/components/qd-help-popup.ts","../src/config/help-content.ts","../src/components/qd-login.ts","../src/utils/validation-helpers.ts","../src/services/storage/migration.ts","../src/components/qd-status.ts","../src/components/qd-instructor/shared-styles.ts","../src/utils/security.ts","../src/config/instructor-password.ts","../src/components/qd-instructor/qd-instructor-unlock.ts","../src/components/qd-scores-modal.ts","../src/components/qd-instructor/qd-instructor-scores.ts","../src/components/qd-instructor/qd-instructor-export.ts","../src/components/qd-instructor/qd-instructor-manage.ts","../src/components/qd-pin-reset-dialog.ts","../src/components/qd-instructor/qd-instructor.ts","../src/init/component-injector.ts","../src/enhancers/home-badges.ts","../src/init/bootstrap.ts","../src/index.ts"],"sourcesContent":["/**\n * Structured logging with sanitization\n *\n * Provides debug/info/error logging with automatic sanitization of sensitive data.\n * Debug logs are controlled by a runtime flag to prevent production leakage.\n */\n\nimport type { ServiceId } from '../types/contracts.js';\n\n/**\n * Debug mode flag\n *\n * Set to true for development logging, false for production.\n * Can be controlled via data-debug attribute on script tag.\n */\nlet debugEnabled = false;\n\n/**\n * Enable or disable debug logging\n *\n * @param enabled - Whether to enable debug logs\n */\nexport function setDebugMode(enabled: boolean): void {\n debugEnabled = enabled;\n}\n\n/**\n * Check if debug mode is enabled\n */\nexport function isDebugEnabled(): boolean {\n return debugEnabled;\n}\n\n/**\n * Mask sensitive service ID\n *\n * Replaces middle characters with asterisks for privacy.\n *\n * @param serviceId - Service ID to mask\n * @returns Masked service ID (e.g., \"RN2344\" → \"RN****\")\n *\n * @example\n * ```typescript\n * const masked = maskServiceId('RN2344');\n * console.log(masked); // \"RN****\"\n * ```\n */\nexport function maskServiceId(serviceId: ServiceId): string {\n if (serviceId.length < 2) {\n return '**';\n }\n if (serviceId.length === 2) {\n return serviceId; // Keep 2-char IDs unmasked\n }\n const prefix = serviceId.slice(0, 2);\n const suffix = '*'.repeat(serviceId.length - 2);\n return prefix + suffix;\n}\n\n/**\n * Sanitize object by removing or masking sensitive fields\n *\n * Removes: name, passwordHash\n * Masks: serviceId\n *\n * @param obj - Object to sanitize\n * @returns Sanitized copy of object\n *\n * @example\n * ```typescript\n * const data = { serviceId: 'RN2344', name: 'John Doe', score: 95 };\n * const safe = sanitize(data);\n * console.log(safe); // { serviceId: 'RN****', score: 95 }\n * ```\n */\nexport function sanitize(obj: T): Partial {\n if (obj === null || typeof obj !== 'object') {\n return obj;\n }\n\n const sanitized: Record = {};\n\n for (const [key, value] of Object.entries(obj)) {\n // Remove sensitive fields\n if (key === 'name' || key === 'passwordHash') {\n continue;\n }\n\n // Mask service IDs\n if (key === 'serviceId' && typeof value === 'string') {\n sanitized[key] = maskServiceId(value);\n continue;\n }\n\n // Recursively sanitize nested objects\n if (typeof value === 'object' && value !== null) {\n sanitized[key] = sanitize(value);\n continue;\n }\n\n sanitized[key] = value;\n }\n\n return sanitized as Partial;\n}\n\n/**\n * Log debug message (only in debug mode)\n *\n * @param message - Debug message\n * @param data - Optional data to log (will be sanitized)\n */\nexport function debug(message: string, data?: unknown): void {\n if (debugEnabled) {\n if (data !== undefined) {\n // eslint-disable-next-line no-console\n console.log(`[DEBUG] ${message}`, sanitize(data));\n } else {\n // eslint-disable-next-line no-console\n console.log(`[DEBUG] ${message}`);\n }\n }\n}\n\n/**\n * Log info message (only in debug mode)\n *\n * @param message - Info message\n * @param data - Optional data to log (will be sanitized)\n */\nexport function info(message: string, data?: unknown): void {\n if (debugEnabled) {\n if (data !== undefined) {\n // eslint-disable-next-line no-console\n console.log(`[INFO] ${message}`, sanitize(data));\n } else {\n // eslint-disable-next-line no-console\n console.log(`[INFO] ${message}`);\n }\n }\n}\n\n/**\n * Log error message\n *\n * @param message - Error message\n * @param error - Error object or data\n */\nexport function error(message: string, error?: unknown): void {\n if (error instanceof Error) {\n const errorObj: { name: string; message: string; stack?: string } = {\n name: error.name,\n message: error.message,\n };\n if (debugEnabled && error.stack) {\n errorObj.stack = error.stack;\n }\n console.error(`[ERROR] ${message}`, errorObj);\n } else if (error !== undefined) {\n console.error(`[ERROR] ${message}`, sanitize(error));\n } else {\n console.error(`[ERROR] ${message}`);\n }\n}\n\n/**\n * Log warning message\n *\n * @param message - Warning message\n * @param data - Optional data to log (will be sanitized)\n */\nexport function warn(message: string, data?: unknown): void {\n if (data !== undefined) {\n console.warn(`[WARN] ${message}`, sanitize(data));\n } else {\n console.warn(`[WARN] ${message}`);\n }\n}\n\n/**\n * Logger object with all methods\n */\nexport const logger = {\n setDebugMode,\n isDebugEnabled,\n debug,\n info,\n warn,\n error,\n sanitize,\n maskServiceId,\n};\n","/**\n * Quiz Table Parser\n *\n * Parses DITA-generated HTML quiz tables and extracts question data.\n *\n * Table Structure:\n * - Must have class \"qd-quiz\"\n * - Exactly 3 columns: Question | Answer | Detail\n * - MCQ: Detail column contains
        with options\n * - Numeric: Detail column contains tolerance number\n */\n\nimport type { ParsedQuizTable, QuizQuestion } from '../types/contracts.js';\n\n/**\n * Parse a quiz table and extract question data\n *\n * @param table - HTMLTableElement with class \"qd-quiz\"\n * @returns ParsedQuizTable with questions and any validation errors\n */\nexport function parseQuizTable(table: HTMLTableElement): ParsedQuizTable {\n const errors: string[] = [];\n const questions: QuizQuestion[] = [];\n\n // Validate table has correct class\n if (!table.classList.contains('qd-quiz')) {\n errors.push('Table must have class \"qd-quiz\"');\n return { element: table, questions, errors };\n }\n\n // Get all rows from tbody (skip thead if present)\n const rows = Array.from(table.querySelectorAll('tbody tr'));\n\n if (rows.length === 0) {\n errors.push('Quiz table has no data rows');\n return { element: table, questions, errors };\n }\n\n // Parse each row\n rows.forEach((row, index) => {\n const cells = Array.from(row.querySelectorAll('td'));\n\n // Validate row has exactly 3 columns\n if (cells.length !== 3) {\n errors.push(\n `Row ${index + 1} has ${cells.length} columns, expected 3 (Question | Answer | Detail)`,\n );\n return;\n }\n\n const questionCell = cells[0];\n const answerCell = cells[1];\n const detailCell = cells[2];\n\n if (!questionCell || !answerCell || !detailCell) {\n return;\n }\n\n // Extract question text\n const questionText = questionCell.textContent?.trim() || '';\n if (!questionText) {\n errors.push(`Row ${index + 1} has empty question text`);\n return;\n }\n\n // Extract correct answer\n const correctAnswer = answerCell.textContent?.trim() || '';\n if (!correctAnswer) {\n errors.push(`Row ${index + 1} has empty answer`);\n return;\n }\n\n // Determine question kind and extract additional data\n const olElement = detailCell.querySelector('ol');\n\n if (olElement) {\n // MCQ question - extract options from ordered list\n const options = extractMcqOptions(olElement);\n\n if (options.length === 0) {\n errors.push(`Row ${index + 1} MCQ has no options in
          `);\n return;\n }\n\n questions.push({\n text: questionText,\n kind: 'mcq',\n correctAnswer,\n options,\n });\n } else {\n // Numeric question - extract tolerance\n const toleranceText = detailCell.textContent?.trim() || '';\n const tolerance = parseFloat(toleranceText);\n\n if (isNaN(tolerance)) {\n errors.push(\n `Row ${index + 1} appears to be numeric but has invalid tolerance: \"${toleranceText}\"`,\n );\n return;\n }\n\n questions.push({\n text: questionText,\n kind: 'numeric',\n correctAnswer,\n tolerance,\n });\n }\n });\n\n return {\n element: table,\n questions,\n errors: errors.length > 0 ? errors : undefined,\n };\n}\n\n/**\n * Extract option text from MCQ ordered list\n *\n * @param ol - The
            element containing options\n * @returns Array of option strings\n */\nfunction extractMcqOptions(ol: HTMLOListElement): string[] {\n const listItems = Array.from(ol.querySelectorAll('li'));\n return listItems.map((li) => li.textContent?.trim() || '').filter((text) => text.length > 0);\n}\n\n/**\n * Find all quiz tables in the document\n *\n * @param doc - Document to search (defaults to global document)\n * @returns Array of ParsedQuizTable results\n */\nexport function findQuizTables(doc: Document = document): ParsedQuizTable[] {\n const tables = Array.from(doc.querySelectorAll('table.qd-quiz'));\n return tables.map((table) => parseQuizTable(table));\n}\n\n/**\n * Validate answer against question\n *\n * @param question - The quiz question\n * @param answer - The user's answer\n * @returns true if answer is correct\n */\nexport function validateAnswer(question: QuizQuestion, answer: string): boolean {\n if (!answer || answer.trim() === '') {\n return false;\n }\n\n const trimmedAnswer = answer.trim();\n\n if (question.kind === 'mcq') {\n // MCQ: exact match of option number (1-indexed)\n return trimmedAnswer === question.correctAnswer;\n } else {\n // Numeric: within tolerance\n const userValue = parseFloat(trimmedAnswer);\n const correctValue = parseFloat(question.correctAnswer);\n\n if (isNaN(userValue) || isNaN(correctValue)) {\n return false;\n }\n\n const tolerance = question.tolerance ?? 0;\n return Math.abs(userValue - correctValue) <= tolerance;\n }\n}\n","/**\n * Frozen Type Contracts for Sonar Quiz System\n * Version: 1.1.0 (Fixed PageCache with answers field)\n *\n * These types are FROZEN and must not be modified without version bump.\n * Any changes require migration strategy and backwards compatibility.\n *\n * Changelog:\n * - 1.1.0: Added missing `answers` field to PageCache (fixes 78 eslint-disable comments)\n * - 1.0.0: Initial contracts\n */\n\n// ============================================================================\n// CORE IDENTIFIERS\n// ============================================================================\n\n/** Release identifier format: \"MM-YYYY\" */\nexport type ReleaseId = string;\n\n/** Service ID for student identification */\nexport type ServiceId = string;\n\n/** Page identifier from DITA document */\nexport type PageId = string;\n\n/** Table identifier (16-char hash based on table structure: rows x cols + class name) */\nexport type TableId = string;\n\n/** Cell key format: \"R{row}C{col}#f:{hash}\" where hash is 8-char from normalized content */\nexport type CellKey = string;\n\n// ============================================================================\n// ENUMERATIONS\n// ============================================================================\n\n/** Page completion state */\nexport type CompletionState = 'unstarted' | 'incomplete' | 'complete';\n\n/** Question type in quiz */\nexport type QuestionKind = 'mcq' | 'numeric';\n\n// ============================================================================\n// QUIZ ENTITIES\n// ============================================================================\n\n/** Individual quiz answer with correctness */\nexport interface AnswerRecord {\n /** User's answer value */\n answer: string;\n /** Whether the answer is correct */\n success: boolean;\n /** Timestamp when answer was submitted (ISO 8601) */\n timestamp: string;\n}\n\n/** Quiz question definition */\nexport interface QuizQuestion {\n /** Question text */\n text: string;\n /** Question type */\n kind: QuestionKind;\n /** Correct answer */\n correctAnswer: string;\n /** MCQ options (for mcq type) */\n options?: string[];\n /** Numeric tolerance (for numeric type) */\n tolerance?: number;\n}\n\n// ============================================================================\n// ANALYSIS ENTITIES\n// ============================================================================\n\n/** Analysis table data */\nexport interface AnalysisData {\n /** Unique table identifier */\n tableId: TableId;\n /** Cell key to content mapping */\n cells: Record;\n /** First edit timestamp (ISO 8601) */\n firstEdited?: string;\n /** Last edit timestamp (ISO 8601) */\n lastEdited?: string;\n}\n\n// ============================================================================\n// PAGE DATA\n// ============================================================================\n\n/** Student's data for a specific page */\nexport interface PageData {\n /** Array of quiz answers */\n answers: AnswerRecord[];\n /** Calculated completion state */\n state: CompletionState;\n /** First attempt timestamp (ISO 8601) */\n firstAttempted?: string;\n /** Last attempt timestamp (ISO 8601) */\n lastAttempted?: string;\n /** Analysis table data if present */\n analysis?: AnalysisData;\n}\n\n// ============================================================================\n// STUDENT RECORD\n// ============================================================================\n\n/** Complete student progress record */\nexport interface StudentRecord {\n /** Schema version for migrations */\n schema: number;\n /** Document identifier */\n docId: string;\n /** Release version */\n release: ReleaseId;\n /** Student service ID */\n serviceId: ServiceId;\n /** Student name */\n name: string;\n /** Total questions attempted */\n attempted: number;\n /** Total correct answers */\n correct: number;\n /** Last update timestamp (ISO 8601) */\n updated: string;\n /** Page data by page ID */\n pages: Record;\n\n // PIN Authentication (v2)\n /** SHA-256 hash of 4-digit PIN */\n pinHash?: string;\n /** ISO 8601 timestamp when PIN was created */\n pinCreatedAt?: string;\n /** ISO 8601 timestamp when PIN was last reset */\n pinResetAt?: string;\n}\n\n// ============================================================================\n// PIN AUTHENTICATION (v2)\n// ============================================================================\n\n/** Rate limiting state for PIN attempts (stored in sessionStorage) */\nexport interface PinAttemptState {\n /** Student identifier */\n serviceId: ServiceId;\n /** Failed attempt count (0-3) */\n attempts: number;\n /** ISO 8601 timestamp when lockout expires, or null */\n lockoutUntil: string | null;\n /** ISO 8601 timestamp of last attempt */\n lastAttempt: string;\n}\n\n/** Audit trail for instructor PIN resets (stored in IndexedDB) */\nexport interface PinResetEvent {\n /** UUID v4 */\n eventId: string;\n /** Student affected */\n serviceId: ServiceId;\n /** Actor type */\n resetBy: 'instructor';\n /** ISO 8601 timestamp */\n resetAt: string;\n /** Context */\n release: ReleaseId;\n}\n\n// ============================================================================\n// SESSION MANAGEMENT\n// ============================================================================\n\n/**\n * Active session data\n *\n * Note: serviceId and release are duplicated from the storage key\n * for convenient access without requiring a storage lookup\n */\nexport interface SessionData {\n /** Student service ID (duplicated from storage key) */\n serviceId: ServiceId;\n /** Student name */\n name: string;\n /** Current release (duplicated from storage key) */\n release: ReleaseId;\n /** Login timestamp (ISO 8601) */\n loginTime: string;\n /** Last activity timestamp (ISO 8601) */\n lastActivity: string;\n /** Session expiry timestamp (ISO 8601) */\n expiresAt: string;\n /** Whether instructor mode is unlocked */\n instructorUnlocked: boolean;\n /** Instructor unlock timestamp (ISO 8601) */\n unlockTime?: string;\n}\n\n/**\n * Cached page state for performance\n *\n * CRITICAL FIX: Added `answers` field to fix type safety issues\n * This was missing in v1.0.0, causing 78 eslint-disable comments\n */\nexport interface PageCache {\n /** Page completion state */\n state: CompletionState;\n /** Total number of questions registered on this page */\n total: number;\n /** Number of questions answered */\n answered: number;\n /** Number of correct answers */\n correct: number;\n /** Last update timestamp (ISO 8601) */\n last?: string;\n /** Answer records (ADDED in v1.1.0) */\n answers?: AnswerRecord[];\n /** Analysis table data if present (ADDED in v1.2.0) */\n analysis?: AnalysisData;\n}\n\n/** Session cache for quick access */\nexport interface SessionCache {\n /** Aggregated totals */\n totals: {\n total: number;\n answered: number;\n correct: number;\n };\n /** Per-page cache */\n pages: Record;\n}\n\n// ============================================================================\n// INSTRUCTOR FEATURES\n// ============================================================================\n\n/** Student summary for instructor view */\nexport interface StudentSummary {\n /** Student service ID */\n serviceId: ServiceId;\n /** Student name */\n name: string;\n /** Questions attempted */\n attempted: number;\n /** Correct answers */\n correct: number;\n /** Success percentage */\n percentage: number;\n /** Last activity timestamp */\n lastActive: string;\n}\n\n/** Quiz results export format */\nexport interface QuizExport {\n /** Export timestamp */\n timestamp: string;\n /** Release version */\n release: ReleaseId;\n /** Document ID */\n docId: string;\n /** Student results */\n students: StudentSummary[];\n /** Detailed answers by page */\n details?: {\n pageId: PageId;\n studentId: ServiceId;\n answers: AnswerRecord[];\n }[];\n}\n\n// ============================================================================\n// DOM ENHANCEMENT\n// ============================================================================\n\n/** Quiz table parsing result */\nexport interface ParsedQuizTable {\n /** Table element reference */\n element: HTMLTableElement;\n /** Extracted questions */\n questions: QuizQuestion[];\n /** Validation errors if any */\n errors?: string[];\n}\n\n/** Analysis table parsing result */\nexport interface ParsedAnalysisTable {\n /** Table element reference */\n element: HTMLTableElement;\n /** Table identifier */\n tableId: TableId;\n /** Editable cell positions */\n editableCells: Array<{\n row: number;\n col: number;\n key: CellKey;\n }>;\n /** Validation errors if any */\n errors?: string[];\n}\n\n// ============================================================================\n// STORAGE ADAPTER\n// ============================================================================\n\n/** Storage adapter interface for data persistence */\nexport interface StorageAdapter {\n /** Initialize storage */\n init(): Promise;\n\n /** Get student record */\n getStudent(release: ReleaseId, serviceId: ServiceId): Promise;\n\n /** Save student record */\n saveStudent(record: StudentRecord): Promise;\n\n /** Get all students for a release */\n getStudentsByRelease(release: ReleaseId): Promise;\n\n /** Delete all data */\n clearAll(): Promise;\n\n /** Create backup */\n backup(record: StudentRecord): Promise;\n}\n\n// ============================================================================\n// EVENTS\n// ============================================================================\n\n/** Custom event namespace */\nexport const EVENT_NAMESPACE = 'qd';\n\n/** Event type definitions */\nexport interface QuizEvents {\n 'qd:login': { detail: SessionData };\n 'qd:logout': { detail: { serviceId: ServiceId } };\n 'qd:answer-saved': { detail: { pageId: PageId; answer: AnswerRecord } };\n 'qd:state-changed': { detail: { pageId: PageId; state: CompletionState } };\n 'qd:analysis-saved': {\n detail: { pageId: PageId; tableId: TableId; cellKey: CellKey; content: string };\n };\n 'qd:instructor-unlock': { detail: { timestamp: string } };\n 'qd:instructor-lock': { detail: { timestamp: string } };\n 'qd:data-cleared': { detail: { timestamp: string } };\n 'qd:session-expired': { detail: { timestamp: string } };\n 'qd:storage-error': { detail: { error: Error; operation: string } };\n // PIN Authentication events (v2)\n 'qd:pin-created': { detail: { serviceId: ServiceId; timestamp: string } };\n 'qd:pin-verified': { detail: { serviceId: ServiceId; timestamp: string } };\n 'qd:pin-reset': { detail: { serviceId: ServiceId; resetBy: 'instructor'; timestamp: string } };\n}\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\n/** Current schema version */\nexport const SCHEMA_VERSION = 2;\n\n/** Session timeout in milliseconds (30 minutes) */\nexport const SESSION_TIMEOUT_MS = 30 * 60 * 1000;\n\n/** Storage keys */\nexport const STORAGE_KEYS = {\n SESSION: 'qd/session',\n CACHE: 'qd/state',\n INSTRUCTOR: 'qd/instructor',\n PIN_ATTEMPTS: 'qd:pin-attempts',\n} as const;\n\n/** PIN authentication constants */\nexport const PIN_CONSTANTS = {\n /** Maximum failed attempts before lockout */\n MAX_ATTEMPTS: 3,\n /** Lockout duration in milliseconds (30 seconds) */\n LOCKOUT_MS: 30 * 1000,\n /** PIN length (must be exactly 4 digits) */\n PIN_LENGTH: 4,\n} as const;\n\n/** CSS classes for DOM selection */\nexport const CSS_CLASSES = {\n QUIZ_TABLE: 'qd-quiz',\n ANALYSIS_TABLE: 'qd-analysis',\n TEST_LINK: 'quizPageBtn',\n} as const;\n\n/** Element IDs */\nexport const ELEMENT_IDS = {\n STATUS_PANEL: 'qd-status',\n} as const;\n\n/**\n * CSS selectors for DOM injection points\n *\n * These are default/reference values. Actual selectors are configurable\n * via SonarQuizConfig.statusPanelContainer option.\n *\n * @see SonarQuizConfig in src/index.ts\n */\nexport const INJECTION_SELECTORS = {\n /** Default navbar container for Oxygen WebHelp templates */\n NAVBAR_CONTAINER: '.wh_top_menu_and_indexterms_link',\n} as const;\n\n/** Validation limits */\nexport const LIMITS = {\n MAX_QUESTIONS_PER_PAGE: 100,\n MAX_CELL_CONTENT_LENGTH: 500,\n MAX_NAME_LENGTH: 100,\n MAX_SERVICE_ID_LENGTH: 10,\n} as const;\n","/**\n * Session Management Service\n *\n * Handles user session lifecycle, timeout management, and instructor mode.\n * Integrates with encrypted session storage for secure session data.\n */\n\nimport type {\n SessionData,\n SessionCache,\n ServiceId,\n ReleaseId,\n StudentRecord,\n PageCache,\n PageData,\n CompletionState,\n} from '../types/contracts.js';\nimport { STORAGE_KEYS, SESSION_TIMEOUT_MS } from '../types/contracts.js';\nimport { info, warn, error } from '../utils/logger.js';\nimport { isSessionExpired } from '../utils/calculation-helpers.js';\n\n/**\n * Session Service for managing user sessions\n */\nexport class SessionService {\n /**\n * Create a new session\n *\n * @param serviceId - Student service ID\n * @param name - Student name\n * @param release - Current release ID\n * @returns Created session data\n */\n createSession(serviceId: ServiceId, name: string, release: ReleaseId): SessionData {\n const now = new Date();\n const loginTime = now.toISOString();\n const expiresAt = new Date(now.getTime() + SESSION_TIMEOUT_MS).toISOString();\n\n const session: SessionData = {\n serviceId,\n name,\n release,\n loginTime,\n lastActivity: loginTime,\n expiresAt,\n instructorUnlocked: false,\n };\n\n this.saveSession(session);\n info(`Session created for ${serviceId} (${name})`);\n\n // Emit login event\n this.emitEvent('qd:login', { serviceId, name, release, loginTime });\n\n return session;\n }\n\n /**\n * Get the current session\n *\n * @returns Session data or null if no session exists\n */\n getSession(): SessionData | null {\n try {\n const sessionData = sessionStorage.getItem(STORAGE_KEYS.SESSION);\n if (!sessionData) {\n return null;\n }\n\n const session = JSON.parse(sessionData) as SessionData;\n\n // Validate required fields\n if (!session.serviceId || !session.release || !session.expiresAt) {\n warn('Invalid session data, missing required fields');\n return null;\n }\n\n return session;\n } catch (err) {\n error('Failed to parse session data', err as Error);\n return null;\n }\n }\n\n /**\n * Update last activity time and extend session expiry\n */\n updateActivity(): void {\n const session = this.getSession();\n if (!session) {\n return;\n }\n\n const now = new Date();\n session.lastActivity = now.toISOString();\n session.expiresAt = new Date(now.getTime() + SESSION_TIMEOUT_MS).toISOString();\n\n this.saveSession(session);\n }\n\n /**\n * Check if the current session is expired\n *\n * @returns True if session is expired or doesn't exist\n */\n isExpired(): boolean {\n const session = this.getSession();\n if (!session) {\n return true;\n }\n\n return isSessionExpired(session.expiresAt);\n }\n\n /**\n * Clear the current session\n */\n clearSession(): void {\n const session = this.getSession();\n sessionStorage.removeItem(STORAGE_KEYS.SESSION);\n sessionStorage.removeItem(STORAGE_KEYS.CACHE);\n sessionStorage.removeItem(STORAGE_KEYS.INSTRUCTOR);\n\n // Clear instructor-specific state (FR-001)\n sessionStorage.removeItem('qd/instructor/showAnswers');\n\n if (session) {\n info(`Session cleared for ${session.serviceId}`);\n\n // Emit logout event\n this.emitEvent('qd:logout', {\n serviceId: session.serviceId,\n timestamp: new Date().toISOString(),\n });\n }\n }\n\n /**\n * Unlock instructor mode\n */\n unlockInstructor(): void {\n const session = this.getSession();\n if (!session) {\n return;\n }\n\n session.instructorUnlocked = true;\n session.unlockTime = new Date().toISOString();\n\n this.saveSession(session);\n\n info('Instructor mode unlocked');\n\n // Emit custom event\n this.emitEvent('qd:instructor-unlock', { timestamp: session.unlockTime });\n }\n\n /**\n * Lock instructor mode\n */\n lockInstructor(): void {\n const session = this.getSession();\n if (!session) {\n return;\n }\n\n session.instructorUnlocked = false;\n delete session.unlockTime;\n\n this.saveSession(session);\n\n info('Instructor mode locked');\n\n // Emit custom event\n this.emitEvent('qd:instructor-lock', { timestamp: new Date().toISOString() });\n }\n\n /**\n * Check if instructor mode is unlocked\n *\n * @returns True if instructor mode is unlocked\n */\n isInstructorUnlocked(): boolean {\n const session = this.getSession();\n return session?.instructorUnlocked === true;\n }\n\n /**\n * Get session cache from sessionStorage\n *\n * @returns Session cache or null if not found\n */\n getCache(): SessionCache | null {\n try {\n const cacheData = sessionStorage.getItem(STORAGE_KEYS.CACHE);\n if (!cacheData) {\n return null;\n }\n\n return JSON.parse(cacheData) as SessionCache;\n } catch (err) {\n error('Failed to parse cache data', err);\n return null;\n }\n }\n\n /**\n * Save session cache to sessionStorage\n *\n * @param cache - Cache data to save\n */\n saveCache(cache: SessionCache): void {\n try {\n sessionStorage.setItem(STORAGE_KEYS.CACHE, JSON.stringify(cache));\n } catch (err) {\n error('Failed to save cache', err);\n }\n }\n\n /**\n * Clear the session cache\n */\n clearCache(): void {\n sessionStorage.removeItem(STORAGE_KEYS.CACHE);\n }\n\n /**\n * Save session to sessionStorage\n *\n * @param session - Session data to save\n */\n private saveSession(session: SessionData): void {\n try {\n sessionStorage.setItem(STORAGE_KEYS.SESSION, JSON.stringify(session));\n } catch (err) {\n error('Failed to save session', err);\n }\n }\n\n /**\n * Emit a custom event\n *\n * @param eventName - Name of the event\n * @param detail - Event detail data\n */\n private emitEvent(eventName: string, detail: unknown): void {\n try {\n const event = new CustomEvent(eventName, { detail, bubbles: true });\n document.dispatchEvent(event);\n } catch (err) {\n error(`Failed to emit event ${eventName}`, err);\n }\n }\n}\n\n// ============================================================================\n// CACHE BUILDING UTILITIES\n// ============================================================================\n\n/**\n * Build session cache from a student record\n *\n * This creates a SessionCache structure that provides quick access to\n * page states and totals without querying IndexedDB.\n *\n * @param record - Student record to build cache from\n * @returns Session cache with totals and page entries\n */\nexport function buildCacheFromRecord(record: StudentRecord): SessionCache {\n const cache: SessionCache = {\n totals: {\n total: 0,\n answered: 0,\n correct: 0,\n },\n pages: {},\n };\n\n // Build cache entry for each page\n for (const [pageId, pageData] of Object.entries(record.pages)) {\n const pageCache = buildPageCache(pageId, pageData);\n cache.pages[pageId] = pageCache;\n\n // Accumulate totals\n cache.totals.total += pageCache.total;\n cache.totals.answered += pageCache.answered;\n cache.totals.correct += pageCache.correct;\n }\n\n return cache;\n}\n\n/**\n * Build a page cache entry from page data\n *\n * @param _pageId - Page identifier (unused, kept for API consistency)\n * @param pageData - Page data from student record\n * @returns Page cache entry\n */\nexport function buildPageCache(_pageId: string, pageData: PageData): PageCache {\n // Total is the length of answers array (includes empty/placeholder answers)\n const total = pageData.answers.length;\n const answered = pageData.answers.filter((a) => a.answer.trim() !== '').length;\n const correct = pageData.answers.filter((a) => a.success).length;\n\n return {\n state: pageData.state,\n total,\n answered,\n correct,\n last: pageData.lastAttempted,\n answers: pageData.answers,\n analysis: pageData.analysis, // Preserve analysis data from analysis tables\n };\n}\n\n/**\n * Register page questions in cache\n *\n * Called when a quiz page loads to register the total number of questions.\n * This ensures the status panel shows total registered questions, not just answered.\n *\n * @param cache - Current cache to update\n * @param pageId - Page identifier\n * @param totalQuestions - Total number of questions on the page\n * @returns Updated cache\n */\nexport function registerPageQuestions(\n cache: SessionCache,\n pageId: string,\n totalQuestions: number,\n): SessionCache {\n // Get existing page cache or create new one\n const existingPage = cache.pages[pageId];\n\n // If page already registered with same or higher total, don't update\n if (existingPage && existingPage.total >= totalQuestions) {\n return cache;\n }\n\n // Calculate delta for totals update\n const oldTotal = existingPage?.total || 0;\n const delta = totalQuestions - oldTotal;\n\n // Create/update page entry\n const updatedPage: PageCache = {\n state: existingPage?.state || ('unstarted' as const),\n total: totalQuestions,\n answered: existingPage?.answered || 0,\n correct: existingPage?.correct || 0,\n last: existingPage?.last,\n answers: existingPage?.answers,\n analysis: existingPage?.analysis,\n };\n\n return {\n totals: {\n total: cache.totals.total + delta,\n answered: cache.totals.answered,\n correct: cache.totals.correct,\n },\n pages: {\n ...cache.pages,\n [pageId]: updatedPage,\n },\n };\n}\n\n/**\n * Update cache with a new answer\n *\n * This incrementally updates the cache when a new answer is submitted,\n * avoiding the need to rebuild the entire cache.\n *\n * @param cache - Current cache to update\n * @param pageId - Page where answer was submitted\n * @param isCorrect - Whether the answer is correct\n * @param newState - New completion state for the page\n * @returns Updated cache\n */\nexport function updateCacheWithAnswer(\n cache: SessionCache,\n pageId: string,\n isCorrect: boolean,\n newState: CompletionState,\n): SessionCache {\n const now = new Date().toISOString();\n\n // Get or create page entry\n const pageCache = cache.pages[pageId] || {\n state: 'incomplete' as const,\n total: 0,\n answered: 0,\n correct: 0,\n };\n\n // Update page counts\n const updatedPage: PageCache = {\n ...pageCache,\n state: newState,\n answered: pageCache.answered + 1,\n correct: pageCache.correct + (isCorrect ? 1 : 0),\n last: now,\n };\n\n // Update totals\n const updatedTotals = {\n total: cache.totals.total,\n answered: cache.totals.answered + 1,\n correct: cache.totals.correct + (isCorrect ? 1 : 0),\n };\n\n return {\n totals: updatedTotals,\n pages: {\n ...cache.pages,\n [pageId]: updatedPage,\n },\n };\n}\n\n// ============================================================================\n// SINGLETON PATTERN\n// ============================================================================\n\n/**\n * Create and return a singleton instance of the session service\n */\nlet sessionInstance: SessionService | null = null;\n\nexport function getSessionService(): SessionService {\n if (!sessionInstance) {\n sessionInstance = new SessionService();\n }\n return sessionInstance;\n}\n\n/**\n * Reset the singleton instance (useful for testing)\n */\nexport function resetSessionService(): void {\n sessionInstance = null;\n}\n","/**\n * Calculation Helpers\n *\n * Pure functions for status indicators, percentages, and totals.\n * Feature: 007-lit-component-refactor\n *\n * These functions have no side effects and no DOM dependencies,\n * making them easy to unit test.\n */\n\nimport type { PageData, PageId } from '../types/contracts';\n\n/**\n * Status indicator values for R/A/G progress display.\n */\nexport type StatusIndicator = 'red' | 'amber' | 'green';\n\n/**\n * Calculates R/A/G status indicator from quiz totals.\n *\n * @param total - Total number of questions\n * @param correct - Number of correct answers\n * @returns 'green' if all correct, 'red' if none, 'amber' otherwise\n */\nexport function calculateStatusIndicator(total: number, correct: number): StatusIndicator {\n if (total === 0 || correct === 0) {\n return 'red';\n }\n if (correct === total) {\n return 'green';\n }\n return 'amber';\n}\n\n/**\n * Calculates percentage with safe division.\n *\n * @param correct - Numerator (correct count)\n * @param attempted - Denominator (attempted count)\n * @returns Rounded percentage (0 if attempted is 0)\n */\nexport function calculatePercentage(correct: number, attempted: number): number {\n if (attempted === 0) {\n return 0;\n }\n return Math.round((correct / attempted) * 100);\n}\n\n/**\n * Totals calculated from page data.\n */\nexport interface RecalculatedTotals {\n attempted: number;\n correct: number;\n}\n\n/**\n * Recalculates totals from all pages in a student record.\n * Only counts answers with non-empty answer strings (excludes placeholder entries).\n *\n * @param pages - Record of page ID to page data\n * @returns Aggregated attempted and correct counts\n */\nexport function recalculateTotalsFromPages(pages: Record): RecalculatedTotals {\n let attempted = 0;\n let correct = 0;\n\n for (const pageId in pages) {\n const pageData = pages[pageId];\n if (pageData && pageData.answers && Array.isArray(pageData.answers)) {\n // Filter to only non-empty answers (matches storage-service.ts behavior)\n const answered = pageData.answers.filter((a) => a.answer.trim() !== '');\n attempted += answered.length;\n correct += answered.filter((a) => a.success).length;\n }\n }\n\n return { attempted, correct };\n}\n\n/**\n * Checks if a session has expired.\n *\n * @param expiresAt - ISO 8601 expiration timestamp\n * @param now - Current time (defaults to new Date())\n * @returns True if session has expired\n */\nexport function isSessionExpired(expiresAt: string, now: Date = new Date()): boolean {\n const expiryDate = new Date(expiresAt);\n // Invalid date -> treat as expired\n if (isNaN(expiryDate.getTime())) {\n return true;\n }\n return now >= expiryDate;\n}\n\n/**\n * Masks a service ID for display (shows last N digits).\n *\n * @param serviceId - Full service ID\n * @param visibleDigits - Number of digits to show (default 4)\n * @returns Masked string like \"...1234\"\n */\nexport function maskServiceId(serviceId: string, visibleDigits: number = 4): string {\n if (!serviceId) {\n return '';\n }\n if (serviceId.length <= visibleDigits) {\n return serviceId;\n }\n if (visibleDigits === 0) {\n return '...';\n }\n return '...' + serviceId.slice(-visibleDigits);\n}\n","/**\n * Date formatting utilities for consistent timestamp display across the application.\n * Provides both display formatting (24-hour, month/date/time) and CSV export formatting (ISO 8601).\n */\n\n/**\n * Format options for timestamp display\n */\nexport type TimestampFormat = 'display' | 'csv';\n\n/**\n * Format a date for display in the instructor interface\n * @param date - Date to format\n * @returns Formatted string in \"Nov 19 14:23\" or \"11/19 14:23:45\" format (24-hour time)\n */\nfunction formatDisplayTimestamp(date: Date): string {\n // Use short month name format: \"Nov 19 14:23\"\n const month = date.toLocaleDateString('en-US', { month: 'short' });\n const day = date.getDate();\n const hours = date.getHours().toString().padStart(2, '0');\n const minutes = date.getMinutes().toString().padStart(2, '0');\n\n return `${month} ${day} ${hours}:${minutes}`;\n}\n\n/**\n * Format a date for CSV export\n * @param date - Date to format\n * @returns ISO 8601 formatted string for spreadsheet compatibility\n */\nfunction formatCSVTimestamp(date: Date): string {\n return date.toISOString();\n}\n\n/**\n * Main timestamp formatting function\n * @param date - Date to format (can be Date object or ISO string)\n * @param format - Format type ('display' for UI, 'csv' for export)\n * @returns Formatted timestamp string\n */\nexport function formatTimestamp(date: Date | string, format: TimestampFormat = 'display'): string {\n // Handle null/undefined\n if (date == null) {\n console.warn('Invalid date provided to formatTimestamp:', date);\n return 'Invalid Date';\n }\n\n const dateObj = typeof date === 'string' ? new Date(date) : date;\n\n // Validate date\n if (isNaN(dateObj.getTime())) {\n console.warn('Invalid date provided to formatTimestamp:', date);\n return 'Invalid Date';\n }\n\n return format === 'csv' ? formatCSVTimestamp(dateObj) : formatDisplayTimestamp(dateObj);\n}\n\n/**\n * Parse an ISO 8601 timestamp from storage and format for display\n * @param isoString - ISO 8601 timestamp string from IndexedDB\n * @returns Formatted display string\n */\nexport function formatStoredTimestamp(isoString: string): string {\n return formatTimestamp(isoString, 'display');\n}\n\n/**\n * Get current timestamp in ISO 8601 format for storage\n * @returns Current time as ISO 8601 string\n */\nexport function getCurrentTimestamp(): string {\n return new Date().toISOString();\n}\n","/**\n * Debouncer utility for delaying function execution\n *\n * Provides centralized debounce timer management, replacing the WeakMap pattern\n * used in the original implementation. Saves ~22 lines of duplicated code.\n *\n * @example\n * ```typescript\n * const debouncer = new Debouncer();\n *\n * // Debounce save operation\n * function handleInput(value: string) {\n * debouncer.debounce('save-answer', () => {\n * saveToDatabase(value);\n * }, 200);\n * }\n * ```\n */\n\n/**\n * Debouncer class for managing delayed function calls\n *\n * Maintains a map of timers indexed by key, allowing multiple independent\n * debounced operations.\n */\nexport class Debouncer {\n private timers = new Map>();\n\n /**\n * Debounce a function call\n *\n * If called multiple times with the same key, only the last call will execute\n * after the delay period.\n *\n * @param key - Unique identifier for this debounced operation\n * @param fn - Function to execute after delay\n * @param delay - Delay in milliseconds (default: 200ms)\n *\n * @example\n * ```typescript\n * const debouncer = new Debouncer();\n *\n * // Called multiple times rapidly\n * debouncer.debounce('auto-save', () => console.log('Saved!'), 500);\n * debouncer.debounce('auto-save', () => console.log('Saved!'), 500);\n * debouncer.debounce('auto-save', () => console.log('Saved!'), 500);\n * // Only logs \"Saved!\" once after 500ms\n * ```\n */\n debounce(key: string, fn: () => void, delay = 200): void {\n // Cancel existing timer if present\n const existing = this.timers.get(key);\n if (existing !== undefined) {\n clearTimeout(existing);\n }\n\n // Set new timer\n const timer = setTimeout(() => {\n this.timers.delete(key);\n fn();\n }, delay);\n\n this.timers.set(key, timer);\n }\n\n /**\n * Cancel a specific debounced operation\n *\n * @param key - Key of the operation to cancel\n * @returns true if a timer was cancelled, false if no timer existed\n */\n cancel(key: string): boolean {\n const timer = this.timers.get(key);\n if (timer !== undefined) {\n clearTimeout(timer);\n this.timers.delete(key);\n return true;\n }\n return false;\n }\n\n /**\n * Cancel all pending debounced operations\n *\n * @returns Number of timers that were cancelled\n */\n cancelAll(): number {\n let count = 0;\n for (const timer of this.timers.values()) {\n clearTimeout(timer);\n count++;\n }\n this.timers.clear();\n return count;\n }\n\n /**\n * Check if a debounced operation is pending\n *\n * @param key - Key to check\n * @returns true if a timer is active for this key\n */\n isPending(key: string): boolean {\n return this.timers.has(key);\n }\n\n /**\n * Get count of pending operations\n *\n * @returns Number of active timers\n */\n getPendingCount(): number {\n return this.timers.size;\n }\n}\n","/**\n * DOM helper utilities\n *\n * Provides type-safe DOM query and manipulation helpers, eliminating\n * repetitive querySelector patterns. Saves ~80 lines of duplicated code.\n *\n * All functions use textContent instead of innerHTML to prevent XSS vulnerabilities.\n */\n\n/**\n * Get all rows from a table body\n *\n * @param table - Table element\n * @returns Array of table row elements\n *\n * @example\n * ```typescript\n * const table = document.querySelector('table.qd-quiz');\n * if (table instanceof HTMLTableElement) {\n * const rows = getTableRows(table);\n * console.log(`Table has ${rows.length} rows`);\n * }\n * ```\n */\nexport function getTableRows(table: HTMLTableElement): HTMLTableRowElement[] {\n const tbody = table.querySelector('tbody');\n if (!tbody) {\n return [];\n }\n return Array.from(tbody.querySelectorAll('tr'));\n}\n\n/**\n * Get all cells from a table row\n *\n * @param row - Table row element\n * @returns Array of table cell elements\n *\n * @example\n * ```typescript\n * const row = table.querySelector('tr');\n * if (row instanceof HTMLTableRowElement) {\n * const cells = getRowCells(row);\n * console.log(`Row has ${cells.length} cells`);\n * }\n * ```\n */\nexport function getRowCells(row: HTMLTableRowElement): HTMLTableCellElement[] {\n return Array.from(row.cells);\n}\n\n/**\n * Get trimmed text content from an element\n *\n * Returns empty string if element is null or has no text content.\n *\n * @param element - Element to get text from\n * @returns Trimmed text content\n *\n * @example\n * ```typescript\n * const cell = row.cells[0];\n * const text = getTextContent(cell);\n * console.log('Cell text:', text);\n * ```\n */\nexport function getTextContent(element: Element | null): string {\n if (!element) {\n return '';\n }\n return element.textContent?.trim() || '';\n}\n\n/**\n * Set text content on an element (XSS-safe)\n *\n * Uses textContent instead of innerHTML to prevent XSS attacks.\n *\n * @param element - Element to set text on\n * @param text - Text content to set\n *\n * @example\n * ```typescript\n * const div = document.createElement('div');\n * setTextContent(div, 'Safe text content');\n * ```\n */\nexport function setTextContent(element: Element, text: string): void {\n element.textContent = text;\n}\n\n/**\n * Create an element with optional text and class name (XSS-safe)\n *\n * Uses textContent instead of innerHTML for XSS protection.\n *\n * @param tag - HTML tag name\n * @param text - Optional text content\n * @param className - Optional class name\n * @returns Created element\n *\n * @example\n * ```typescript\n * const div = createElement('div', 'Hello, World!', 'greeting');\n * document.body.appendChild(div);\n * ```\n */\nexport function createElement(\n tag: K,\n text?: string,\n className?: string,\n): HTMLElementTagNameMap[K] {\n const element = document.createElement(tag);\n\n if (text !== undefined) {\n element.textContent = text;\n }\n\n if (className !== undefined) {\n element.className = className;\n }\n\n return element;\n}\n\n/**\n * Create multiple child elements and append to parent (XSS-safe)\n *\n * @param parent - Parent element\n * @param children - Array of child elements to append\n *\n * @example\n * ```typescript\n * const div = createElement('div');\n * appendChildren(div, [\n * createElement('span', 'First'),\n * createElement('span', 'Second'),\n * ]);\n * ```\n */\nexport function appendChildren(parent: Element, children: Element[]): void {\n for (const child of children) {\n parent.appendChild(child);\n }\n}\n\n/**\n * Query selector with type safety\n *\n * @param selector - CSS selector\n * @param parent - Parent element (default: document)\n * @returns Element or null\n *\n * @example\n * ```typescript\n * const table = querySelector('table.qd-quiz');\n * if (table) {\n * const rows = getTableRows(table);\n * }\n * ```\n */\nexport function querySelector(\n selector: string,\n parent: ParentNode = document,\n): T | null {\n return parent.querySelector(selector);\n}\n\n/**\n * Query selector all with type safety\n *\n * @param selector - CSS selector\n * @param parent - Parent element (default: document)\n * @returns Array of elements\n *\n * @example\n * ```typescript\n * const tables = querySelectorAll('table.qd-quiz');\n * console.log(`Found ${tables.length} quiz tables`);\n * ```\n */\nexport function querySelectorAll(\n selector: string,\n parent: ParentNode = document,\n): T[] {\n return Array.from(parent.querySelectorAll(selector));\n}\n\n/**\n * Get element by ID with type safety\n *\n * @param id - Element ID\n * @returns Element or null\n *\n * @example\n * ```typescript\n * const status = getElementById('qd-status');\n * if (status) {\n * status.style.display = 'block';\n * }\n * ```\n */\nexport function getElementById(id: string): T | null {\n const element = document.getElementById(id);\n return element as T | null;\n}\n\n/**\n * Remove all children from an element\n *\n * @param element - Element to clear\n *\n * @example\n * ```typescript\n * const container = getElementById('results');\n * if (container) {\n * removeAllChildren(container);\n * }\n * ```\n */\nexport function removeAllChildren(element: Element): void {\n while (element.firstChild) {\n element.removeChild(element.firstChild);\n }\n}\n\n/**\n * Replace all children of an element with new children\n *\n * @param element - Element to update\n * @param children - New children to add\n *\n * @example\n * ```typescript\n * const container = getElementById('results');\n * if (container) {\n * replaceChildren(container, [\n * createElement('div', 'Result 1'),\n * createElement('div', 'Result 2'),\n * ]);\n * }\n * ```\n */\nexport function replaceChildren(element: Element, children: Element[]): void {\n removeAllChildren(element);\n appendChildren(element, children);\n}\n\n/**\n * Check if element has a specific class\n *\n * @param element - Element to check\n * @param className - Class name to look for\n * @returns true if element has the class\n */\nexport function hasClass(element: Element, className: string): boolean {\n return element.classList.contains(className);\n}\n\n/**\n * Add one or more classes to an element\n *\n * @param element - Element to modify\n * @param classNames - Class names to add\n */\nexport function addClass(element: Element, ...classNames: string[]): void {\n element.classList.add(...classNames);\n}\n\n/**\n * Remove one or more classes from an element\n *\n * @param element - Element to modify\n * @param classNames - Class names to remove\n */\nexport function removeClass(element: Element, ...classNames: string[]): void {\n element.classList.remove(...classNames);\n}\n\n/**\n * Toggle a class on an element\n *\n * @param element - Element to modify\n * @param className - Class name to toggle\n * @returns true if class was added, false if removed\n */\nexport function toggleClass(element: Element, className: string): boolean {\n return element.classList.toggle(className);\n}\n","/**\n * Event helper utilities\n *\n * Provides type-safe custom event emission and handling, with consistent\n * configuration for bubbling and composition. Saves ~8 lines per event emission.\n */\n\nimport type { QuizEvents } from '../types/contracts.js';\n\n/**\n * Emit a custom event on the document\n *\n * Events bubble by default and are composed (cross shadow DOM boundaries).\n *\n * @param name - Event name (should use 'qd:' namespace)\n * @param detail - Event detail data\n * @param options - Optional event configuration\n *\n * @example\n * ```typescript\n * // Emit login event\n * emitCustomEvent('qd:login', {\n * serviceId: 'RN2344',\n * name: 'John Doe',\n * loginTime: new Date().toISOString(),\n * });\n * ```\n */\nexport function emitCustomEvent(\n name: K,\n detail: QuizEvents[K]['detail'],\n options?: {\n bubbles?: boolean;\n composed?: boolean;\n cancelable?: boolean;\n },\n): boolean {\n const event = new CustomEvent(name, {\n detail,\n bubbles: options?.bubbles ?? true,\n composed: options?.composed ?? true,\n cancelable: options?.cancelable ?? false,\n });\n\n return document.dispatchEvent(event);\n}\n\n/**\n * Add event listener for custom event\n *\n * @param name - Event name\n * @param handler - Event handler function\n * @param options - Optional event listener options\n *\n * @example\n * ```typescript\n * // Listen for login events\n * const unsubscribe = addEventListener('qd:login', (event) => {\n * console.log('User logged in:', event.detail.serviceId);\n * });\n *\n * // Later: remove listener\n * unsubscribe();\n * ```\n */\nexport function addEventListener(\n name: K,\n handler: (event: CustomEvent) => void,\n options?: AddEventListenerOptions,\n): () => void {\n const listener = handler as EventListener;\n document.addEventListener(name, listener, options);\n\n // Return unsubscribe function\n return () => {\n document.removeEventListener(name, listener, options);\n };\n}\n\n/**\n * Remove event listener for custom event\n *\n * @param name - Event name\n * @param handler - Event handler function\n * @param options - Optional event listener options\n *\n * @example\n * ```typescript\n * function handleLogin(event) {\n * console.log('Logged in:', event.detail.serviceId);\n * }\n *\n * addEventListener('qd:login', handleLogin);\n * // Later...\n * removeEventListener('qd:login', handleLogin);\n * ```\n */\nexport function removeEventListener(\n name: K,\n handler: (event: CustomEvent) => void,\n options?: EventListenerOptions,\n): void {\n const listener = handler as EventListener;\n document.removeEventListener(name, listener, options);\n}\n\n/**\n * Add one-time event listener that auto-removes after first trigger\n *\n * @param name - Event name\n * @param handler - Event handler function\n *\n * @example\n * ```typescript\n * // Wait for login, then perform action once\n * addEventListenerOnce('qd:login', (event) => {\n * console.log('First login detected');\n * });\n * ```\n */\nexport function addEventListenerOnce(\n name: K,\n handler: (event: CustomEvent) => void,\n): void {\n const listener = handler as EventListener;\n document.addEventListener(name, listener, { once: true });\n}\n\n/**\n * Wait for a specific event to occur\n *\n * Returns a promise that resolves when the event is emitted.\n *\n * @param name - Event name to wait for\n * @param timeout - Optional timeout in milliseconds\n * @returns Promise that resolves with event detail\n *\n * @example\n * ```typescript\n * // Wait for login\n * const session = await waitForEvent('qd:login', 5000);\n * console.log('User logged in:', session.serviceId);\n * ```\n */\nexport function waitForEvent(\n name: K,\n timeout?: number,\n): Promise {\n return new Promise((resolve, reject) => {\n let timeoutId: ReturnType | undefined;\n\n const handler = (event: Event) => {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n const customEvent = event as CustomEvent;\n resolve(customEvent.detail);\n };\n\n document.addEventListener(name, handler, { once: true });\n\n if (timeout !== undefined) {\n timeoutId = setTimeout(() => {\n document.removeEventListener(name, handler);\n reject(new Error(`Timeout waiting for event: ${name}`));\n }, timeout);\n }\n });\n}\n\n/**\n * Dispatch event on a specific element\n *\n * @param element - Element to dispatch event on\n * @param name - Event name\n * @param detail - Event detail\n * @param options - Optional event configuration\n *\n * @example\n * ```typescript\n * const button = document.querySelector('button');\n * if (button) {\n * dispatchEventOn(button, 'qd:custom', { data: 'test' });\n * }\n * ```\n */\nexport function dispatchEventOn(\n element: Element,\n name: string,\n detail: T,\n options?: {\n bubbles?: boolean;\n composed?: boolean;\n cancelable?: boolean;\n },\n): boolean {\n const event = new CustomEvent(name, {\n detail,\n bubbles: options?.bubbles ?? true,\n composed: options?.composed ?? true,\n cancelable: options?.cancelable ?? false,\n });\n\n return element.dispatchEvent(event);\n}\n","/**\n * Storage helper utilities\n *\n * Provides type-safe JSON storage operations for sessionStorage,\n * replacing repetitive try-catch JSON.parse patterns. Saves ~54 lines\n * of duplicated code.\n */\n\nimport { warn } from './logger.js';\n\n/**\n * Get and parse JSON data from sessionStorage\n *\n * @param key - Storage key\n * @returns Parsed object of type T, or null if not found or invalid\n *\n * @example\n * ```typescript\n * interface SessionData {\n * userId: string;\n * loginTime: string;\n * }\n *\n * const session = getJSON('qd/session');\n * if (session) {\n * console.log('User ID:', session.userId);\n * }\n * ```\n */\nexport function getJSON(key: string): T | null {\n try {\n const data = sessionStorage.getItem(key);\n if (!data) {\n return null;\n }\n return JSON.parse(data) as T;\n } catch (error) {\n warn(`Failed to parse JSON from sessionStorage key: ${key}`, error);\n return null;\n }\n}\n\n/**\n * Stringify and store JSON data in sessionStorage\n *\n * @param key - Storage key\n * @param value - Data to store\n * @returns true if successful, false if failed\n *\n * @example\n * ```typescript\n * const session = {\n * userId: 'RN2344',\n * loginTime: new Date().toISOString(),\n * };\n *\n * setJSON('qd/session', session);\n * ```\n */\nexport function setJSON(key: string, value: T): boolean {\n try {\n const json = JSON.stringify(value);\n sessionStorage.setItem(key, json);\n return true;\n } catch (error) {\n warn(`Failed to store JSON in sessionStorage key: ${key}`, error);\n return false;\n }\n}\n\n/**\n * Remove item from sessionStorage\n *\n * @param key - Storage key to remove\n */\nexport function removeItem(key: string): void {\n sessionStorage.removeItem(key);\n}\n\n/**\n * Check if key exists in sessionStorage\n *\n * @param key - Storage key to check\n * @returns true if key exists\n */\nexport function hasItem(key: string): boolean {\n return sessionStorage.getItem(key) !== null;\n}\n\n/**\n * Clear all quiz data from sessionStorage\n *\n * Only removes keys with 'qd/' prefix, leaving other data intact.\n *\n * @returns Number of items cleared\n *\n * @example\n * ```typescript\n * // Clear all quiz-related session data\n * const cleared = clearQuizData();\n * console.log(`Cleared ${cleared} items`);\n * ```\n */\nexport function clearQuizData(): number {\n const keysToRemove: string[] = [];\n\n // Find all keys with 'qd/' prefix\n for (let i = 0; i < sessionStorage.length; i++) {\n const key = sessionStorage.key(i);\n if (key && key.startsWith('qd/')) {\n keysToRemove.push(key);\n }\n }\n\n // Remove found keys\n for (const key of keysToRemove) {\n sessionStorage.removeItem(key);\n }\n\n return keysToRemove.length;\n}\n\n/**\n * Get all quiz data keys from sessionStorage\n *\n * @returns Array of keys with 'qd/' prefix\n */\nexport function getQuizDataKeys(): string[] {\n const keys: string[] = [];\n\n for (let i = 0; i < sessionStorage.length; i++) {\n const key = sessionStorage.key(i);\n if (key && key.startsWith('qd/')) {\n keys.push(key);\n }\n }\n\n return keys;\n}\n\n/**\n * Clear all sessionStorage data\n *\n * Use with caution - clears everything, not just quiz data.\n */\nexport function clearAll(): void {\n sessionStorage.clear();\n}\n","/**\n * Storage Adapter Utilities\n *\n * Provides utility functions for working with storage keys, validation,\n * and error types for the storage layer.\n *\n * Storage Key Format: qd/{release}/u{serviceId}\n * Example: qd/11-2024/uRN2344\n */\n\nimport type { StudentRecord, ReleaseId, ServiceId } from '../../types/contracts.js';\nimport { error as logError } from '../../utils/logger.js';\n\n/**\n * Generate storage key for a student record\n *\n * Format: qd/{release}/u{serviceId}\n *\n * @param release - Release identifier (e.g., \"01-2025\")\n * @param serviceId - Service ID (e.g., \"RN2344\")\n * @returns Storage key string\n *\n * @example\n * ```typescript\n * const key = getStorageKey('11-2024', 'RN2344');\n * // Returns: \"qd/11-2024/uRN2344\"\n * ```\n */\nexport function getStorageKey(release: ReleaseId, serviceId: ServiceId): string {\n return `qd/${release}/u${serviceId}`;\n}\n\n/**\n * Parse a storage key back into its components\n *\n * @param key - Storage key to parse\n * @returns Object with release and serviceId, or null if invalid\n *\n * @example\n * ```typescript\n * const parts = parseStorageKey('qd/11-2024/uRN2344');\n * // Returns: { release: '11-2024', serviceId: 'RN2344' }\n * ```\n */\nexport function parseStorageKey(key: string): { release: ReleaseId; serviceId: ServiceId } | null {\n const match = key.match(/^qd\\/([^/]+)\\/u(.+)$/);\n if (!match || !match[1] || !match[2]) {\n return null;\n }\n return {\n release: match[1],\n serviceId: match[2],\n };\n}\n\n/**\n * Validate release ID format (MM-YYYY)\n *\n * @param release - Release ID to validate\n * @returns True if valid, false otherwise\n *\n * @example\n * ```typescript\n * isValidReleaseId('11-2024'); // true\n * isValidReleaseId('2024-11'); // false\n * isValidReleaseId('13-2024'); // false (month > 12)\n * ```\n */\nexport function isValidReleaseId(release: string): boolean {\n const match = release.match(/^(\\d{2})-(\\d{4})$/);\n if (!match || !match[1] || !match[2]) {\n return false;\n }\n\n // Validate month range (01-12)\n const month = parseInt(match[1], 10);\n return month >= 1 && month <= 12;\n}\n\n/**\n * Validate service ID format (2-10 alphanumeric characters)\n *\n * @param serviceId - Service ID to validate\n * @returns True if valid, false otherwise\n *\n * @example\n * ```typescript\n * isValidServiceId('RN2344'); // true\n * isValidServiceId('AB'); // true (minimum 2 chars)\n * isValidServiceId('A'); // false (too short)\n * isValidServiceId('ABCDEFGHIJK'); // false (too long)\n * ```\n */\nexport function isValidServiceId(serviceId: string): boolean {\n return /^[A-Za-z0-9]{2,10}$/.test(serviceId);\n}\n\n/**\n * Create a default empty StudentRecord\n *\n * @param release - Release identifier\n * @param serviceId - Service identifier\n * @param name - Student name\n * @param docId - Document identifier\n * @returns New StudentRecord with default values\n *\n * @example\n * ```typescript\n * const record = createEmptyStudentRecord('11-2024', 'RN2344', 'Alice Student', 'doc-123');\n * // Returns StudentRecord with empty pages, 0 scores, current timestamp\n * ```\n */\nexport function createEmptyStudentRecord(\n release: ReleaseId,\n serviceId: ServiceId,\n name: string,\n docId: string,\n): StudentRecord {\n return {\n schema: 1,\n docId,\n release,\n serviceId,\n name,\n attempted: 0,\n correct: 0,\n updated: new Date().toISOString(),\n pages: {},\n };\n}\n\n/**\n * Storage adapter error types\n */\nexport class StorageError extends Error {\n constructor(\n message: string,\n public readonly operation: string,\n public readonly cause?: Error,\n ) {\n super(message);\n this.name = 'StorageError';\n\n // Log error for debugging\n if (cause) {\n logError(`Storage error in ${operation}: ${message}`, cause);\n } else {\n logError(`Storage error in ${operation}: ${message}`);\n }\n }\n}\n\n/**\n * Error thrown when storage is not initialized\n */\nexport class StorageNotInitializedError extends StorageError {\n constructor(operation: string) {\n super('Storage adapter not initialized. Call init() first.', operation);\n this.name = 'StorageNotInitializedError';\n }\n}\n\n/**\n * Error thrown when a storage operation times out\n */\nexport class StorageTimeoutError extends StorageError {\n constructor(operation: string, timeout: number) {\n super(`Storage operation timed out after ${timeout}ms`, operation);\n this.name = 'StorageTimeoutError';\n }\n}\n\n/**\n * Error thrown when storage quota is exceeded\n */\nexport class StorageQuotaError extends StorageError {\n constructor(operation: string) {\n super('Storage quota exceeded. Please clear old data or free up space.', operation);\n this.name = 'StorageQuotaError';\n }\n}\n","/**\n * IndexedDB Storage Adapter Implementation\n *\n * Provides persistent storage for student records using browser IndexedDB.\n * Implements atomic transactions and proper error handling.\n *\n * Database: Configured via #qd-db-name element (REQUIRED)\n * Stores: students (main data), backups (backup copies)\n * Keys: qd/{release}/u{serviceId}\n */\n\nimport type {\n StorageAdapter,\n StudentRecord,\n ReleaseId,\n ServiceId,\n PinResetEvent,\n} from '../../types/contracts.js';\nimport {\n getStorageKey,\n StorageNotInitializedError,\n StorageError,\n StorageQuotaError,\n} from './adapter-utils.js';\nimport { warn as logWarn, error as logError } from '../../utils/logger.js';\n\n// NOTE: No default database name - must be provided by caller\n\n/** Database version - increment to force schema upgrade */\nconst DB_VERSION = 3;\n\n/** Object store names */\nconst STORE_STUDENTS = 'students';\nconst STORE_BACKUPS = 'backups';\nconst STORE_AUDIT_LOG = 'auditLog';\n\n/**\n * Backup record with metadata\n */\ninterface BackupRecord extends StudentRecord {\n /** Original storage key */\n originalKey: string;\n /** Backup timestamp */\n timestamp: string;\n}\n\n/**\n * IndexedDB implementation of StorageAdapter\n *\n * Features:\n * - Automatic schema creation with indexes\n * - Atomic transactions\n * - Quota error handling\n * - Backup functionality\n */\nexport class IndexedDBStorageAdapter implements StorageAdapter {\n private db: IDBDatabase | null = null;\n private initPromise: Promise | null = null;\n private dbName: string;\n\n /**\n * Create a new IndexedDB storage adapter\n *\n * @param dbName - Database name (REQUIRED - no default)\n */\n constructor(dbName: string) {\n if (!dbName) {\n throw new Error('FATAL: dbName is required for IndexedDBStorageAdapter');\n }\n this.dbName = dbName;\n }\n\n /**\n * Initialize the IndexedDB database\n *\n * Creates object stores and indexes on first run.\n * Safe to call multiple times - will reuse existing connection.\n *\n * @returns Promise that resolves when database is ready\n */\n async init(): Promise {\n // Return existing initialization promise if already in progress\n if (this.initPromise) {\n return this.initPromise;\n }\n\n // If already initialized, return immediately\n if (this.db) {\n return Promise.resolve();\n }\n\n this.initPromise = new Promise((resolve, reject) => {\n // Timeout for hung database operations\n const OPEN_TIMEOUT_MS = 5000;\n let timeoutId: number | undefined;\n let resolved = false;\n\n const cleanup = () => {\n if (timeoutId) {\n clearTimeout(timeoutId);\n timeoutId = undefined;\n }\n };\n\n timeoutId = window.setTimeout(() => {\n if (resolved) return;\n resolved = true;\n this.initPromise = null;\n\n logWarn(`IndexedDB open timed out after ${OPEN_TIMEOUT_MS}ms - attempting recovery`);\n\n // Try to delete and recreate\n const deleteReq = indexedDB.deleteDatabase(this.dbName);\n deleteReq.onsuccess = () => {\n this.init().then(resolve).catch(reject);\n };\n deleteReq.onerror = () => {\n reject(\n new StorageError(\n `Database \"${this.dbName}\" appears corrupted. Please clear site data in browser settings.`,\n 'init',\n ),\n );\n };\n deleteReq.onblocked = () => {\n reject(\n new StorageError(\n `Cannot recover database - close all other tabs with this site and reload.`,\n 'init',\n ),\n );\n };\n }, OPEN_TIMEOUT_MS);\n\n const request = indexedDB.open(this.dbName, DB_VERSION);\n\n request.onerror = () => {\n if (resolved) return;\n resolved = true;\n cleanup();\n logError(`IndexedDB open error: ${request.error?.message || 'unknown'}`);\n this.initPromise = null;\n reject(new StorageError('Failed to open database', 'init', request.error as Error));\n };\n\n request.onblocked = () => {\n logWarn('IndexedDB open blocked - close other tabs with this database');\n };\n\n request.onsuccess = () => {\n if (resolved) return;\n resolved = true;\n cleanup();\n\n this.db = request.result;\n\n // Verify object stores exist - if not, database is corrupted\n if (\n !this.db.objectStoreNames.contains(STORE_STUDENTS) ||\n !this.db.objectStoreNames.contains(STORE_BACKUPS) ||\n !this.db.objectStoreNames.contains(STORE_AUDIT_LOG)\n ) {\n // Database exists but stores missing - delete and recreate\n logWarn(\n `Database corrupted (missing stores). Found: [${Array.from(this.db.objectStoreNames).join(', ')}]`,\n );\n this.db.close();\n this.db = null;\n\n // Delete corrupted database\n const deleteRequest = indexedDB.deleteDatabase(this.dbName);\n deleteRequest.onsuccess = () => {\n // Retry initialization\n this.initPromise = null;\n this.init().then(resolve).catch(reject);\n };\n deleteRequest.onerror = () => {\n this.initPromise = null;\n reject(\n new StorageError(\n 'Failed to delete corrupted database',\n 'init',\n deleteRequest.error as Error,\n ),\n );\n };\n return;\n }\n\n this.initPromise = null;\n resolve();\n };\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result;\n const transaction = (event.target as IDBOpenDBRequest).transaction;\n\n if (transaction) {\n transaction.onerror = () => {\n logError(`Upgrade transaction error: ${transaction.error?.message || 'unknown'}`);\n };\n transaction.onabort = () => {\n logError(`Upgrade transaction aborted: ${transaction.error?.message || 'unknown'}`);\n };\n }\n\n try {\n // Create students object store\n if (!db.objectStoreNames.contains(STORE_STUDENTS)) {\n const studentsStore = db.createObjectStore(STORE_STUDENTS, { keyPath: null });\n studentsStore.createIndex('by-release', 'release', { unique: false });\n studentsStore.createIndex('by-service-id', 'serviceId', { unique: false });\n }\n\n // Create backups object store\n if (!db.objectStoreNames.contains(STORE_BACKUPS)) {\n const backupsStore = db.createObjectStore(STORE_BACKUPS, { keyPath: null });\n backupsStore.createIndex('by-original-key', 'originalKey', { unique: false });\n backupsStore.createIndex('by-timestamp', 'timestamp', { unique: false });\n }\n\n // Create audit log object store (v3 - PIN reset events)\n if (!db.objectStoreNames.contains(STORE_AUDIT_LOG)) {\n const auditStore = db.createObjectStore(STORE_AUDIT_LOG, {\n keyPath: 'eventId',\n });\n auditStore.createIndex('by-service-id', 'serviceId', { unique: false });\n auditStore.createIndex('by-reset-at', 'resetAt', { unique: false });\n }\n } catch (err) {\n logError('Error during database upgrade', err as Error);\n throw err;\n }\n };\n });\n\n return this.initPromise;\n }\n\n /**\n * Ensure database is initialized before operations\n *\n * @throws StorageNotInitializedError if not initialized\n * @returns Database instance\n */\n private ensureInitialized(): IDBDatabase {\n if (!this.db) {\n throw new StorageNotInitializedError('ensureInitialized');\n }\n return this.db;\n }\n\n /**\n * Get a student record by release and service ID\n *\n * @param release - Release identifier\n * @param serviceId - Service identifier\n * @returns Student record or null if not found\n */\n async getStudent(release: ReleaseId, serviceId: ServiceId): Promise {\n const db = this.ensureInitialized();\n const key = getStorageKey(release, serviceId);\n\n return new Promise((resolve, reject) => {\n try {\n const transaction = db.transaction(STORE_STUDENTS, 'readonly');\n const store = transaction.objectStore(STORE_STUDENTS);\n const request = store.get(key);\n\n request.onsuccess = () => {\n resolve((request.result as StudentRecord | undefined) || null);\n };\n\n request.onerror = () => {\n reject(\n new StorageError('Failed to get student record', 'getStudent', request.error as Error),\n );\n };\n } catch (error) {\n reject(new StorageError('Failed to get student record', 'getStudent', error as Error));\n }\n });\n }\n\n /**\n * Save a student record\n *\n * @param record - Student record to save\n * @throws StorageQuotaError if storage quota exceeded\n */\n async saveStudent(record: StudentRecord): Promise {\n const db = this.ensureInitialized();\n const key = getStorageKey(record.release, record.serviceId);\n\n return new Promise((resolve, reject) => {\n try {\n const transaction = db.transaction(STORE_STUDENTS, 'readwrite');\n const store = transaction.objectStore(STORE_STUDENTS);\n const request = store.put(record, key);\n\n request.onsuccess = () => {\n resolve();\n };\n\n request.onerror = () => {\n // Check for quota errors\n if (request.error?.name === 'QuotaExceededError') {\n reject(new StorageQuotaError('saveStudent'));\n } else {\n reject(\n new StorageError(\n 'Failed to save student record',\n 'saveStudent',\n request.error as Error,\n ),\n );\n }\n };\n\n transaction.onerror = () => {\n reject(\n new StorageError(\n 'Transaction failed while saving student',\n 'saveStudent',\n transaction.error as Error,\n ),\n );\n };\n } catch (error) {\n reject(new StorageError('Failed to save student record', 'saveStudent', error as Error));\n }\n });\n }\n\n /**\n * Get all students for a specific release\n *\n * Uses the by-release index for efficient queries.\n *\n * @param release - Release identifier\n * @returns Array of student records (empty if none found)\n */\n async getStudentsByRelease(release: ReleaseId): Promise {\n const db = this.ensureInitialized();\n\n return new Promise((resolve, reject) => {\n try {\n const transaction = db.transaction(STORE_STUDENTS, 'readonly');\n const store = transaction.objectStore(STORE_STUDENTS);\n const index = store.index('by-release');\n const request = index.getAll(release);\n\n request.onsuccess = () => {\n resolve(request.result || []);\n };\n\n request.onerror = () => {\n reject(\n new StorageError(\n 'Failed to get students by release',\n 'getStudentsByRelease',\n request.error as Error,\n ),\n );\n };\n } catch (error) {\n reject(\n new StorageError(\n 'Failed to get students by release',\n 'getStudentsByRelease',\n error as Error,\n ),\n );\n }\n });\n }\n\n /**\n * Clear all data from the database\n *\n * Removes both students and backups in a single atomic transaction.\n */\n async clearAll(): Promise {\n const db = this.ensureInitialized();\n\n return new Promise((resolve, reject) => {\n try {\n const transaction = db.transaction(\n [STORE_STUDENTS, STORE_BACKUPS, STORE_AUDIT_LOG],\n 'readwrite',\n );\n\n const studentsStore = transaction.objectStore(STORE_STUDENTS);\n const backupsStore = transaction.objectStore(STORE_BACKUPS);\n const auditStore = transaction.objectStore(STORE_AUDIT_LOG);\n\n const clearStudentsRequest = studentsStore.clear();\n const clearBackupsRequest = backupsStore.clear();\n const clearAuditRequest = auditStore.clear();\n\n let studentsCleared = false;\n let backupsCleared = false;\n let auditCleared = false;\n\n clearStudentsRequest.onsuccess = () => {\n studentsCleared = true;\n if (backupsCleared && auditCleared) {\n resolve();\n }\n };\n\n clearBackupsRequest.onsuccess = () => {\n backupsCleared = true;\n if (studentsCleared && auditCleared) {\n resolve();\n }\n };\n\n clearAuditRequest.onsuccess = () => {\n auditCleared = true;\n if (studentsCleared && backupsCleared) {\n resolve();\n }\n };\n\n clearStudentsRequest.onerror = () => {\n reject(\n new StorageError(\n 'Failed to clear students',\n 'clearAll',\n clearStudentsRequest.error as Error,\n ),\n );\n };\n\n clearBackupsRequest.onerror = () => {\n reject(\n new StorageError(\n 'Failed to clear backups',\n 'clearAll',\n clearBackupsRequest.error as Error,\n ),\n );\n };\n\n clearAuditRequest.onerror = () => {\n reject(\n new StorageError(\n 'Failed to clear audit log',\n 'clearAll',\n clearAuditRequest.error as Error,\n ),\n );\n };\n\n transaction.onerror = () => {\n reject(\n new StorageError(\n 'Transaction failed during clearAll',\n 'clearAll',\n transaction.error as Error,\n ),\n );\n };\n } catch (error) {\n reject(new StorageError('Failed to clear all data', 'clearAll', error as Error));\n }\n });\n }\n\n /**\n * Create a backup of a student record\n *\n * Backup key format: backup_{timestamp}_{serviceId}\n *\n * @param record - Student record to backup\n * @throws StorageQuotaError if storage quota exceeded\n */\n async backup(record: StudentRecord): Promise {\n const db = this.ensureInitialized();\n const timestamp = new Date().toISOString();\n const backupKey = `backup_${timestamp}_${record.serviceId}`;\n const originalKey = getStorageKey(record.release, record.serviceId);\n\n const backupRecord: BackupRecord = {\n ...record,\n originalKey,\n timestamp,\n };\n\n return new Promise((resolve, reject) => {\n try {\n const transaction = db.transaction(STORE_BACKUPS, 'readwrite');\n const store = transaction.objectStore(STORE_BACKUPS);\n const request = store.put(backupRecord, backupKey);\n\n request.onsuccess = () => {\n resolve();\n };\n\n request.onerror = () => {\n // Check for quota errors\n if (request.error?.name === 'QuotaExceededError') {\n reject(new StorageQuotaError('backup'));\n } else {\n reject(new StorageError('Failed to create backup', 'backup', request.error as Error));\n }\n };\n\n transaction.onerror = () => {\n reject(\n new StorageError(\n 'Transaction failed during backup',\n 'backup',\n transaction.error as Error,\n ),\n );\n };\n } catch (error) {\n reject(new StorageError('Failed to create backup', 'backup', error as Error));\n }\n });\n }\n\n /**\n * Save a PIN reset event to the audit log\n *\n * @param event - PIN reset event to log\n */\n async saveAuditEvent(event: PinResetEvent): Promise {\n const db = this.ensureInitialized();\n\n return new Promise((resolve, reject) => {\n try {\n const transaction = db.transaction(STORE_AUDIT_LOG, 'readwrite');\n const store = transaction.objectStore(STORE_AUDIT_LOG);\n const request = store.add(event);\n\n request.onsuccess = () => {\n resolve();\n };\n\n request.onerror = () => {\n reject(\n new StorageError(\n 'Failed to save audit event',\n 'saveAuditEvent',\n request.error as Error,\n ),\n );\n };\n } catch (error) {\n reject(new StorageError('Failed to save audit event', 'saveAuditEvent', error as Error));\n }\n });\n }\n\n /**\n * Close the database connection\n *\n * Useful for cleanup in tests and application shutdown.\n */\n close(): void {\n if (this.db) {\n this.db.close();\n this.db = null;\n this.initPromise = null;\n }\n }\n}\n\n/**\n * Singleton storage adapter instance\n */\nlet storageInstance: IndexedDBStorageAdapter | null = null;\nlet currentDbName: string | null = null;\n\n/**\n * Get the singleton storage adapter instance\n *\n * Creates a new instance on first call, reuses it thereafter.\n * If dbName changes, closes old instance and creates new one.\n *\n * @param dbName - Database name (REQUIRED - no default)\n * @returns IndexedDB storage adapter\n */\nexport function getStorageAdapter(dbName: string): IndexedDBStorageAdapter {\n if (!dbName) {\n throw new Error('FATAL: dbName is required for getStorageAdapter()');\n }\n\n // If dbName changed, close old instance and create new one\n if (storageInstance && currentDbName !== dbName) {\n storageInstance.close();\n storageInstance = null;\n }\n\n if (!storageInstance) {\n storageInstance = new IndexedDBStorageAdapter(dbName);\n currentDbName = dbName;\n }\n return storageInstance;\n}\n\n/**\n * Reset the singleton instance\n *\n * Useful for testing to ensure clean state between tests.\n */\nexport function resetStorageAdapter(): void {\n if (storageInstance) {\n storageInstance.close();\n storageInstance = null;\n currentDbName = null;\n }\n}\n","/**\n * Completion State Calculator\n *\n * Functions for calculating page completion states based on answer data.\n *\n * State Rules (from CLAUDE.md):\n * - unstarted: No answers provided\n * - incomplete: Some answered OR any incorrect\n * - complete: All answered AND all correct\n */\n\nimport type { AnswerRecord, CompletionState } from '../types/contracts.js';\n\n/**\n * Calculate the completion state for a page\n *\n * @param answers - Array of answer records for the page\n * @param totalQuestions - Total number of questions on the page\n * @returns Completion state (unstarted | incomplete | complete)\n *\n * @example\n * ```typescript\n * const answers = [\n * { answer: 'a', success: true, timestamp: '2024-11-16T10:00:00Z' },\n * { answer: 'b', success: false, timestamp: '2024-11-16T10:01:00Z' },\n * ];\n * const state = calculateCompletionState(answers, 3); // 'incomplete' (not all answered)\n * ```\n */\nexport function calculateCompletionState(\n answers: AnswerRecord[],\n totalQuestions: number,\n): CompletionState {\n // Handle edge case: no questions\n if (totalQuestions === 0) {\n return 'unstarted';\n }\n\n // Check if unstarted\n if (isPageUnstarted(answers)) {\n return 'unstarted';\n }\n\n // Check if complete\n if (isPageComplete(answers, totalQuestions)) {\n return 'complete';\n }\n\n // Otherwise, it's incomplete\n return 'incomplete';\n}\n\n/**\n * Check if a page is complete\n *\n * A page is complete when:\n * - All questions are answered\n * - All answered questions are correct\n *\n * @param answers - Array of answer records\n * @param totalQuestions - Total number of questions\n * @returns True if page is complete\n */\nexport function isPageComplete(answers: AnswerRecord[], totalQuestions: number): boolean {\n // Must have answered all questions\n if (answers.length !== totalQuestions) {\n return false;\n }\n\n // All answers must be correct\n return answers.every((answer) => answer.success === true);\n}\n\n/**\n * Check if a page is unstarted\n *\n * A page is unstarted when no answers have been provided.\n *\n * @param answers - Array of answer records\n * @returns True if page is unstarted\n */\nexport function isPageUnstarted(answers: AnswerRecord[]): boolean {\n return answers.length === 0;\n}\n\n/**\n * Count the number of correct answers\n *\n * @param answers - Array of answer records\n * @returns Number of correct answers\n */\nexport function countCorrectAnswers(answers: AnswerRecord[]): number {\n return answers.filter((answer) => answer.success === true).length;\n}\n\n/**\n * Calculate success percentage\n *\n * @param answers - Array of answer records\n * @param totalQuestions - Total number of questions\n * @returns Percentage of correct answers (0-100)\n *\n * @example\n * ```typescript\n * const answers = [\n * { answer: 'a', success: true, timestamp: '...' },\n * { answer: 'b', success: false, timestamp: '...' },\n * { answer: 'c', success: true, timestamp: '...' },\n * ];\n * const percentage = calculateSuccessPercentage(answers, 3); // 67 (2 out of 3 correct)\n * ```\n */\nexport function calculateSuccessPercentage(\n answers: AnswerRecord[],\n totalQuestions: number,\n): number {\n if (totalQuestions === 0) {\n return 0;\n }\n\n const correct = countCorrectAnswers(answers);\n return Math.round((correct / totalQuestions) * 100);\n}\n","/**\n * Storage Service\n *\n * Coordinates between IndexedDB persistence and sessionStorage cache.\n * Provides high-level operations for loading/saving student records.\n */\n\nimport type {\n StudentRecord,\n SessionData,\n SessionCache,\n PageData,\n PageId,\n ReleaseId,\n AnswerRecord,\n} from '../types/contracts.js';\nimport { getStorageAdapter } from './storage/indexeddb.js';\nimport { buildCacheFromRecord } from './session.js';\nimport { calculateCompletionState } from './state-calculator.js';\nimport { recalculateTotalsFromPages } from '../utils/calculation-helpers.js';\nimport { info, warn, error as logError } from '../utils/logger.js';\n\n/**\n * Storage Service for managing student records\n */\nexport class StorageService {\n private adapter;\n private dbName: string;\n\n /**\n * Create storage service with specified database name\n *\n * @param dbName - IndexedDB database name (REQUIRED - no default)\n */\n constructor(dbName: string) {\n if (!dbName) {\n throw new Error('FATAL: dbName is required for StorageService');\n }\n this.dbName = dbName;\n this.adapter = getStorageAdapter(dbName);\n }\n\n /**\n * Initialize IndexedDB storage\n */\n async init(): Promise {\n try {\n await this.adapter.init();\n info(`Storage service initialized (IndexedDB \"${this.dbName}\" ready)`);\n } catch (err) {\n logError('Failed to initialize storage service', err as Error);\n throw err;\n }\n }\n\n /**\n * Load student record from IndexedDB\n *\n * Creates a new record if none exists.\n *\n * @param session - Current session data\n * @returns Student record\n */\n async loadStudentRecord(session: SessionData): Promise {\n try {\n const existing = await this.adapter.getStudent(session.release, session.serviceId);\n\n if (existing) {\n info(`Loaded student record for ${session.serviceId} from IndexedDB`);\n return existing;\n }\n\n // Create new student record\n const newRecord: StudentRecord = {\n schema: 1,\n docId: session.release, // Use release as docId\n release: session.release,\n serviceId: session.serviceId,\n name: session.name,\n attempted: 0,\n correct: 0,\n updated: new Date().toISOString(),\n pages: {},\n };\n\n info(`Created new student record for ${session.serviceId}`);\n return newRecord;\n } catch (err) {\n // If IndexedDB has schema issues, create a new record\n warn(`IndexedDB error, creating new record: ${(err as Error).message}`);\n const newRecord: StudentRecord = {\n schema: 1,\n docId: session.release,\n release: session.release,\n serviceId: session.serviceId,\n name: session.name,\n attempted: 0,\n correct: 0,\n updated: new Date().toISOString(),\n pages: {},\n };\n return newRecord;\n }\n }\n\n /**\n * Save student record to IndexedDB\n *\n * @param record - Student record to save\n */\n async saveStudentRecord(record: StudentRecord): Promise {\n try {\n // Update timestamp\n record.updated = new Date().toISOString();\n\n // Recalculate totals from pages using calculation helper\n const totals = recalculateTotalsFromPages(record.pages);\n record.attempted = totals.attempted;\n record.correct = totals.correct;\n\n await this.adapter.saveStudent(record);\n info(`Saved student record for ${record.serviceId} to IndexedDB`);\n } catch (err) {\n logError('Failed to save student record', err as Error);\n throw err;\n }\n }\n\n /**\n * Update student record with a new answer\n *\n * @param record - Current student record\n * @param pageId - Page where answer was submitted\n * @param questionIndex - Question index (0-based)\n * @param answer - Answer record\n * @param totalQuestions - Total questions on the page\n * @returns Updated student record\n */\n updateRecordWithAnswer(\n record: StudentRecord,\n pageId: PageId,\n questionIndex: number,\n answer: AnswerRecord,\n totalQuestions: number,\n ): StudentRecord {\n // Get or create page data\n const existingPage = record.pages[pageId];\n const pageData: PageData = existingPage || {\n answers: [],\n state: 'unstarted',\n };\n\n // Ensure answers array is large enough\n while (pageData.answers.length <= questionIndex) {\n pageData.answers.push({\n answer: '',\n success: false,\n timestamp: new Date().toISOString(),\n });\n }\n\n // Update answer at index (FR-015: overwrites previous answer for re-submissions)\n // Only the most recent answer is stored, with updated timestamp\n pageData.answers[questionIndex] = answer;\n\n // Update timestamps\n const now = new Date().toISOString();\n if (!pageData.firstAttempted) {\n pageData.firstAttempted = now;\n }\n pageData.lastAttempted = now;\n\n // Recalculate state\n pageData.state = calculateCompletionState(pageData.answers, totalQuestions);\n\n // Update record\n return {\n ...record,\n pages: {\n ...record.pages,\n [pageId]: pageData,\n },\n };\n }\n\n /**\n * Build session cache from student record\n *\n * @param record - Student record\n * @returns Session cache\n */\n buildCache(record: StudentRecord): SessionCache {\n return buildCacheFromRecord(record);\n }\n\n /**\n * Get all students for a release\n *\n * @param release - Release identifier\n * @returns Array of student records\n */\n async getStudentsByRelease(release: ReleaseId): Promise {\n try {\n return await this.adapter.getStudentsByRelease(release);\n } catch (err) {\n logError('Failed to get students by release', err as Error);\n throw err;\n }\n }\n\n /**\n * Clear all data from IndexedDB\n */\n async clearAll(): Promise {\n try {\n await this.adapter.clearAll();\n info('Cleared all data from IndexedDB');\n } catch (err) {\n logError('Failed to clear all data', err as Error);\n throw err;\n }\n }\n\n /**\n * Create backup of student record\n *\n * @param record - Student record to backup\n */\n async backup(record: StudentRecord): Promise {\n try {\n await this.adapter.backup(record);\n info(`Created backup for ${record.serviceId}`);\n } catch (err) {\n warn(`Failed to create backup for ${record.serviceId}`, err);\n }\n }\n}\n\n// ============================================================================\n// SINGLETON PATTERN\n// ============================================================================\n\nlet storageServiceInstance: StorageService | null = null;\nlet currentServiceDbName: string | null = null;\n\n/**\n * Get singleton storage service instance\n *\n * @param dbName - IndexedDB database name (optional, uses existing instance if available)\n */\nexport function getStorageService(dbName?: string): StorageService {\n // If instance exists and no dbName specified, return existing\n if (storageServiceInstance && !dbName) {\n return storageServiceInstance;\n }\n\n // If dbName specified and different, warn but return existing (don't break app)\n if (storageServiceInstance && dbName && currentServiceDbName !== dbName) {\n warn(\n `Storage service already initialized with dbName=\"${currentServiceDbName}\", ignoring new dbName=\"${dbName}\"`,\n );\n return storageServiceInstance;\n }\n\n // Create new instance if none exists\n if (!storageServiceInstance) {\n if (!dbName) {\n throw new Error('FATAL: dbName is required for first getStorageService() call');\n }\n storageServiceInstance = new StorageService(dbName);\n currentServiceDbName = dbName;\n }\n\n return storageServiceInstance;\n}\n\n/**\n * Reset singleton (for testing)\n */\nexport function resetStorageService(): void {\n storageServiceInstance = null;\n currentServiceDbName = null;\n}\n","/**\n * Quiz Table Enhancer\n *\n * Implements single-phase progressive enhancement for quiz tables.\n * Replaces the old two-phase (prepare/activate) pattern with a simpler\n * conditional approach based on interactive flag.\n *\n * Features:\n * - Non-interactive mode: Hide answer column for security\n * - Interactive mode: Inject input controls, validation, auto-save\n * - Uses WeakMap for metadata (not DOM attributes)\n * - Debounced auto-save to prevent excessive writes\n * - Event emission for state changes\n */\n\nimport type {\n ParsedQuizTable,\n QuizQuestion,\n AnswerRecord,\n PageId,\n SessionData,\n SessionCache,\n} from '../types/contracts.js';\nimport { parseQuizTable } from '../services/quiz-parser.js';\nimport { validateAnswer } from '../services/quiz-parser.js';\nimport { registerPageQuestions } from '../services/session.js';\nimport { getQuestionInputSpec } from '../services/question-input.js';\nimport { formatStudentAnswersForDisplay } from '../services/answer-display.js';\nimport { Debouncer } from '../utils/debouncer.js';\nimport { createElement, addClass, removeClass } from '../utils/dom-helpers.js';\nimport { emitCustomEvent } from '../utils/event-helpers.js';\nimport { getJSON, setJSON } from '../utils/storage-helpers.js';\nimport { STORAGE_KEYS } from '../types/contracts.js';\nimport { info, error as logError, warn } from '../utils/logger.js';\nimport { getStorageService } from '../services/storage-service.js';\n\n/**\n * Enhancement options\n */\nexport interface EnhanceQuizTableOptions {\n /** Whether to enable interactive controls */\n interactive: boolean;\n /** Current page ID (required for interactive mode) */\n pageId?: PageId;\n}\n\n/**\n * Quiz table metadata (stored in WeakMap)\n */\ninterface QuizTableMetadata {\n /** Parsed quiz data */\n parsed: ParsedQuizTable;\n /** Enhancement mode */\n interactive: boolean;\n /** Page ID (if interactive) */\n pageId?: PageId;\n /** Row input elements (if interactive) - can be text inputs or select dropdowns */\n inputs?: (HTMLInputElement | HTMLSelectElement)[];\n /** Debouncer for auto-save */\n debouncer?: Debouncer;\n /** Cleanup function for instructor event listeners */\n cleanupInstructorListeners?: () => void;\n}\n\n// WeakMap to store table metadata without polluting DOM\nconst tableMetadata = new WeakMap();\n\n/**\n * Enhance a quiz table with single-phase enhancement\n *\n * @param table - The quiz table element\n * @param options - Enhancement options\n * @returns true if enhancement succeeded, false if errors occurred\n *\n * @example\n * ```typescript\n * // Non-interactive mode (hide answers)\n * const table = document.querySelector('table.qd-quiz');\n * if (table) {\n * enhanceQuizTable(table, { interactive: false });\n * }\n *\n * // Interactive mode (inject controls)\n * enhanceQuizTable(table, { interactive: true, pageId: 'gram-1' });\n * ```\n */\nexport function enhanceQuizTable(\n table: HTMLTableElement,\n options: EnhanceQuizTableOptions,\n): boolean {\n // Check if already enhanced\n const existing = tableMetadata.get(table);\n let parsed: ParsedQuizTable;\n\n if (existing) {\n // If upgrading from non-interactive to interactive, proceed\n if (!existing.interactive && options.interactive) {\n info('Upgrading quiz table from non-interactive to interactive mode');\n // Reuse existing parsed data (answers already extracted before clearing DOM)\n parsed = existing.parsed;\n } else {\n // Already enhanced in same or higher mode, skip\n info('Quiz table already enhanced, skipping');\n return true;\n }\n } else {\n // Parse the table (first enhancement)\n parsed = parseQuizTable(table);\n\n // Check for parsing errors\n if (parsed.errors && parsed.errors.length > 0) {\n logError('Quiz table has validation errors:', parsed.errors);\n // Still continue enhancement to show errors visually\n }\n }\n\n // Store metadata in WeakMap\n const metadata: QuizTableMetadata = {\n parsed,\n interactive: options.interactive,\n pageId: options.pageId,\n };\n\n if (options.interactive) {\n // Validate pageId is provided for interactive mode\n if (!options.pageId) {\n logError('Interactive mode requires pageId option');\n return false;\n }\n\n info(`Preparing interactive enhancement for pageId: ${options.pageId}`);\n\n // Initialize debouncer for auto-save\n metadata.debouncer = new Debouncer();\n metadata.inputs = [];\n }\n\n tableMetadata.set(table, metadata);\n\n // Apply enhancement based on mode\n if (options.interactive) {\n const result = enhanceInteractive(table, metadata);\n if (result) {\n info(`Interactive enhancement succeeded for table with ${parsed.questions.length} questions`);\n } else {\n logError('Interactive enhancement failed');\n }\n return result;\n } else {\n return enhanceNonInteractive(table);\n }\n}\n\n/**\n * Enhance table in non-interactive mode\n * - Hide answer column (security: don't show correct answers before login)\n * - Hide detail column (security: don't show MCQ options or tolerances before login)\n *\n * @param table - Quiz table element\n * @returns true if successful\n */\nfunction enhanceNonInteractive(table: HTMLTableElement): boolean {\n // Remove colgroup to allow auto-sizing of columns\n removeColgroup(table);\n\n // Hide answer column (column index 1) - security: hide correct answers before login\n hideAnswerColumn(table);\n\n // Hide detail column (column index 2) - security: hide MCQ options/tolerances\n hideDetailColumn(table);\n\n addClass(table, 'qd-quiz-non-interactive');\n info('Quiz table enhanced in non-interactive mode');\n\n return true;\n}\n\n/**\n * Enhance table in interactive mode\n * - Inject input controls for each question\n * - Setup validation and auto-save\n * - Load existing answers from storage\n *\n * @param table - Quiz table element\n * @param metadata - Table metadata\n * @returns true if successful\n */\nfunction enhanceInteractive(table: HTMLTableElement, metadata: QuizTableMetadata): boolean {\n const { parsed, pageId, debouncer } = metadata;\n\n if (!pageId || !debouncer) {\n logError('Interactive mode requires pageId and debouncer');\n return false;\n }\n\n // Show answer column (remove qd-hidden class from non-interactive mode)\n showAnswerColumn(table);\n\n // Hide detail column in interactive mode\n // - MCQ options are now in the select dropdown\n // - Numeric tolerance is applied automatically\n hideDetailColumn(table);\n\n // Get session data\n const session = getJSON(STORAGE_KEYS.SESSION);\n if (!session) {\n logError('No active session found');\n return false;\n }\n\n // Get session cache\n let cache = getJSON(STORAGE_KEYS.CACHE);\n if (!cache) {\n info('No cache found, creating empty cache');\n cache = {\n totals: { total: 0, answered: 0, correct: 0 },\n pages: {},\n };\n } else {\n info(\n `Cache loaded: ${cache.totals.total} total questions, ${Object.keys(cache.pages).length} pages`,\n );\n }\n\n // Register page questions (updates total count in cache)\n const totalQuestions = parsed.questions.length;\n cache = registerPageQuestions(cache, pageId, totalQuestions);\n setJSON(STORAGE_KEYS.CACHE, cache);\n\n const pageCache = cache?.pages[pageId];\n const existingAnswers = pageCache?.answers || [];\n info(\n `Page ${pageId}: ${existingAnswers.length} existing answers, state: ${pageCache?.state || 'none'}`,\n );\n\n // Get all tbody rows\n const tbody = table.querySelector('tbody');\n if (!tbody) {\n logError('Quiz table has no tbody element');\n return false;\n }\n\n const rows = Array.from(tbody.querySelectorAll('tr'));\n const inputs: (HTMLInputElement | HTMLSelectElement)[] = [];\n\n // Inject controls for each question\n parsed.questions.forEach((question, index) => {\n const row = rows[index];\n if (!row) return;\n\n const cells = Array.from(row.querySelectorAll('td'));\n if (cells.length !== 3) return;\n\n const questionCell = cells[0];\n const answerCell = cells[1];\n\n if (!questionCell || !answerCell) return;\n\n // Get existing answer for this question\n const existingAnswer = existingAnswers[index];\n if (existingAnswer && existingAnswer.answer) {\n info(\n `Q${index + 1}: Pre-filling with \"${existingAnswer.answer}\" (${existingAnswer.success ? 'correct' : 'incorrect'})`,\n );\n }\n\n // Create input control based on question type\n const input = createQuestionInput(question, existingAnswer);\n inputs.push(input);\n\n // Clear answer cell and inject input\n answerCell.textContent = '';\n answerCell.appendChild(input);\n\n // Apply validation styling if answer exists\n if (existingAnswer) {\n applyValidationStyling(answerCell, existingAnswer.success);\n }\n\n // Setup auto-save on input change\n // Use 'change' for select elements (MCQ), 'input' for text inputs (numeric)\n const eventType = input.tagName === 'SELECT' ? 'change' : 'input';\n input.addEventListener(eventType, () => {\n handleAnswerInput(table, metadata, index, input.value);\n });\n });\n\n // Store input references\n metadata.inputs = inputs;\n\n // Setup instructor answer display listeners\n const showAnswersHandler = () => {\n void showStudentAnswersForTable(table, metadata);\n };\n const hideAnswersHandler = () => {\n hideStudentAnswersForTable(table);\n };\n\n document.addEventListener('qd:instructor-show-answers', showAnswersHandler);\n document.addEventListener('qd:instructor-hide-answers', hideAnswersHandler);\n\n // Check if instructor mode with toggle already enabled\n const isInstructor = sessionStorage.getItem(STORAGE_KEYS.INSTRUCTOR) === 'true';\n const showAnswers = sessionStorage.getItem('qd/instructor/showAnswers') === 'true';\n if (isInstructor && showAnswers) {\n void showStudentAnswersForTable(table, metadata);\n }\n\n // Add logout listener to clear student-specific UI state (FR-001, FR-002)\n const logoutHandler = () => {\n // Clear student-specific color-coded feedback\n const answerCells = table.querySelectorAll('td.qd-answer-correct, td.qd-answer-incorrect');\n answerCells.forEach((cell) => {\n removeClass(cell, 'qd-answer-correct', 'qd-answer-incorrect');\n });\n\n // Clear any displayed student answers\n hideStudentAnswersForTable(table);\n\n info('Cleared student UI state from quiz table on logout');\n };\n\n document.addEventListener('qd:logout', logoutHandler);\n\n // Store cleanup function in metadata\n metadata.cleanupInstructorListeners = () => {\n document.removeEventListener('qd:instructor-show-answers', showAnswersHandler);\n document.removeEventListener('qd:instructor-hide-answers', hideAnswersHandler);\n document.removeEventListener('qd:logout', logoutHandler);\n };\n\n addClass(table, 'qd-quiz-interactive');\n info(`Quiz table enhanced in interactive mode for page ${pageId}`);\n\n return true;\n}\n\n/**\n * Create input control for a question\n *\n * For MCQ questions: Creates a - Show student answers on page + Show current answers @@ -340,6 +361,13 @@ export class QdInstructor extends LitElement { @close=${this.handleClosePinReset} @qd:pin-reset=${this.handlePinReset} > + + `; } diff --git a/src/components/qd-login.ts b/src/components/qd-login.ts index d19e8f7a..27565449 100644 --- a/src/components/qd-login.ts +++ b/src/components/qd-login.ts @@ -37,6 +37,9 @@ import { import './qd-build-info.js'; import './qd-password-modal.js'; import './qd-confirm-dialog.js'; +import './qd-help-trigger.js'; +import './qd-help-popup.js'; +import { getHelpContent } from '../config/help-content.js'; /** * Login event data @@ -113,6 +116,12 @@ export class QdLogin extends LitElement { @state() private showPinConfirmation = false; + /** + * Whether help popup is open + */ + @state() + private helpOpen = false; + /** * Lockout countdown interval */ @@ -299,6 +308,7 @@ export class QdLogin extends LitElement { this.pin = ''; this.lockoutSeconds = 0; this.showPinConfirmation = false; + this.helpOpen = false; // Clean up lockout interval if (this.lockoutInterval) { @@ -313,7 +323,11 @@ export class QdLogin extends LitElement { render() { return html` @@ -199,6 +212,12 @@ export class QdStatus extends LitElement { + `; } @@ -293,6 +312,20 @@ export class QdStatus extends LitElement { this.updateVisibility(); }; + /** + * Handle help open event + */ + private handleHelpOpen = (): void => { + this.helpOpen = true; + }; + + /** + * Handle help close event + */ + private handleHelpClose = (): void => { + this.helpOpen = false; + }; + /** * Handle logout button click */ diff --git a/src/config/help-content.ts b/src/config/help-content.ts new file mode 100644 index 00000000..4f69d434 --- /dev/null +++ b/src/config/help-content.ts @@ -0,0 +1,40 @@ +/** + * Help Content Configuration + * + * Centralized help text for all panels. Edit this file to update help content. + * Feature: 008-user-guidance-popups + */ + +export type HelpPanelType = 'login' | 'status' | 'instructor'; + +export interface HelpContent { + title: string; + body: string; +} + +/** + * Help content for each panel type + */ +export const HELP_CONTENT: Record = { + login: { + title: 'Login Help', + body: '

            Enter Name and Service ID to log in. Provide a new PIN if this is your first visit to this release of this document, otherwise use the PIN you previously created. Your instructor is able to reset PINs. See the Feedback page for more support.

            Instructors: click "Instructor" for instructor login page (password accompanies distribution).

            ', + }, + + status: { + title: 'Student View', + body: '

            Page color coding:

            • Green=All correct
            • Amber=Some answered
            • Red=None yet

            You can view your overall progress at attempted questions in the Test Progress panel.

            ', + }, + + instructor: { + title: 'Instructor Tools', + body: '

            • Show current answers: Toggle for display of student answers for the current page.
            • View All Scores: View table scores for all students.
            • Reset PIN: Reset student PINs.
            • Export CSV: CSV download of all scores/answers.
            • Erase All Data: Clear all stored student data.

            ', + }, +}; + +/** + * Get help content for a panel type + */ +export function getHelpContent(panelType: HelpPanelType): HelpContent { + return HELP_CONTENT[panelType]; +} diff --git a/stories/components/qd-help-popup.stories.ts b/stories/components/qd-help-popup.stories.ts new file mode 100644 index 00000000..90f3972f --- /dev/null +++ b/stories/components/qd-help-popup.stories.ts @@ -0,0 +1,250 @@ +/** + * Storybook stories for qd-help-popup component + * + * Demonstrates the help popup modal with various content configurations. + * Feature: 008-user-guidance-popups + */ + +import type { Meta, StoryObj } from '@storybook/web-components'; +import { html } from 'lit'; +import '../../src/components/qd-help-popup.js'; +import '../../src/components/qd-help-trigger.js'; + +const meta: Meta = { + title: 'Components/HelpPopup', + component: 'qd-help-popup', + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: ` +A modal popup that displays contextual help content. + +**Features:** +- Portal rendering to document.body for proper z-index +- Customizable title and HTML content +- Multiple close methods: Escape key, backdrop click, close button +- Focus management (focuses close button, restores on close) +- Accessible (role="dialog", aria-modal, aria-labelledby) + +**Properties:** +- \`open\`: Boolean - whether popup is visible +- \`title\`: String - popup header text (default: "Help") +- \`content\`: String - HTML content to display + +**Events:** +- \`qd:modal-close\`: Emitted when popup closes + +**Accessibility:** +- Dialog role with aria-modal="true" +- aria-labelledby points to title +- Close button has aria-label="Close" +- Focus trapped in popup while open + `, + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +/** + * Login Help + * + * Help content for the login panel. + */ +export const LoginHelp: Story = { + render: () => html` + Welcome to BrowserTest

            Enter your Service ID and name to log in as a student and track your quiz progress.

            Instructors: Click the "Instructor" button to access admin features.

            '} + > +
            + `, +}; + +/** + * Status Help + * + * Help content for the student status panel. + */ +export const StatusHelp: Story = { + render: () => html` + Understanding Your Score

            Your score reflects your progress on quiz pages you have visited.

            Green = All questions correct
            Amber = Some questions answered
            Red = No questions answered

            '} + > +
            + `, +}; + +/** + * Instructor Help + * + * Help content for the instructor panel. + */ +export const InstructorHelp: Story = { + render: () => html` + Instructor Tools

            View Scores: See all student results.

            Export CSV: Download detailed answer data.

            Erase Data: Clear database for new student cohort.

            '} + > +
            + `, +}; + +/** + * Custom Content + * + * Shows how HTML content is rendered. + */ +export const CustomContent: Story = { + render: () => html` + Getting Started

            This is a custom help popup with formatted content.

            • First item
            • Second item
            • Third item

            Contact: support@example.com

            '} + > +
            + `, +}; + +/** + * Interactive + * + * Demonstrates opening and closing the popup with the trigger button. + */ +export const Interactive: Story = { + render: () => { + const openPopup = () => { + const popup = document.querySelector('#interactive-popup') as HTMLElement & { open: boolean }; + if (popup) { + popup.open = true; + } + }; + + const closePopup = () => { + const popup = document.querySelector('#interactive-popup') as HTMLElement & { open: boolean }; + if (popup) { + popup.open = false; + } + }; + + return html` +
            +
            + Click for Help + +
            + + Welcome to BrowserTest

            Enter your Service ID and name to log in as a student and track your quiz progress.

            Instructors: Click the "Instructor" button to access admin features.

            Contact: support@example.com

            '} + @qd:modal-close=${closePopup} + > +
            + +
            +

            + Click the ? button to open the help popup. Close with Escape, backdrop click, or the × + button. +

            +
            +
            + `; + }, +}; + +/** + * All Three Panels + * + * Side-by-side comparison of all three help content types. + */ +export const AllThreePanels: Story = { + render: () => { + const openPopup = (id: string) => () => { + const popup = document.querySelector(`#${id}`) as HTMLElement & { open: boolean }; + if (popup) popup.open = true; + }; + + const closePopup = (id: string) => () => { + const popup = document.querySelector(`#${id}`) as HTMLElement & { open: boolean }; + if (popup) popup.open = false; + }; + + return html` +
            +
            +
            +
            + Login Panel + +
            +
            + +
            +
            + Status Panel + +
            +
            + +
            +
            + Instructor Panel + +
            +
            +
            + + Welcome to BrowserTest

            Enter your Service ID and name to log in as a student and track your quiz progress.

            Instructors: Click the "Instructor" button to access admin features.

            '} + @qd:modal-close=${closePopup('login-help')} + > +
            + + Understanding Your Score

            Your score reflects your progress on quiz pages you have visited.

            Green = All questions correct
            Amber = Some questions answered
            Red = No questions answered

            '} + @qd:modal-close=${closePopup('status-help')} + > +
            + + Instructor Tools

            View Scores: See all student results.

            Export CSV: Download detailed answer data.

            Erase Data: Clear database for new student cohort.

            '} + @qd:modal-close=${closePopup('instructor-help')} + > +
            +
            + `; + }, +}; diff --git a/stories/components/qd-help-trigger.stories.ts b/stories/components/qd-help-trigger.stories.ts new file mode 100644 index 00000000..a48dad88 --- /dev/null +++ b/stories/components/qd-help-trigger.stories.ts @@ -0,0 +1,162 @@ +/** + * Storybook stories for qd-help-trigger component + * + * Demonstrates the help icon trigger button with various configurations. + * Feature: 008-user-guidance-popups + */ + +import type { Meta, StoryObj } from '@storybook/web-components'; +import { html } from 'lit'; +import '../../src/components/qd-help-trigger.js'; + +const meta: Meta = { + title: 'Components/HelpTrigger', + component: 'qd-help-trigger', + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: ` +A small help icon button (?) that triggers contextual help popups. + +**Features:** +- Circular blue button with white "?" icon +- Keyboard accessible (focusable button) +- Emits qd:help-open event with panelType detail +- Three panel types: login, status, instructor + +**Properties:** +- \`panelType\`: String - which panel this trigger belongs to ('login' | 'status' | 'instructor') + +**Events:** +- \`qd:help-open\`: CustomEvent<{panelType: string}> - Emitted when button is clicked + +**Accessibility:** +- \`aria-label="Help"\` +- \`title="Help"\` +- Native button element (keyboard accessible) + `, + }, + }, + }, + argTypes: { + panelType: { + control: { type: 'select' }, + options: ['login', 'status', 'instructor'], + description: 'Which panel this trigger belongs to', + }, + }, +}; + +export default meta; +type Story = StoryObj; + +/** + * Default + * + * Basic help trigger with default login panel type. + */ +export const Default: Story = { + render: () => html` `, +}; + +/** + * Login Panel Type + * + * Help trigger for the login panel. + */ +export const LoginPanelType: Story = { + render: () => html` `, +}; + +/** + * Status Panel Type + * + * Help trigger for the student status panel. + */ +export const StatusPanelType: Story = { + render: () => html` `, +}; + +/** + * Instructor Panel Type + * + * Help trigger for the instructor panel. + */ +export const InstructorPanelType: Story = { + render: () => html` `, +}; + +/** + * Interactive + * + * Click the button to see the event emitted. + */ +export const Interactive: Story = { + render: () => { + const handleHelpOpen = (e: Event) => { + const detail = (e as CustomEvent<{ panelType: string }>).detail; + alert(`Help requested for: ${detail.panelType}`); + }; + + return html` +
            +
            +
            + +
            Login
            +
            +
            + +
            Status
            +
            +
            + +
            Instructor
            +
            +
            + +
            +

            Click any help button to see the event with its panelType.

            +
            +
            + `; + }, +}; + +/** + * In Context + * + * Shows how the help trigger looks in a typical panel header. + */ +export const InContext: Story = { + render: () => html` +
            +
            + Login + +
            + +
            + Your Progress: 75% + +
            + +
            + Instructor Tools + +
            +
            + `, +}; diff --git a/stories/components/qd-instructor/qd-instructor.stories.ts b/stories/components/qd-instructor/qd-instructor.stories.ts index ca97089d..380c2bf0 100644 --- a/stories/components/qd-instructor/qd-instructor.stories.ts +++ b/stories/components/qd-instructor/qd-instructor.stories.ts @@ -176,3 +176,38 @@ export const UnlockedNoData: Story = { return container; }, }; + +/** + * With Help + * + * Shows instructor panel with help trigger button for E2E testing. + */ +export const WithHelp: Story = { + render: () => { + const container = html` +
            + + +
            +

            + Click the ? button to open the help popup explaining instructor + features. +

            +
            +
            + `; + + setTimeout(() => { + const element = document.querySelector('qd-instructor') as QdInstructor; + if (element) { + element.setAttribute('data-show', ''); + element.unlock(); + element.setStudents(mockStudents); + } + }, 50); + + return container; + }, +}; diff --git a/stories/components/qd-status.stories.ts b/stories/components/qd-status.stories.ts index 9228294e..fb70065b 100644 --- a/stories/components/qd-status.stories.ts +++ b/stories/components/qd-status.stories.ts @@ -315,3 +315,58 @@ export const MinimalExample: Story = { return html``; }, }; + +/** + * With Help + * + * Shows status panel with help trigger button for E2E testing. + */ +export const WithHelp: Story = { + render: () => { + // Set up session data to make component visible + const sessionData = { + serviceId: 'RN2344', + name: 'John Smith', + release: 'TRV Connectors Autumn 2025', + role: 'student', + }; + sessionStorage.setItem(STORAGE_KEYS.SESSION, JSON.stringify(sessionData)); + + // Set up session cache with sample data + const cache: SessionCache = { + totals: { total: 15, answered: 15, correct: 12 }, + pages: { + 'page-1': { state: 'complete', total: 5, answered: 5, correct: 5, answers: [] }, + 'page-2': { state: 'incomplete', total: 5, answered: 5, correct: 4, answers: [] }, + 'page-3': { state: 'incomplete', total: 5, answered: 5, correct: 3, answers: [] }, + }, + }; + sessionStorage.setItem(STORAGE_KEYS.CACHE, JSON.stringify(cache)); + + setTimeout(() => { + // Force component to show by setting data-show attribute + const statusComponent = document.querySelector('qd-status'); + if (statusComponent) { + statusComponent.setAttribute('data-show', ''); + // Trigger refresh + const event = new CustomEvent('qd:state-changed'); + document.dispatchEvent(event); + } + }, 50); + + return html` +
            + + +
            +

            + Click the ? button to open the help popup explaining the scoring + system. +

            +
            +
            + `; + }, +}; diff --git a/tests/e2e/help-popups.spec.ts b/tests/e2e/help-popups.spec.ts new file mode 100644 index 00000000..726df380 --- /dev/null +++ b/tests/e2e/help-popups.spec.ts @@ -0,0 +1,190 @@ +/** + * E2E tests for Help Popups + * + * Tests the help popup functionality on login, status, and instructor panels. + * Feature: 008-user-guidance-popups + */ + +import { test, expect } from '@playwright/test'; + +test.describe('Help Popups', () => { + test.describe('Login Panel Help', () => { + test.beforeEach(async ({ page }) => { + // Navigate to a Storybook story that shows login with help + await page.goto('http://localhost:6006/iframe.html?id=components-login--default'); + // Wait for component to be ready + await page.waitForSelector('qd-login[data-ready]', { timeout: 5000 }); + }); + + test('displays help trigger button on login panel', async ({ page }) => { + const helpTrigger = page.locator('qd-login qd-help-trigger'); + await expect(helpTrigger).toBeVisible(); + }); + + test('opens help popup when help trigger is clicked', async ({ page }) => { + const helpTrigger = page.locator('qd-login qd-help-trigger button'); + await helpTrigger.click(); + + // Help popup portal renders to body + const popup = page.locator('.qd-help-backdrop'); + await expect(popup).toBeVisible(); + }); + + test('displays login help content in popup', async ({ page }) => { + const helpTrigger = page.locator('qd-login qd-help-trigger button'); + await helpTrigger.click(); + + const title = page.locator('.qd-help-title'); + await expect(title).toBeVisible(); + + const body = page.locator('.qd-help-body'); + await expect(body).toContainText('Service ID'); + }); + + test('closes popup on Escape key', async ({ page }) => { + const helpTrigger = page.locator('qd-login qd-help-trigger button'); + await helpTrigger.click(); + + await expect(page.locator('.qd-help-backdrop')).toBeVisible(); + + await page.keyboard.press('Escape'); + + await expect(page.locator('.qd-help-backdrop')).not.toBeVisible(); + }); + + test('closes popup on backdrop click', async ({ page }) => { + const helpTrigger = page.locator('qd-login qd-help-trigger button'); + await helpTrigger.click(); + + await expect(page.locator('.qd-help-backdrop')).toBeVisible(); + + // Click backdrop (not the content) + await page.locator('.qd-help-backdrop').click({ position: { x: 10, y: 10 } }); + + await expect(page.locator('.qd-help-backdrop')).not.toBeVisible(); + }); + + test('closes popup on close button click', async ({ page }) => { + const helpTrigger = page.locator('qd-login qd-help-trigger button'); + await helpTrigger.click(); + + await expect(page.locator('.qd-help-backdrop')).toBeVisible(); + + await page.locator('.qd-help-close').click(); + + await expect(page.locator('.qd-help-backdrop')).not.toBeVisible(); + }); + }); + + test.describe('Status Panel Help', () => { + test.beforeEach(async ({ page }) => { + // Navigate to a Storybook story that shows status with help + await page.goto('http://localhost:6006/iframe.html?id=components-status--with-help'); + // Wait for component to be ready + await page.waitForSelector('qd-status[data-show]', { timeout: 5000 }); + }); + + test('displays help trigger button on status panel', async ({ page }) => { + const helpTrigger = page.locator('qd-status qd-help-trigger'); + await expect(helpTrigger).toBeVisible(); + }); + + test('opens help popup when help trigger is clicked', async ({ page }) => { + const helpTrigger = page.locator('qd-status qd-help-trigger button'); + await helpTrigger.click(); + + // Help popup portal renders to body + const popup = page.locator('.qd-help-backdrop'); + await expect(popup).toBeVisible(); + }); + + test('displays status help content in popup', async ({ page }) => { + const helpTrigger = page.locator('qd-status qd-help-trigger button'); + await helpTrigger.click(); + + const title = page.locator('.qd-help-title'); + await expect(title).toBeVisible(); + + const body = page.locator('.qd-help-body'); + await expect(body).toContainText('Green'); + }); + + test('closes popup on Escape key', async ({ page }) => { + const helpTrigger = page.locator('qd-status qd-help-trigger button'); + await helpTrigger.click(); + + await expect(page.locator('.qd-help-backdrop')).toBeVisible(); + + await page.keyboard.press('Escape'); + + await expect(page.locator('.qd-help-backdrop')).not.toBeVisible(); + }); + + test('closes popup on close button click', async ({ page }) => { + const helpTrigger = page.locator('qd-status qd-help-trigger button'); + await helpTrigger.click(); + + await expect(page.locator('.qd-help-backdrop')).toBeVisible(); + + await page.locator('.qd-help-close').click(); + + await expect(page.locator('.qd-help-backdrop')).not.toBeVisible(); + }); + }); + + test.describe('Instructor Panel Help', () => { + test.beforeEach(async ({ page }) => { + // Navigate to a Storybook story that shows instructor with help + await page.goto('http://localhost:6006/iframe.html?id=components-qdinstructor--with-help'); + // Wait for component to be ready and unlocked + await page.waitForSelector('qd-instructor[data-show]', { timeout: 5000 }); + }); + + test('displays help trigger button on instructor panel', async ({ page }) => { + const helpTrigger = page.locator('qd-instructor qd-help-trigger'); + await expect(helpTrigger).toBeVisible(); + }); + + test('opens help popup when help trigger is clicked', async ({ page }) => { + const helpTrigger = page.locator('qd-instructor qd-help-trigger button'); + await helpTrigger.click(); + + // Help popup portal renders to body + const popup = page.locator('.qd-help-backdrop'); + await expect(popup).toBeVisible(); + }); + + test('displays instructor help content in popup', async ({ page }) => { + const helpTrigger = page.locator('qd-instructor qd-help-trigger button'); + await helpTrigger.click(); + + const title = page.locator('.qd-help-title'); + await expect(title).toBeVisible(); + + const body = page.locator('.qd-help-body'); + await expect(body).toContainText('Show Answers'); + }); + + test('closes popup on Escape key', async ({ page }) => { + const helpTrigger = page.locator('qd-instructor qd-help-trigger button'); + await helpTrigger.click(); + + await expect(page.locator('.qd-help-backdrop')).toBeVisible(); + + await page.keyboard.press('Escape'); + + await expect(page.locator('.qd-help-backdrop')).not.toBeVisible(); + }); + + test('closes popup on close button click', async ({ page }) => { + const helpTrigger = page.locator('qd-instructor qd-help-trigger button'); + await helpTrigger.click(); + + await expect(page.locator('.qd-help-backdrop')).toBeVisible(); + + await page.locator('.qd-help-close').click(); + + await expect(page.locator('.qd-help-backdrop')).not.toBeVisible(); + }); + }); +}); diff --git a/tests/unit/components/qd-help-popup.test.ts b/tests/unit/components/qd-help-popup.test.ts new file mode 100644 index 00000000..9700bfec --- /dev/null +++ b/tests/unit/components/qd-help-popup.test.ts @@ -0,0 +1,291 @@ +/** + * Tests for qd-help-popup.ts component + * + * Feature: 008-user-guidance-popups + * TDD: These tests are written FIRST, before implementation. + */ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import type { QdHelpPopup } from '../../../src/components/qd-help-popup'; + +// Import component +import '../../../src/components/qd-help-popup.js'; + +describe('qd-help-popup', () => { + let container: HTMLElement; + let element: QdHelpPopup; + + beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); + }); + + afterEach(() => { + container.remove(); + // Clean up any portals + document.querySelectorAll('.qd-help-backdrop').forEach((el) => el.remove()); + }); + + async function createPopup( + options: { + open?: boolean; + title?: string; + content?: string; + } = {}, + ): Promise { + element = document.createElement('qd-help-popup'); + if (options.title) element.title = options.title; + if (options.content) element.content = options.content; + container.appendChild(element); + await element.updateComplete; + // Set open last to trigger portal creation + if (options.open) { + element.open = true; + await element.updateComplete; + // Wait for portal to be created + await new Promise((r) => setTimeout(r, 50)); + } + return element; + } + + describe('initial state', () => { + it('is hidden by default', async () => { + const el = await createPopup(); + expect(el.open).toBe(false); + const backdrop = document.querySelector('.qd-help-backdrop'); + expect(backdrop).toBeFalsy(); + }); + + it('has default title "Help"', async () => { + const el = await createPopup(); + expect(el.title).toBe('Help'); + }); + + it('has empty content by default', async () => { + const el = await createPopup(); + expect(el.content).toBe(''); + }); + }); + + describe('opening behavior', () => { + it('creates portal when opened', async () => { + await createPopup({ + open: true, + title: 'Test Help', + content: '

            Help content

            ', + }); + const backdrop = document.querySelector('.qd-help-backdrop'); + expect(backdrop).toBeTruthy(); + }); + + it('displays title in portal', async () => { + await createPopup({ + open: true, + title: 'Login Help', + content: '

            Content

            ', + }); + const title = document.querySelector('.qd-help-title'); + expect(title?.textContent).toBe('Login Help'); + }); + + it('displays content in portal', async () => { + await createPopup({ + open: true, + title: 'Test', + content: '

            This is help content

            ', + }); + const body = document.querySelector('.qd-help-body'); + expect(body?.innerHTML).toContain('This is help content'); + }); + + it('renders HTML content', async () => { + await createPopup({ + open: true, + title: 'Test', + content: '

            Title

            Bold text

            ', + }); + const body = document.querySelector('.qd-help-body'); + expect(body?.innerHTML).toContain('

            Title

            '); + expect(body?.innerHTML).toContain('Bold'); + }); + }); + + describe('closing behavior', () => { + it('closes on Escape key', async () => { + const el = await createPopup({ + open: true, + title: 'Test', + content: '

            Content

            ', + }); + + const event = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }); + document.dispatchEvent(event); + await el.updateComplete; + await new Promise((r) => setTimeout(r, 50)); + + expect(el.open).toBe(false); + const backdrop = document.querySelector('.qd-help-backdrop'); + expect(backdrop).toBeFalsy(); + }); + + it('closes on backdrop click', async () => { + const el = await createPopup({ + open: true, + title: 'Test', + content: '

            Content

            ', + }); + + const backdrop = document.querySelector('.qd-help-backdrop') as HTMLElement; + backdrop?.click(); + await el.updateComplete; + await new Promise((r) => setTimeout(r, 50)); + + expect(el.open).toBe(false); + }); + + it('closes on close button click', async () => { + const el = await createPopup({ + open: true, + title: 'Test', + content: '

            Content

            ', + }); + + const closeBtn = document.querySelector('.qd-help-close') as HTMLElement; + closeBtn?.click(); + await el.updateComplete; + await new Promise((r) => setTimeout(r, 50)); + + expect(el.open).toBe(false); + }); + + it('does NOT close on content click', async () => { + const el = await createPopup({ + open: true, + title: 'Test', + content: '

            Content

            ', + }); + + const content = document.querySelector('.qd-help-content') as HTMLElement; + content?.click(); + await el.updateComplete; + await new Promise((r) => setTimeout(r, 50)); + + expect(el.open).toBe(true); + }); + + it('emits qd:modal-close event on close', async () => { + const el = await createPopup({ + open: true, + title: 'Test', + content: '

            Content

            ', + }); + const closeHandler = vi.fn(); + el.addEventListener('qd:modal-close', closeHandler); + + const closeBtn = document.querySelector('.qd-help-close') as HTMLElement; + closeBtn?.click(); + await el.updateComplete; + + expect(closeHandler).toHaveBeenCalled(); + }); + + it('removes portal on close', async () => { + const el = await createPopup({ + open: true, + title: 'Test', + content: '

            Content

            ', + }); + + el.open = false; + await el.updateComplete; + await new Promise((r) => setTimeout(r, 50)); + + const backdrop = document.querySelector('.qd-help-backdrop'); + expect(backdrop).toBeFalsy(); + }); + }); + + describe('accessibility', () => { + it('has dialog role', async () => { + await createPopup({ + open: true, + title: 'Test', + content: '

            Content

            ', + }); + const dialog = document.querySelector('[role="dialog"]'); + expect(dialog).toBeTruthy(); + }); + + it('has aria-modal="true"', async () => { + await createPopup({ + open: true, + title: 'Test', + content: '

            Content

            ', + }); + const dialog = document.querySelector('[role="dialog"]'); + expect(dialog?.getAttribute('aria-modal')).toBe('true'); + }); + + it('has aria-labelledby pointing to title', async () => { + await createPopup({ + open: true, + title: 'Test', + content: '

            Content

            ', + }); + const dialog = document.querySelector('[role="dialog"]'); + expect(dialog?.getAttribute('aria-labelledby')).toBe('qd-help-title'); + }); + + it('close button has aria-label="Close"', async () => { + await createPopup({ + open: true, + title: 'Test', + content: '

            Content

            ', + }); + const closeBtn = document.querySelector('.qd-help-close'); + expect(closeBtn?.getAttribute('aria-label')).toBe('Close'); + }); + + it('focuses close button when opened', async () => { + await createPopup({ + open: true, + title: 'Test', + content: '

            Content

            ', + }); + await new Promise((r) => setTimeout(r, 100)); // Wait for focus + + const closeBtn = document.querySelector('.qd-help-close'); + expect(document.activeElement).toBe(closeBtn); + }); + }); + + describe('close() method', () => { + it('provides close() method that closes popup', async () => { + const el = await createPopup({ + open: true, + title: 'Test', + content: '

            Content

            ', + }); + + el.close(); + await el.updateComplete; + await new Promise((r) => setTimeout(r, 50)); + + expect(el.open).toBe(false); + }); + + it('close() method emits qd:modal-close event', async () => { + const el = await createPopup({ + open: true, + title: 'Test', + content: '

            Content

            ', + }); + const closeHandler = vi.fn(); + el.addEventListener('qd:modal-close', closeHandler); + + el.close(); + await el.updateComplete; + + expect(closeHandler).toHaveBeenCalled(); + }); + }); +}); diff --git a/tests/unit/components/qd-help-trigger.test.ts b/tests/unit/components/qd-help-trigger.test.ts new file mode 100644 index 00000000..78147c2c --- /dev/null +++ b/tests/unit/components/qd-help-trigger.test.ts @@ -0,0 +1,155 @@ +/** + * Tests for qd-help-trigger.ts component + * + * Feature: 008-user-guidance-popups + * TDD: These tests are written FIRST, before implementation. + */ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import type { QdHelpTrigger } from '../../../src/components/qd-help-trigger'; + +// Import component +import '../../../src/components/qd-help-trigger.js'; + +describe('qd-help-trigger', () => { + let container: HTMLElement; + let element: QdHelpTrigger; + + beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); + }); + + afterEach(() => { + container.remove(); + }); + + async function createTrigger( + options: { + panelType?: 'login' | 'status' | 'instructor'; + } = {}, + ): Promise { + element = document.createElement('qd-help-trigger'); + if (options.panelType) element.panelType = options.panelType; + container.appendChild(element); + await element.updateComplete; + return element; + } + + describe('rendering', () => { + it('renders a button with ? icon', async () => { + const el = await createTrigger(); + const button = el.shadowRoot?.querySelector('button'); + expect(button).toBeTruthy(); + expect(button?.textContent?.trim()).toBe('?'); + }); + + it('has default panelType of login', async () => { + const el = await createTrigger(); + expect(el.panelType).toBe('login'); + }); + + it('accepts custom panelType', async () => { + const el = await createTrigger({ panelType: 'instructor' }); + expect(el.panelType).toBe('instructor'); + }); + }); + + describe('accessibility', () => { + it('button has aria-label="Help"', async () => { + const el = await createTrigger(); + const button = el.shadowRoot?.querySelector('button'); + expect(button?.getAttribute('aria-label')).toBe('Help'); + }); + + it('button has title="Help"', async () => { + const el = await createTrigger(); + const button = el.shadowRoot?.querySelector('button'); + expect(button?.getAttribute('title')).toBe('Help'); + }); + + it('button is keyboard focusable', async () => { + const el = await createTrigger(); + const button = el.shadowRoot?.querySelector('button'); + // Buttons are focusable by default + expect(button?.tagName).toBe('BUTTON'); + }); + }); + + describe('events', () => { + it('emits qd:help-open event on click', async () => { + const el = await createTrigger({ panelType: 'login' }); + const handler = vi.fn(); + el.addEventListener('qd:help-open', handler); + + const button = el.shadowRoot?.querySelector('button'); + button?.click(); + + expect(handler).toHaveBeenCalledTimes(1); + }); + + it('event detail contains panelType', async () => { + const el = await createTrigger({ panelType: 'status' }); + let eventDetail: { panelType: string } | null = null; + el.addEventListener('qd:help-open', (e: Event) => { + eventDetail = (e as CustomEvent<{ panelType: string }>).detail; + }); + + const button = el.shadowRoot?.querySelector('button'); + button?.click(); + + expect(eventDetail).toEqual({ panelType: 'status' }); + }); + + it('event bubbles and is composed', async () => { + const el = await createTrigger(); + let eventBubbles = false; + let eventComposed = false; + + el.addEventListener('qd:help-open', (e: Event) => { + eventBubbles = e.bubbles; + eventComposed = e.composed; + }); + + const button = el.shadowRoot?.querySelector('button'); + button?.click(); + + expect(eventBubbles).toBe(true); + expect(eventComposed).toBe(true); + }); + }); + + describe('panelType variations', () => { + it('works with panelType="login"', async () => { + const el = await createTrigger({ panelType: 'login' }); + let eventDetail: { panelType: string } | undefined; + el.addEventListener('qd:help-open', (e: Event) => { + eventDetail = (e as CustomEvent<{ panelType: string }>).detail; + }); + + el.shadowRoot?.querySelector('button')?.click(); + expect(eventDetail?.panelType).toBe('login'); + }); + + it('works with panelType="status"', async () => { + const el = await createTrigger({ panelType: 'status' }); + let eventDetail: { panelType: string } | undefined; + el.addEventListener('qd:help-open', (e: Event) => { + eventDetail = (e as CustomEvent<{ panelType: string }>).detail; + }); + + el.shadowRoot?.querySelector('button')?.click(); + expect(eventDetail?.panelType).toBe('status'); + }); + + it('works with panelType="instructor"', async () => { + const el = await createTrigger({ panelType: 'instructor' }); + let eventDetail: { panelType: string } | undefined; + el.addEventListener('qd:help-open', (e: Event) => { + eventDetail = (e as CustomEvent<{ panelType: string }>).detail; + }); + + el.shadowRoot?.querySelector('button')?.click(); + expect(eventDetail?.panelType).toBe('instructor'); + }); + }); +});