Imported from aamirenic/Quiz-Atelier (
AGENTS.md). Install upstream withnpx skills add aamirenic/Quiz-Atelier. Copyright stays with the author.
Quiz Atelier — Standing Rules (always read before working in this repo)
STANDING RULE — index.html is GENERATED from Python
quiz-manager/index.html is generated by quiz-manager/python/build_html.py
(one function per page section). To change the markup:
- Edit the matching section function in
build_html.py - Run
python quiz-manager/python/build_html.py— it rewrites index.html Do NOT hand-edit index.html; your edits are overwritten on the next run. The generated file is verified semantically identical to the original (841/841 tags, attribute-order-insensitive).
Other generated/derived webapp files: app.js (~5.1k lines) is hand-maintained,
styles.css (~2.2k lines) hand-maintained; index.html is currently ~1.2k
lines of shell markup (the heavy markup lives in the section functions).
STANDING RULE — every webapp change ships the APK too
USER RULE (overrides the above when invoked): the user may declare a change "webapp-only". Before building/shipping ANY APK for a change, ASK the user first (ask_questions) — never ship the APK unprompted. The default pipeline below applies only when the user has approved an APK release for that change.
The Android app at android/ wraps the webapp as a bundled asset. Whenever
quiz-manager/index.html (or any web-facing file) changes, ALWAYS:
- Copy the updated webapp into the bundle — all web files, not just index.html
(since v1.5.0 the app is split: index.html + styles.css + app.js + sw.js):
cp quiz-manager/index.html quiz-manager/styles.css quiz-manager/app.js quiz-manager/sw.js android/app/src/main/assets/www/ - Bump
versionCode/versionNameinandroid/app/build.gradle.kts. - Rebuild and replace the APK:
(name the file with the new version, e.g. QuizAtelier-v1.6.3.apk). Keep ONLY the latest APK inexport JAVA_HOME="<path to JDK 17>" # e.g. android-build-tools/jdk17 export ANDROID_HOME="<path to Android SDK>" # e.g. android-build-tools/android-sdk export QA_STORE_PASSWORD=... QA_KEY_PASSWORD=... # signing passwords (never commit) cd android && sh gradlew.sh assembleRelease --no-daemon cp app/build/outputs/apk/release/app-release.apk ./QuizAtelier-v1.6.2.apkandroid/— prune older ones when shipping. - Verify:
apksigner verifypasses and the new index.html is inside the APK (jar tf ... | grep assets/www).
Verify the SERVED asset, not just any asset. The WebView loads
assets/www/index.html (see START_URL in MainActivity.kt). A stray copy at
android/app/src/main/assets/index.html also gets bundled but is NEVER
served — v1.4.0 once shipped stale this way. Prove freshness with:
unzip -p <apk> assets/www/index.html | grep <new-code-marker>.
Toolchain (JDK 17 + Android SDK) lives in a local untracked folder
(android-build-tools/, gitignored). Keystore: android/app/release.keystore,
alias quizatelier. Signing passwords are NOT in version control — pass them
via QA_STORE_PASSWORD / QA_KEY_PASSWORD env vars (or an untracked
~/.gradle/gradle.properties); build.gradle.kts fails the release build
without them.
Never tell the user a change is "done" without the rebuilt APK in place.
Project facts (so future sessions don't re-derive them)
- Current release: v1.6.2 (
CACHE_VERSION "qa-v1.6.2"in sw.js, versionCode 34). Recent history: v1.6.1 render/save perf fix (renderAll renders only the active view; save() debounced 350ms with flush on beforeunload/visibilitychange — do NOT reintroduce eager save() calls), v1.6.0 streak/badge redesign, v1.5.8 scoreboard playtest fixes. - Repo is on GitHub (see
git remote -vfor the origin URL), branchmaster..gitignoreexcludes db.json, keystore, APKs, node_modules, android-build-tools, demo data, .freebuff. Deployment target is Vercel; the long-runningserver.jsneeds a serverless/KV refactor (or a Render-side host) before the live site can persist data. - Webapp:
quiz-manager/— index.html is generated shell markup, app.js + styles.css are hand-maintained; zero-build. - Server:
server/server.js, zero-dependency Node 18+ (http, fs, crypto, tls). - Server base URL: the webapp auto-detects — same-origin when served by
server.js, and
http://10.0.2.2:8790when running in an Android emulator. For real phones, deploy the server and setwindow.QA_SERVER_URLoverride. - Gemini keys are SERVER-side now (
server/server.jsenvGEMINI_API_KEYS, comma-separated pool). The webapp has no keys and callsPOST /api/ai/generatewith{ kind: "quiz" | "eli5", parts: [...] }. - PWA:
quiz-manager/manifest.webmanifest+quiz-manager/sw.js(cache-first for app shell, network-only for API/Gemini). Registration is guarded to NOT run inside the APK WebView origin (appassets.androidplatform.net). - Roadmap + decision log: a personal file outside the repo (not committed; ask the user for its location if needed).
- Docs:
Quiz-Atelier-Project-Documentation.docx(19-section report, built bymake_docx.py; screenshot sources indocx-assets/). The docx documents v1.6.2 feature set. When a change is feature-worthy, add it to the docx ADDITIVELY only — the user considers existing content final. - Plans: teacher lenient/caring/strict; student backbencher/average/nerd. Student plan ids gate: ELI5 (3/day free), daily warm-up (paid), comeback rounds + graveyard (free).
Debugging & testing quirks (live preview / APK)
- SW cache-first serves STALE HTML after every webapp edit. Before any live
test after changing files: bump
CACHE_VERSIONinquiz-manager/sw.js(per release), then in the page:navigator.serviceWorker.getRegistrations()→update(),caches.delete()every cache, reload. Symptom: code changes verified bycurldon't appear inpreview_evaluateprobes. requestAnimationFramedoes NOT tick in a hidden preview tab — physics/ timer code can't be tested by waiting; drive state directly and inspect.#login-gatehidden === truemeans SIGNED IN (inverted-sounding name — don't invert the check).- Server login is rate-limited: 10 attempts / 10 min per IP. Heavy probe
sessions can trip 429s and look like auth bugs. Server DB lives at
server/data/db.json(not<root>/data/); the server serializes writes and rewrites the whole file — stop the server before hand-editing db.json or it will overwrite your edit. - Teacher Results/Scoreboard: filter is two rows (Attendance: All/Present/
Absent; Result: Pass/Fail, tap to toggle) applied within each expanded
quiz's roster;
resultsOpenQuizIdkeeps the group open across re-renders. Student Snapshots: checkboxes apply instantly; Sort cycler under the Unattended box cycles Name A-Z → Z-A → Least/Most Taken → Least/Most Average (.snap-sort__*classes use the sans stack, not mono). - Theming:
--ink-softand other raw palette tokens do NOT rebind in dark mode — text colors inside panels must use theme-aware tokens (--muted,--fg) or dark-mode text goes invisible (v1.5.2 bug)..mark-badmust stay defined (v1.5.6). quiz-manager/index.htmlhas once been mysteriously renamed toindex.txtmid-session (editor/OS side). If the file "vanishes", check for that and restore instead of rebuilding.- Auth/fetch probes: hook
window.addEventListener("unhandledrejection", ...)andconsolelogs viapreview_logsbefore concluding a handler is dead — several "stuck" states were probe races, not app bugs.
User-stated preferences & decisions
- "Continue editing" (Drafts) must ask "Load quiz into quiz builder? Unsaved
quiz will be lost." (Yes/No) only when the builder has unsaved work
(
builderHasUnsaved()); empty builder loads directly. - The cloth-tear reveals (score card, daily seal, comeback card, answer patches) are REQUIRED UI — don't simplify them back to plain buttons. Verlet engine lives inline in index.html; reduced-motion gets instant reveal.
- Score ring:
score/total+%INSIDE the circle; verdict message outside below it. - Mobile quiz-builder block order is fixed: New Quiz → Add a Question →
Questions → Class Roster (CSS
orderat phone widths). - Every page plays the staggered cascade entry animation on each navigation
(
.view-inre-applied per view switch). - Login form doubles as signup BY DESIGN (unknown email creates an account), but a confirm dialog is required first and nameless accounts must never be created.
- Quiz runs must survive reload/restart: progress saves per-user locally and
resumes on next sign-in (
resumeRunProgress()); don't regress this. - Student header at phone width is compacted to one line (Open Latest, bell, account ≤46% width with ellipsis) — don't re-wrap it.
- Never print or commit the GitHub token / stored credentials; use
git credential filltransiently and delete temp files immediately. - The SVG rail mark (
.rail__mark--logo) must keep the Q glyph inside the ring circle — v1.6.7's Q tail crossed the ring stroke and had to be shrunk.