Live Quiz Platform
Build Plan — Combined
| Status | Live checklist |
| Generated | 2026-06-14 |
| Source of truth | .claude/context/knowledgebase/build-plan/ (5 files: project + per-app) |
Implementation work organised across one file per app plus a cross-cutting project file. Each app's content is collapsible below — project starts expanded; per-app sections start collapsed. Within each app, sections are grouped by phase (MVP → Alpha → Beta → Production → Stretch) and ordered by build dependency.
Definition of Done for every item: TDD followed (failing test first, then implementation, then refactor); end-to-end automated test exists for any user-facing feature the item touches; lint and format pass; coding standards followed.
Progress
Project (cross-cutting)20 / 217
Foundation MVP project1 / 2
- Repo initialized with the
Quiz.Designer/Tauri 2 + Angular 21 workspace and theschemas/JSON Schema source-of-truth tree. On-disk layout in Repository Layout. - Pre-commit hooks for GDScript format + lint (
gdformat/gdlintvia gdtoolkit) coveringQuiz.Stage/, alongside the existing Husky + lint-staged web hooks.
Quiz.Stage project scaffold MVP project0 / 7
Per Repository Layout — Quiz.Stage: one Godot 4.x project hosting the Host, Client, and Preview entry apps. The per-app surfaces land in host.md, client.md, and designer.md (preview); this section is the shared skeleton.
Quiz.Stage/project.godot— Godot 4.x project with the Compatibility (GL) renderer as the project-wide rendering method (never Forward+ — the web export supports nothing else, and preview pixel-fidelity depends on every preset matching).boot/boot.gdentry dispatch — loadsapps/<app>/<app>_main.tscnfrom the export feature tag (host/client/preview), with an--app=<name>command-line override for editor / dev runs; dispatch table covered by a GdUnit4 unit test.export_presets.cfg—host-windows,host-macos,client-android,client-ios,preview-web, each carrying its custom feature tag and per-app identity (product name, icons, bundle / application ids, permission manifests); export filters strip the other apps' scenes where size matters (Client mobile, Preview web). Thepreview-webpreset exports single-threaded so the embed needs no COOP/COEP headers inside the Designer's WebView.core/skeleton — engine-free plain-RefCountedGDScript (noNode, no scene-tree access, no autoload references) per Separation of Concerns:generated/(git-ignored codegen output),packaging/,protocol/,scoring/,discovery/,object_types/.render/+apps/host|client|preview/seeded with placeholder entry scenes so every preset boots headless without errors.- GdUnit4 test rig — installed and runnable headless (
godot --headlessdriving the GdUnit4 CLI) overtests/(unit tests forcore/, scene tests forrender/+apps/); first engine-free unit test green end-to-end. assets/seeded with the locked brand fonts (Bebas Neue, Inter, JetBrains Mono) and palette resources.
Azure DevOps CI / CD pipelines MVP project0 / 19
Per CI / CD. Pipelines are YAML at .azure-pipelines/, owned in-repo and reviewed in PR. Trunk-based branching; pr-validation.yml is a required check on master.
Status (2026-06-14). Six pipelines are registered + backed by real YAML: quiz-designer, quiz-stage, quiz-remote, quiz-schemas, pr-validation, release. The stale pre-consolidation quiz-host / quiz-client definitions were deleted (host/client consolidated into one quiz-stage), and quiz-designer-lint was retired (superseded by pr-validation). quiz-stage now calls Quiz.Stage/tools/gate.ps1 (the full GdUnit4 + gdformat + gdlint + typing gate), not the old import-only stub. quiz-schemas + pr-validation were run green on the hosted pool before wiring. pr-validation is now a blocking Build Validation branch policy on master — direct pushes stay possible for maintainers via the "Bypass policies when pushing" permission, preserving the trunk-based direct-push flow. Still unticked below: per-preset exports + cross-pipeline artefact handoff, JUnit test-result publishing, and lockfile-keyed caches. Branches target master, not main.
Agent + service connections
- Self-hosted Mac mini agent provisioned: latest macOS, current Xcode + iOS / macOS SDKs, Godot 4.x editor + matching export templates, Android SDK + JDK + signing keystore, Rust toolchain + Tauri prerequisites (desktop + mobile targets), signing certificates and provisioning profiles in macOS Keychain. Registered in ADO under agent pool
MacMini. - Microsoft-hosted
Azure Pipelinesagent pool authorised for the project (default for any pipeline not pinned toMacMini). - Apple Developer account credentials + App Store Connect API keys stored as ADO secret variables for
release.yml. - Azure Repos remote configured; repository's default branch set to
main; branch policies set: 1 reviewer minimum,pr-validation.ymlrequired, no direct pushes tomain.
Pipelines
.azure-pipelines/pr-validation.yml— runs on every pull request. Triggers per-area pipelines on the agent pool that can build them; aggregates pass/fail..azure-pipelines/quiz-stage.yml— headless Godot import (godot --headless --import) + full GdUnit4 suite, then per-preset exports: Host Windows (Microsoft-hosted), Host macOS + Client iOS (Mac mini), Client Android, and the Preview web export published as a pipeline artefact forquiz-designer.ymlto embed..azure-pipelines/quiz-remote.yml—Quiz.Remote/Tauri mobile builds:pnpminstall +ng build+ Jest +tauri android buildandtauri ios build(Mac mini for iOS); signed artefacts published..azure-pipelines/quiz-schemas.yml— schema codegen (schemas/codegen/gen_gdscript.py→ GDScript intoQuiz.Stage/core/generated/; quicktype + ajv → TypeScript into the Angular workspaces); publishes both drops as pipeline artefacts..azure-pipelines/quiz-designer.yml— consumes the TS schema artefact and the Quiz.Stage web-export artefact; on Microsoft-hosted Windows runsnpm ci+ng build+ng test(Jest) + Playwright screenshot pass againstng serve+tauri buildto produce the signed Windows installer; on the Mac mini runs the samenpm ci+ng build+tauri buildto produce the signed + notarised macOS DMG..azure-pipelines/release.yml— manual or git-tag triggered. Signs, packages, and publishes installers to the configured store / distribution channels. Dry-run nightly onmain(no publish).
Caching + reporting
- pnpm and cargo caches keyed by lockfiles enabled across the web pipelines.
- Godot import cache (
Quiz.Stage/.godot/) cached per-agent so re-imports skip on incremental builds. - Test results published as JUnit XML (GdUnit4, Jest, Playwright, cargo) so the ADO test report aggregates across pipelines.
- Code coverage published as Cobertura XML where the toolchain emits it (Jest, cargo); coverage ratchet enforced (no decrease).
End-to-end pipeline tests
- PR-validation green path: open a PR with a one-line readme change, confirm
pr-validation.ymlruns and passes on every relevant pipeline. - PR-validation red path: open a PR with a deliberately failing test, confirm
pr-validation.ymlfails and blocks merge per the branch policy. - Mac mini cold start: clear the
MacMiniagent's caches, confirm a full pipeline run completes within a target wall-clock budget (TBD at first measure). - Quiz.Stage → Quiz.Designer artefact handoff:
quiz-stage.ymlpublishes the web-export bundle andquiz-designer.ymlconsumes it; round-trip works onmain. release.ymldry run: produces signed installers for every configured channel without publishing.
Azure DevOps repos + wiki setup MVP project2 / 2
ADO hosts source control + the wiki mirror + CI pipelines. No Boards / work-item usage — this build plan is the task list.
- ADO project provisioned (or existing project repurposed) with Repos, Pipelines, and Wiki enabled.
- Wiki provisioned and pointed at the auto-generated knowledgebase mirror produced by the
docsskill.
Object-type plugin contract MVP projectdesignerhostclient5 / 8
ObjectTypecontract in engine-free GDScript underQuiz.Stage/core/object_types/(object_type.gd— id, schema version, capabilities,default_data/validate/migrate;object_type_data.gdbase) — see Object-Type Architecture. Headless GdUnit4 coverage intests/core/object_types/.- Designer editor-surface contract (Angular standalone component + view-model service in
Quiz.Designer/src/app/object-types/;DesignerEditor<TData>descriptor declares palette icon + group + the inputs the properties inspector binds to) — shipped + implemented for the full MVP cohort during the question-first Designer chunks. - Stage runtime-surface contract — one
render/object-type scene (.tscn+ script) per type, instantiated by the canvas renderer and shared by Host, Client, and Preview by construction; handles both canvas modes (fixed 1920×1080 Host canvas, responsive Client canvas) via the element model's anchors + pivot. [needs-Fable-pass] — therender/scenes are visual/presentation work; the autonomous Opus loop does not author them solo (model-routing). The engine-free contract they implement (host_runtime/client_runtimeduck-typed surface) is defined in Object-Type Contract; the scenes themselves are a creative/visual chunk. - Optional protocol-extension contract for object-type-specific messages (
protocol_extension.gd+object_message.gd— serialize/deserialize pair keyed by message namespace). - Per-element statefulness declaration on the
ObjectTypecontract —ObjectTypeCapabilities.is_statefulflags types that own live-session state; the Host crash-recovery snapshot path queries them (thecapture_state/restore_stateruntime pair lands with the Host snapshot chunk). Stateless types skip the snapshot path. See Object-Type Contract — Statefulness. - Built-in registry implementation — populated at startup by each Stage app surface and by the Designer's
ObjectTypeRegistry. Stage side:core/object_types/builtin_registry.gdbuild()registers + seals the cohort (core.text now) and is called at boot by the host / client / preview_mainscenes; Designer side seeds itsObjectTypeRegistryservice at app init. - First built-in object type —
core.text— implementing every contract end-to-end as the reference example - End-to-end test: a quiz containing only a
core.textelement on each canvas renders correctly across Designer preview (Quiz.Stage web export), Host, and Client
Schemas + codegen MVP projectdesigner3 / 6
Per Repository Layout — Cross-language contract. The schemas/ tree is the single source of truth for every data shape that crosses the Designer/Remote ↔ Stage boundary; codegen produces GDScript types + validators into Quiz.Stage/core/generated/ and TypeScript types + ajv validators into the Angular workspaces.
- Establish
schemas/directory layout:package-format/,live-play/,transfer/,object-types/,fixtures/; commit existing manifest / slide / canvas / element / envelope schemas as JSON Schema draft 2020-12. schemas/codegen.config.jsondeclares inputs, output paths, and naming conventions for both codegen targets (TypeScript + GDScript —outputs.gdscript.outputDir).- GDScript codegen target —
schemas/codegen/gen_gdscript.pyproduces typed GDScript data classes (from_dict/to_dict/validate) for the package-format schemas intoQuiz.Stage/core/generated/. camelCase JSON ↔ snake_case GDScript mapping documented in the generator header; cross-file$refs become typed generated classes (recursive),$defs/inline-objects/unions pass through as Dictionary/Variant, andallOf/if-thenconditionals are deferred to hand-writtenObjectType.validate. Output is git-ignored; the gate (tools/gate.ps1step 0) + CI regenerate before the Godot import. Round-trip + validation covered bytests/core/schema/schema_roundtrip_test.gdagainst the mirrored shared fixtures. - TypeScript codegen target —
quicktypeproduces interfaces;ajvproduces compiled validators; both land inQuiz.Designer/src/app/generated/. Output is git-ignored;npm run schema:genis invoked byng buildand on watch. - Shared semantic-fixture suite under
schemas/fixtures/— paired GdUnit4 / Jest harnesses assert the GDScript and TS validators agree on every input. - CI pipeline
.azure-pipelines/quiz-schemas.ymlpublishes both code drops as artefacts.
Load-bearing prototype MVP projecthostclient0 / 5
- In-process WebSocket server runs in the Host on Godot built-ins —
TCPServer+WebSocketPeerserver-side handshake on a dedicated autoload pumping_process, protocol interpretation in engine-freecore/protocol/— per Tech Stack. Server must support all three message families from MVP: Designer transfer, Client live-play, Remote control. The MVP control-message envelope must reserve room for the Alpha rich-command set and the Host-canvas frame stream so adding them is purely additive. - Host advertises itself via the pure-GDScript mDNS advertiser in
core/discovery/(PacketPeerUDPmulticast) per Tech Stack - Client discovers the Host via the matching
core/discovery/browser - Typed ping/pong message exchanged between Host and Client, validated against the generated GDScript envelope types
- End-to-end test: Client discovers Host and round-trips a message (GdUnit4, headless)
Remote app — minimum viable controls MVP remotehost0 / 10
The fourth app — Tauri 2 + Angular, see Applications — Remote; Remote-side implementation surfaces live in remote.md. MVP delivers the minimum viable controls (discovery, pairing, mirror, host-notes, live state, advance/go-back). The richer command set is in the Alpha section below.
- Remote discovers Hosts via mDNS —
mdns-sdRust crate behind a Tauri command (F-RE-1) - Pairing UX — Host displays both a short numeric code AND a QR; Remote can either type the code manually or scan (F-RE-2)
- Remote opens control-message-family WebSocket to paired Host (F-RE-3)
- Remote renders the downscaled Host-canvas frame stream pushed by the Host over the control family — the Remote never re-implements slide rendering (F-RE-4)
- Remote displays the per-slide host-notes for the current slide (F-RE-5)
- Remote displays live session state — scores, timer remaining, slide index (F-RE-6)
- Remote sends core navigation commands to the Host — advance, go-back (F-RE-7)
- Remote sends the answer-reveal cue and the buzzer
adjudicate(Correct / Incorrect) message to the Host — the MVP's only manual-marking surface, mirrored on the Host operator window (per Question Model — marking) - Remote handles disconnection and reconnection (Wi-Fi blip recovery; rejoins same paired Host) (F-RE-8)
- End-to-end test: Remote pairs with Host, sees the mirror + host-notes + live state, drives a full quiz session through advance/go-back including a buzzer adjudication and an answer-reveal cue
MVP object-type cohort MVP designerhostclient0 / 10
The canonical 8 MVP question types plus the two display elements, per the Question Model catalogue. Each question type implements the full plugin contract (Designer editor surface + input element + render/ scene) end-to-end: Designer authoring against the question node, Host-side auto-marking against question.answerKey (never shipped to Clients), the type's built-in answer-reveal presentation, and Client rendering + input.
Status (autonomous loop): the Designer editor surfaces shipped (Designer Alpha); the engine-free Host-side auto-marking for all 7 auto-marked types shipped as
Quiz.Stage/core/marking/answer_marker.gd(+ scoring/tiebreak incore/scoring/). What remains per type is therender/scene (Client input + Host audience presentation + built-in answer reveal) — [needs-Fable-pass], visual work the Opus loop does not author solo — plus the Host/Client socket wiring + the end-to-end test. So these bullets stay unticked: the cross-app runtime halves are visual + transport, not engine-free logic.
core.multiple-choiceend-to-end (Client tap-one-option input; Host auto-marks againstanswerKey.correctOptionId; built-in reveal highlights the correct option)core.free-textend-to-end (Client text entry; Host auto-marks againstanswerKey.acceptedAnswers+matchMode; built-in reveal shows the canonical accepted answer)core.first-letterend-to-end (Client single-pick A–Z grid, optional constrained subset; Host auto-marks againstanswerKey.correctLetter)core.numericend-to-end (Client numeric entry; Host auto-marks againstanswerKey.targetValuewithproximityexact / closestAbsolute / closestRelative; built-in reveal shows the target value)core.true-falseend-to-end (Client ✓ / ✗ tap; Host auto-marks againstanswerKey.correctAnswer)core.rankingend-to-end (Client drag-to-reorder; items text / image / both; Host auto-marks againstanswerKey.correctOrderwith all-or-nothing or per-position credit)core.eliminatorend-to-end (options vanish peranswerKey.eliminationScheduleor quizmaster cue; teams answer at any point, earlier correct answers score more via the speed bonus; quizmaster-cued final reveal)core.buzzerend-to-end (first-press race across Clients; Host receive timestamp authoritative; no answer key — quizmaster adjudicates Correct / Incorrect from the Host operator window or the Remote; incorrect excludes the team and re-opens buzzers)core.timerend-to-end (display element, either canvas; a view ofquestion.timing— carries no duration of its own; Host-authoritative ticks; lock-on-time-up + late-submission policy + quizmaster override driven from the question node)core.leaderboardend-to-end (display element, either canvas; per-team standings; trigger-driven reveal — on slide entry / on quizmaster trigger / after delay; reveal animation: full table / row-by-row / bottom-up)
Designer→Host transfer MVP designerhost0 / 7
- Designer can save a quiz to disk as a
.quizfile (manifest + slides) —SaveQuizCommandbundlesAuthoringSessionstate into aQuizPackage;PersistenceService.saveAsyncatomic-writes the ZIP via the Tauri fs command. Resource bundling lands with the bundle-on-save item. (F-DE-12) - Designer can re-open an existing
.quizfile from disk —MenuActions.openAsync→PlatformAdapter.pickFile→PersistenceService.openAsync→ReplaceActiveQuizcommand. (F-DE-13) - Designer discovers Hosts on the local network via mDNS — Tauri
mdns-sdRust command surfaced throughTransferService(F-DE-14) - Designer can push a saved
.quizfile to a chosen Host over the WebSocket transport — uses the standard browserWebSocketAPI (viarxjs/webSocket) from the Angular process (F-DE-14) - Host accepts incoming
.quiztransfers, validates the manifest against its built-in registry, and stores the file locally on operator confirm (F-HO-4, F-HO-7) - Host UI manual-confirm prompt for every incoming transfer ("Designer X wants to send 'My Quiz' (50 MB). Accept?")
- End-to-end test: Designer saves → Designer pushes to Host → operator accepts → Host loads → Host can start a session
Designer Run-from-slide subprocess [MVP — deferred] designerhost0 / 3
Deferred to the end of the MVP feature work. Ctrl+F5's spawnHost Tauri command currently throws notInTauri; the renderer-side shortcut is wired, the backend implementation lands here once the Host build (the host-windows / host-macos Quiz.Stage export) is signable + bundled as externalBin.
- Wire
tauri-plugin-shell+bundle.externalBinintauri.conf.jsonso the Designer ships with the Host binary inside its app bundle. TauriPlatformAdapter.spawnHostinvokes the bundled binary with--quiz <path> --start-at-slide <index> --launched-from-designer(parsed by the Host app's CLI handling at boot).- End-to-end: Ctrl+F5 on a saved
.quizlaunches the local Host with the active slide pre-selected.
Cross-platform validation MVP projectdesignerhostclientremote0 / 13
Cross-platform from day one — every platform stood up during MVP rather than expanded into post-MVP. Each item below is a check that the corresponding feature works on the named platform end-to-end (build, network discovery, transfer, live play). Not separate engineering work, but separate validation runs. Designer ships desktop only in MVP (Windows + macOS); iPad and Android tablet authoring are Stretch. Host ships Windows + macOS in v1 (iPad / Android tablet / Linux Host are Stretch). Linux is out of scope.
- Designer (Tauri + Angular) validated on Windows — Quiz.Stage web-export canvas embedded,
JavaScriptBridgeround-trip, multi-window via TauriWebviewWindowAPI, custom titlebar with Win 11 Mica, Run from slide subprocess spawn end-to-end - Designer (Tauri + Angular) validated on macOS — same surface as Windows; multi-window; macOS traffic-light titlebar overlay; Run from slide subprocess spawn end-to-end
- Host validated on macOS
- Host validated on Windows
- Client validated on iPhone
- Client validated on Android phone
- Client validated on iPad
- Client validated on Android tablet
- Remote validated on iPhone
- Remote validated on Android phone
- Remote validated on iPad
- Remote validated on Android tablet
- Per-platform Bonjour/mDNS validated
End-to-end vertical slice MVP projectdesignerhostclient0 / 2
- Automated end-to-end test: Designer authors a quiz (with per-slide host-notes) → Designer pushes to Host over LAN → Host loads → Clients join (eager push, answer keys stripped) → quizmaster pairs a Remote → quizmaster advances slides from the Remote → answers auto-mark Host-side against
question.answerKey→ quizmaster cues an answer reveal → leaderboard reveals on slide-entry → leaderboard finalises - Manual playthrough of a multi-round quiz (interstitials + multiple-choice + free-text + numeric + a buzzer round with Correct / Incorrect adjudication + timer + leaderboard, with an answer reveal per question) on a real Wi-Fi network on every supported platform combination, with the quizmaster walking the room and driving advance/go-back from a paired Remote
First rich object types Alpha designerhostclient0 / 3
core.audio-clipelement (Host canvas) — paired with thecore.free-textquestion type makes a music round- End-to-end test exercising a slide composed of audio + free-text answer
- Document the object-type plugin contract as a new knowledgebase page once it has stabilized across the Alpha cohort
Additional content object types Alpha designerhostclient0 / 2
core.imageelement (display on either canvas)core.videoelement (display on Host canvas)
(core.buzzer and core.ranking moved into the MVP object-type cohort as question types, and core.drawing moved to Stretch, per the Question Model / catalogue.)
Crash recovery Alpha hostclient0 / 7
- Host snapshots session state to disk after every scoring event and every slide advance (F-HO-18)
- Snapshot format: JSON DTOs built from the generated schema types — same shape as the live message envelopes, written to
user://, so deserialise == replay - Host on launch detects a saved session matching the loaded
.quizand prompts the operator to resume or start fresh (F-HO-19) - Resume restores slide pointer, team list, scores, per-element state; Host re-advertises via mDNS
- Client persists its team identity (Host-issued stable token) to
user://local storage (F-CL-10) - Client reconnect path attaches as the original team using the stored token, whether the Host disconnect was Wi-Fi or crash
- End-to-end test: simulated Host crash mid-session → relaunch → resume → all Clients reconnect with scores intact
Remote app — rich control commands Alpha remotehost0 / 6
Layered onto the MVP minimum viable controls — the rich command set that turns the Remote from "advance the deck" into "fully run the room from your pocket". F-RE-9 (Remote side) and F-HO-24 (Host side).
- Remote rich command: jump to a specific slide (F-RE-9, F-HO-24)
- Remote rich command: trigger element reveals (e.g. show the leaderboard, show the answer) (F-RE-9, F-HO-24)
- Remote rich command: lock / unlock Client input (F-RE-9, F-HO-24)
- Remote rich command: extend / skip the timer (F-RE-9, F-HO-24)
- Remote rich command: override scoring per team (F-RE-9, F-HO-24)
- End-to-end test: Remote drives a full quiz session using the rich command set — including a triggered leaderboard reveal mid-slide and a live scoring override
Reliability and performance Alpha projecthostclientremote0 / 5
- Reliability soak test: client disconnect storms, host backgrounding, Wi-Fi flap, simulated Host crash + recovery
- Performance: 60 fps animation budget verified on iPhone 12 / equivalent Android (measured in the exported Godot Client build)
- Element answer-submit round-trip latency < 200 ms on local Wi-Fi (verified)
- Remote control-message round-trip latency < 200 ms on local Wi-Fi (verified)
- mDNS reliability on real devices. Validate the pure-GDScript
core/discovery/advertise + browse (and the Designer/Remotemdns-sdbrowse) on iOS 16+, Android 12+, Windows 10/11, macOS 12+ over a typical pub-grade Wi-Fi router. iOS raw multicast requires the restrictedcom.apple.developer.networking.multicastentitlement — if the entitlement is refused, or multicast proves unreliable on any platform (advertisement drops, discovery misses Hosts, multicast blocked by the OS), land the thin native Bonjour bridge for that platform (NWBrowseron iOS,NsdManageron Android) behind the same discovery API. QR / manual-IP join remains the universal fallback. Conditional work — only landed if validation flags it; see Open Questions.
Image-reveal question type Alpha designerhostclient0 / 2
core.image-revealend-to-end (Host canvas: image obscured by author-picked shader filter — pixelate / blur / mosaic — clearing over the question duration; Client canvas: multiple-choice submit; earlier-correct scoring via configurable speed-bonus curve)- Inspector fields: reveal curve (linear / ease-out / stepped), filter type, filter start/end intensity, points-vs-time function
Match-pairs question type Beta designerhostclient0 / 1
core.match-pairsend-to-end (Client canvas; drag/connect pairing UX; left + right columns; items text / image / both; per-pair scoring)
Word-scramble question type Beta designerhostclient0 / 2
core.word-scrambleend-to-end (Host canvas: animated scrambled-letter tiles with drift / swap motion; Client canvas: buzzer-style first-press + text submit; first correct team scores)- Inspector fields: target word, scramble difficulty (animation intensity + hint cadence), wrong-answer penalty, per-team buzzer cooldown after wrong attempt
Multiple-choice + speed-bonus Beta extensions Beta designerhostclient0 / 3
Per the Question Model: elimination timelines belong to the MVP core.eliminator question type, and there is no element-level speed-bonus concept — Beta extends the existing single homes.
- Extend
core.multiple-choice'sanswerKeywith an optional multi-answer mode (multiple correct options must all be selected; partial credit configurable) - Additional
question.scoring.speedBonus.curvevalues (easeOut,stepped) beyond the MVPlinear - Both default off; inspector reveals them under an "advanced" disclosure
Brand-true polish Beta projectdesignerhostclientremote2 / 6
- Shared design language: typography, palette, motion vocabulary per Design Specification applied across Designer, Host, Client, and Remote (F-X-3)
- Final font picks locked: Bebas Neue (display + numerals), Inter (body), JetBrains Mono (mono)
- Final brand colours locked from
branding.jpgsampling: magenta#FF009F, electric-blue#16B2EB, deep-purple#961EEF, white#FFFFFF, near-black#0F0B1A - Mascot animation rig — rigged once under
Quiz.Stage/assets/with anAnimationPlayerpose library callable from Host, Client, and Preview; Designer + Remote chrome consume exported renders - Brand-true reveal/transition motion replaces the Alpha first-pass
- Final product/brand name locked (apps referred to by their engineering project names — Quiz.Designer / Quiz.Stage (Host / Client / Preview) / Quiz.Remote — until then)
Theme system Beta designerhostclientremote0 / 5
- Designer chrome ships dark + light themes; author choice persisted in app settings (F-DE-25)
- Quiz manifest gains a
themefield (dark/light/ brand presets) (F-DE-26) - Host renders each loaded quiz against the manifest's declared theme (F-X-4)
- Client renders each loaded quiz against the manifest's declared theme (F-X-4)
- Remote renders the host-mirror in the manifest's declared theme (F-X-4)
Per-team theming Beta designerclienthost0 / 4
- Author defines a palette of team colours and an avatar set in the quiz (F-DE-21)
- Team picks colour + avatar at join (F-CL-12)
- Team identity (colour + avatar) renders consistently across every Client surface
- Per-team Host moments (leaderboard rows, team callouts) use the chosen colour and avatar
Mini-game framework Beta hostclient0 / 5
- Mini-game framework (native Godot scenes) and a
core.mini-gameelement (F-CL-7) - Mini-game registry (id → mini-game module) resolved by the
core.mini-gameelement at slide load mini-game.internal-clockshipped as a built-in (Host runs a hidden countdown; teams press to stop their own clock with no visible number; closer to configurable target time scores more; overshoot scores zero by default; inspector fields: target time, points-vs-error curve, overshoot policy, per-team retry rule)mini-game.team-shootshipped as a built-in (Host flashes a team name; that team races a configured opponent — named team or next-fastest — to tap their Client; wrong-team presses lose configurable points; inspector fields: match-up rule, win/lose points, wrong-team penalty per team, reaction window)mini-game.spin-wheel-modifiershipped as a built-in (post-answer flourish; spinning wheel with± valuesegments; lands on a segment that applies the modifier to the configured target teams; inspector fields: segment list, spin duration, target rule, parent-question linkage)
Sound design Beta projecthostclientremote0 / 5
Full audio language per Design Specification — sound design (F-X-5).
- Source / commission stings: correct, incorrect, lock, time-up, big reveal, end of round, end of quiz
- Source / commission transition motifs: slide-advance whoosh, leaderboard reveal swell, round-change motif
- Optional ambient music bed (light, between-rounds), author-controllable per quiz
- Audio asset licensing decided (royalty-free pack, commissioned, or hybrid)
- Audio mix tuned for venue — stings cut through pub ambient noise without being intrusive
Quiz UK pilot Beta project0 / 4
- At least one real Quiz UK pub night runs successfully on the Beta build
- Real-device testing across all platforms — modern + older device on each side of iOS / Android
- Performance and latency targets re-verified on older test devices
- Hardware/OS minimum versions confirmed against real devices and Godot's platform support matrix.
Pre-release Production project0 / 9
Confirmed distribution channels: iOS App Store, Google Play Store, Microsoft Store, macOS App Store, macOS direct DMG.
- iOS App Store: signing, packaging, submission flow, update mechanism
- Google Play Store: signing, packaging, submission flow, update mechanism
- Microsoft Store: signing, packaging, submission flow, update mechanism
- macOS App Store: signing, packaging, submission flow, update mechanism
- macOS direct DMG: notarization, distribution, update mechanism
- Sign in with Apple wired up (only relevant if cloud auth Stretch ships first)
- Privacy policy drafted and published
- Store listings drafted (descriptions, screenshots, age rating, accessibility statements)
- Crash reporting / telemetry decisions made
Help documentation Production projectdesignerhostclientremote0 / 5
Public-facing help docs the apps link to from their Help menus. Published to the same Cloudflare Pages site as the dev docs (or a sibling subdomain — decide at delivery time).
- Author user-facing help docs: getting started, building your first quiz, hosting a quiz night, joining as a team, using the Remote, keyboard shortcuts cheatsheet, troubleshooting (mDNS / Wi-Fi / device pairing).
- Decide where the help site lives (e.g.
help.quiz.adamlewis.me) and stand it up via thecloudflare-pagesskill. - Wire each Designer Help-menu entry (
Documentation,Keyboard shortcuts,Release notes,Report an issue,About Quiz Designer) to its live URL. Replace the interimdocs.quiz.adamlewis.meplaceholder link wired in MVP. - Wire equivalent Help entry points in Host, Client, Remote.
- Release-notes feed: per-version changelog rendered as a static page (and an in-app
What's newdialog on first launch after upgrade).
Stretch goals Stretch projectdesignerhostclientremote0 / 40
These items deliver the cloud-backed authoring path sketched in Backend Schema and Authentication, plus other Stretch work. None are in v1 scope.
Cloud-backed authoring
- Pick cloud vendor (managed Postgres + object storage) and provision the project
profiles,quizzes,quiz_versionstables (F-DE-15)- RLS policies on tables (
owner_id = auth.uid()) - Storage RLS scoped to
quizzes/{owner_id}/... - Version pruning job (latest 20 + pinned)
- Email magic link sign-in on Designer (F-DE-1)
- Email magic link sign-in on Host (F-HO-1)
- Designer: save quiz to cloud, version history, restore (F-DE-15)
- Designer: soft-delete + trash view (F-DE-16)
- Designer: pin a version to protect from pruning
- Host: list quizzes from the cloud library, mark which are downloaded (F-HO-2)
- Host: download and cache a quiz package from the cloud (F-HO-2)
Additional question types
core.hotspot(tap on image coordinates; map / anatomy / geography rounds)core.drawing(Client capture, Host display, live mirror; touch-only; quizmaster-marked — moved out of v1)core.categoriesend-to-end (Client canvas; multi-line entry; per-line scoring; "Name 5 X" format — moved out of v1)
AI-aided quiz authoring (F-DE-22)
- LLM integration in Designer with verification UX (every generated artefact requires explicit author confirmation)
- Generation modes: question on a topic, multiple-choice distractors, host-notes draft from a question + answer
- Privacy posture decision: third-party API vs local model
Broadcast / streaming-friendly Host view (F-HO-22)
- Alternative Host display mode: subdued backgrounds, boosted overlay contrast, in-room-only chrome suppressed
- Toggleable from the Host (and via the Remote) without restarting the session
Recurring teams across sessions (F-HO-23, F-CL-13)
- Team-identity store keyed by quizmaster + venue (depends on cloud auth)
- Team-claim UX on Client join ("Are you returning team X?")
- Privacy and data-protection posture documented alongside
Tournaments / leagues
- Season schema in the cloud library (depends on recurring teams + cloud-backed authoring)
- Cross-session scoring rules
- League standings UI on Designer/Host
Quiz templates / starter packs (F-DE-23)
- Cloud library exposes templates as a distinct kind of quiz
- Designer "clone from template" flow makes a private, editable copy
Question bank / reusable questions (F-DE-24)
- Question schema decoupled from a specific quiz (cloud library)
- Designer "pull from question bank" flow into any quiz
Speed-round mode
- Round-level config: very short timer, no inter-slide pause, no countdown UI
- Could be promoted to Alpha as a small extension to round timing schema
Other stretch
- Bundle-supplied object types loader (signing, sandboxing, install UX — see OQ#1)
- Apple Pencil sketching/annotation in Designer (F-DE-17)
- Equivalent stylus support on non-Apple tablets (F-DE-17)
- Per-individual identity within a team — would change the eager-push model and join screen
- Multi-Remote support (co-quizmasters, conflict resolution, override semantics)
- Faster-cadence Host crash recovery (per-message snapshots; multi-instance Host failover)
- Cross-quiz analytics — see OQ#3
- Internet-based live play relay (Cloudflare Durable Objects or similar) — see OQ#2
Designer74 / 86
Tauri 2 + Angular Designer setup MVP designer8 / 8
Per Designer Shell. Single Tauri 2 workspace hosting an Angular 21 application; desktop only (Windows + macOS).
- Create
Quiz.Designer/as a Tauri 2 + Angular 21 workspace (pnpm create tauri-appthenng newinto the same root, or scaffold by hand per Repository Layout — Designer project graph). Angular CLI workspace targets ES2022; Tauri Rust shell targets Rust 1.78+. - Pin Angular to the latest stable major (21.x at time of MVP; check
npm view @angular/core dist-tagsand use the latest); pin Tauri to 2.x stable. - Configure
src-tauri/tauri.conf.jsonwithdecorations: false,titleBarStyle: "Overlay"on macOS,windowEffects: { effects: ["mica"] }on Windows 11, an initial window size matching the mockups, and aQuiz.Designerapp identifier. - Wire
tauri-plugin-dialog,tauri-plugin-fs,tauri-plugin-process, Tauri 2's built-in menu API (src-tauri/src/menu.rs), and themdns-sdRust crate via Tauri commands undersrc-tauri/src/commands/. - Bootstrap the Angular workspace under
Quiz.Designer/src/with Angular Material + Angular CDK + RxJS; configure standalone-components architecture, signal-based reactivity, and zoneless change detection. - Override Angular Material primary / secondary / surface tokens in a custom CSS-variable theme that mirrors the Studio chrome surface tokens defined in Design Specification — Designer language. Showtime brand colours stay inside the embedded slide-preview canvas only.
- Splash + boot-sequence window. Tauri opens a borderless 720 × 480
splashWebviewWindowfirst, loading the AngularSplashComponent. Splash pre-warms async services (schema codegen check, library index hydration, slide-preview ready event, mDNS listener init) and reports stage events to the splash UI. On all-ready, Tauri opens the main shell window at the saved size, focuses it, and fade-closes the splash (~120 ms). Splash is brand-locked Showtime and ignores the Studio chrome theme — identical visual in light and dark. Per Designer Shell — Boot sequence. - Custom Angular titlebar component (
<app-titlebar>) implements the Rider-style strip: logomark + wordmark + File / Edit / View / Help dropdowns + doc title + dirty dot + Undo / Redo + autosave badge + Run-from-slide CTA.-webkit-app-region: dragfor the drag region;setTrafficLightPositionfor macOS traffic-light placement; right-edge padding on Windows to clear the OS caption buttons. Per Designer Shell — Custom titlebar.
Designer composition + service layer MVP designer6 / 6
The Angular DI graph and the platform-adapter layer that lets the same code run under Tauri today and the browser later.
PlatformAdapterinterface insrc/app/platform/declares file-dialog, filesystem, mDNS, subprocess-spawn, and OS-menu capabilities.TauriPlatformAdapterimplementation wires each capability to the matching Tauri command.AuthoringSessionAngular service —activeQuiz,selection,isDirtyexposed as signals + observables;QuizChanged/SelectionChanged/DirtyChangedevents.CommandDispatcherAngular service —execute()+undo()pipeline; per-quiz undo / redo stacks. First two concrete commands —CreateNewQuizandReplaceActiveQuiz— validate the contract end-to-end.PropertyDispatcherhelper that batches keystroke edits in inspector components and emits commands at commit boundaries — per Designer Shell — Undo / redo granularity.- DI bootstrap registers
AuthoringSession,CommandDispatcher,ObjectTypeRegistry(seeded withcore.textinitially), and the platform adapter.
Designer persistence + autosave MVP designer11 / 11
PersistenceServiceAngular service —.quizsave / load viaTauriPlatformAdapter's fs commands + thezipRust crate; recent-files index in the platform app-data folder.- Atomic write protocol for every disk write — delegated to
tauri-plugin-fs'swriteFile(plugin handles the safe-write semantics; no custom Rust command). Stranded.tmpGC on launch. - Autosave timer — debounced (8 s after last edit, 30 s ceiling); RxJS-driven scheduler with injectable clock for headless tests.
- Tiered slot retention — keep snapshots at anchor ages (now / 5 min / 1 hour / 6 hours / 1 day / 7 days); evict the rest.
- File → Restore from backup… Angular dialog listing surviving slot backups for the active quiz. Picking a backup loads it as
activeQuizwithisDirty = true. - Corruption detection on Open — when a
.quizfails to load, surface a banner "Couldn't open. The most recent backup is from - Recovery flow on launch with no quiz arg — empty-state banner listing orphaned drafts ("
drafts from an earlier session"). - Crash detection — clean-exit marker written on shutdown; absence on next launch indicates an unclean exit.
- Snapshot GC pass on launch — cleans stranded
.tmpfiles, evicts past-cap backups, removesAutoSaves/<quizId>/folders whose source has not been opened in 60 days, removesAutoSaves/<sessionId>/draft folders older than 30 days. - Persistent autosave-failure banner ("Autosave failed 3× — your work is at risk") after 3 consecutive backup-or-write failures within 2 minutes. Backup failure aborts the autosave write — never write the new state without a backup.
- Bundle-on-save / unbundle-on-load logic spanning Persistence + Library: Save copies referenced library bytes into the
.quizarchive'sresources/; Open content-hash-de-dupes archive resources against the local library. Round-trip test verifies a saved-then-opened quiz preserves every resource id and bytes match.
Designer library + file pickers MVP designer4 / 4
LibraryServiceAngular service — device-wide media catalog at platform app-data folder; SHA-256 content-hash de-dup (Web Crypto API in the renderer);LibraryAssetrecord.- File-picker capability on
PlatformAdapter— Tauri impl wrapstauri-plugin-dialog. - macOS entitlements /
Info.plistfor file access — declareapp-sandbox,network.client,network.server,files.user-selected.read-write. - Windows installer manifest opts the app out of
Program Filesplacement constraints that would block library writes.
Designer object types (Designer side) MVP designer2 / 2
ObjectTypeRegistryAngular service — editor-surface registry on the Designer side of the plugin contract. First registered type iscore.text(paired with the cross-cutting Object-type plugin contract section in project.md).- First Angular properties-inspector component for
core.text, bound to the type's view-model service via thePropertyDispatcherhelper.
Designer transfer skeleton MVP designer1 / 1
TransferServiceAngular service — wraps the LAN-discovery + WebSocket push path. Skeleton lands asStubTransferServicethat throws onpushAsync/discoverHostsAsync; the real impl lands with the Designer→Host transfer chunk in project.md.
Designer input + shortcuts MVP designer3 / 3
- Global keyboard-shortcut router — Angular service installs a
keydownlistener that routes Ctrl/Cmd-Z / Shift-Z / S / O / Delete / arrows toCommandDispatcheror component event outputs. Ignores keypresses inside inputs / contentEditable so typing into the inspector still works. - PowerPoint-style preview shortcut bindings:
F5/Shift+F5/Ctrl+F5are registered inShortcutRegistry, but the actions are no-ops until the deferred Quiz.Stage preview chunk lands the preview mode + Run-on-Host spawn they trigger. - Drag-drop into the design surface — Angular CDK drag-drop wires palette → canvas and slide-list reorder. Touch fallback via long-press gesture.
Designer UI / component skeletons MVP designer1 / 1
- Stub Angular standalone components:
ShellComponent(3-column layout via CDK),SlideListComponent,PaletteComponent,PropertiesInspectorComponent,LibraryPanelComponent,PushToHostDialogComponent,LibraryPickerDialogComponent,RestoreFromBackupDialogComponent. Each subscribes to the matching service event (AuthoringSession.QuizChanged/SelectionChanged,LibraryService.Changed) and cleans up subscriptions viaDestroyRef.
Designer panel layout — resize, move, pop-out MVP designer5 / 5
Per the Designer Shell, the v1 shell needs operator-customisable layout — panels that resize, drag-reorder, and detach into separate windows.
- Column splitters — draggable splitter handles between the slide-list / canvas / right-rail columns. Drag clamps each column to a min / max range; double-click resets to the mockup default. Remaining: widths persist to a
PreferencesServiceslice keyedquiz.designer.layout.v1(the service lands alongside the autosave-retention chunk). - Pane registry —
LayoutStateenumerates the panes (slide-list,canvas,right-rail) and the dock zones (left/center/right/popped); aPaneHostComponentresolves a pane id to its component. The shell renders each zone fromLayoutService.stateso panels are re-homed by moving ids, not by rewriting layout markup. - Drag-reorder between dock zones — each pane carries a drag grip; dropping it on another dock zone calls
LayoutService.movePane. Layout changes are workspace state, deliberately not routed through the quizCommandDispatcher(a panel move should not sit on the document undo stack alongside slide edits — consistent with Designer Shell — Undo / redo granularity treating workspace operations as session, not authoring, actions). Persistence viaPreferencesServicelands alongside the autosave-retention chunk. - Pop-out to a separate window — pane header has a "↗ pop out" button. Tauri shell binds this to
WebviewWindow.new("pane-<id>", { url: "/pane/<id>" })via thepop_out_panecommand; closing the pop-out re-docks the pane to its previous slot via thepane:redockevent the backend emits onWindowEvent::Destroyed. Web-shell binding towindow.openlands with the BrowserPlatformAdapter (Web Designer Stretch). - Layout reset — the titlebar
View → Reset Layoutmenu item callsLayoutService.reset, restoring the defaultslide-list/canvas/right-railshape; the reset is persisted so it survives a restart. Preview pop-out as the first concrete use is deferred alongside the rest of the Quiz.Stage preview chunks below.
Designer authoring features MVP designer14 / 14
- Create new quiz —
CreateNewQuizCommandis wired to the slide-rail+ New quizbutton + the File menu. (F-DE-2) - New-quiz title / description / tags entry dialog so the user picks an identity instead of
CreateNewQuizCommandhard-coding "Untitled". (F-DE-2) - Add, edit, reorder, delete slides —
AddSlideCommand+DeleteSlideCommand+ click-to-select + CDK drag-reorder viaReorderSlidesCommandare wired intoSlideList. (F-DE-3) - Per-slide rename — inline-edit affordance on each slide-list row, dispatching
SetSlideTitleCommanddirectly throughCommandDispatcher(blur / Enter commits; Escape cancels). (F-DE-3) - Group slides into rounds; reorder, ungroup (F-DE-4)
- Place / move / configure / delete elements on the Host canvas (F-DE-5)
- Place / configure / delete elements on the Client canvas (F-DE-6)
- Object-type palette listing built-in types — initially
core.text, expanded as the MVP cohort lands (F-DE-7) - Configure scoring per slide and per round, including late-submission rules (F-DE-9)
- Configure timing per slide: duration, lock-on-time-up, late-submission rule, quizmaster-override-allowed (F-DE-10)
- Per-element reveal trigger UI: choose on-slide-entry / on-quizmaster-trigger / after-delay (F-DE-20)
- Per-slide host-notes editor (free-form text, rendered only on the Remote during play) (F-DE-19)
- Package as canonical
.quizformat —quiz-package-codec.ts(encodeQuiz/decodeQuiz) +PersistenceServicewrite + read the ZIP (manifest.json + slides/*.json), data-only, no runtime code. (F-DE-18) - Auto-derive the manifest's
objectTypes[]registry on save from the element type-ids actually used across the slide tree, so the Host can refuse / upgrade packages whose declared cohort doesn't match its built-in registry. (F-DE-18)
Question-first slide model MVP designer7 / 7
The Designer-side landing of the canonical Question Model — every slide is a question or an interstitial, and the question is a first-class domain node the picker, inspector, palette, and validators hang off.
- Schema migration: slide gains
kind("question"|"interstitial") + aquestionnode (prompt,answerKey,scoring,timing,inputBinding);scoring/timingmove off the slide onto the question node; round/manifest restructure —manifest.slideOrderis the sole order authority,slide.roundIdthe sole membership authority,manifest.rounds[]metadata-only (noslideIds;slide.indexdeleted). Regenerate codegen; update the persistence codec (quiz-package-codec.ts) + validators. Pre-alpha: no compat shims —.quizfiles from earlier dev builds are discarded, not migrated. - "+ Add slide" question-type picker + per-type slide templates (the 8 MVP question types + Blank (interstitial)): picking a type scaffolds prompt text elements on both canvases (template-bound to
question.prompt), a pre-placed input element on the client canvas bound viaquestion.inputBinding, and default scoring/timing. - Question-first slide inspector — prompt, per-type answer-key editor, scoring, timing at the top; canvas/presentation concerns below. Interstitial slides show a presentation-only inspector.
- Slide-rail kind/type badges on every slide; a question slide with a missing/duplicated input binding renders a blocking badge, and the package refuses to export while any binding is invalid (
quiz-validation.tsdrives both the rail badges and thePersistenceServicesave gate —QuizValidationErrorsurfaces the issue list; autosave drafts bypass the gate so no work is lost). - Palette behaviour: input elements are scaffold-owned — placing a second input element on a question slide is rejected (
AddElementToCanvasCommandrefuses question-type ids; a palette drop of a question type is a slide-level intent); changing the question type — or dropping one on an interstitial — re-scaffolds the slide behind a confirm dialog (ChangeQuestionTypeCommand). - Designer-side editor surfaces for all 8 MVP question types — palette/template entries, per-type answer-key inspectors, and slide-thumb renderers (plus
core.timer/core.leaderboarddisplay-element editors). - Test coverage per the applicable Definition of Done types: Jest (schema fixtures + validators, question-model + templates, every command incl. change-question-type / set-question-prompt / set-question-answer-key, quiz-validation, every per-type element + answer-key editor) and Playwright e2e incl. a11y (slide-type picker, question inspector, per-type element editors, validation badges + export refusal, media elements) are green. Tauri IPC contract + Rust unit: N/A — the question-first work is pure schema + TypeScript; no
src-tauricommand surface changed. Visual screenshot-compare is the one gap: the new surfaces (question-type picker, per-type answer-key editors, prompt-bound canvas previews) have nodocs/ui-mockups/designer/counterpart to diff against yet — the mockups + visual pass land with thefrontend-designpolish chunk before Alpha sign-off.
Designer Quiz.Stage preview bridge [MVP — deferred] designer0 / 7
Deferred to the end of the MVP feature work. The static DOM Host / Client canvas inside the Designer's main canvas panel is enough to author a .quiz package against — the embedded preview only shows how the slide will look once it lands on the Host + Client runtimes, so it can land last without blocking the rest of the authoring surface.
- Bundle the Quiz.Stage web export (
preview-webpreset) underQuiz.Designer/src/assets/preview/(consumed from thequiz-stage.ymlCI artefact at build time) and load it inline as a<canvas>via the Godot web loader. PreviewBridgeAngular service — pushes slide JSON to the embedded canvas through the export'sJavaScriptBridgewindow-level API; subscribes to the ready / rendered / error callbacks the preview app raises back into JS.- First end-to-end push: load a placeholder
core.textslide, push state, observe re-render in the embedded canvas, log therenderedcallback. - Reconnect strategy: if the canvas reports
erroror fails to load, retry with backoff and surface a banner. - Embedded preview canvas reflects the slide live as it is edited — every state mutation pushes JSON to the canvas via
PreviewBridge(F-DE-11) - Preview pop-out is the first concrete use of the multi-window pane framework — preview canvas lives in its own pane so authors can run it on a second monitor while they edit. Default keyboard shortcut:
Ctrl/Cmd + Shift + P. - Smoke-test launch via
tauri devon Windows and macOS — splash → main shell with embedded preview canvas visible and a previewreadyevent observed.
Quiz.Stage preview surface [MVP — deferred] designer0 / 4
Deferred alongside the preview bridge. The preview entry app inside Quiz.Stage/ — no editing UI and no rendering of its own: a pure render target that binds the Designer's JS-bridge state pushes onto the same render/ scenes the Host and Client instantiate at quiz time. Exported via the single-threaded preview-web preset.
apps/preview/preview_main.tscn— render-target scene hosting the slide canvas; loaded byboot.gdon thepreviewfeature tag.apps/preview/preview_main.gd— registersJavaScriptBridgecallbacks for the window-level messages (LoadSlide,UpdateProperty,SetSelection); raisesready/rendered/errorback into the host page. Message dispatch lives below theJavaScriptBridgeseam so it is GdUnit4-testable headless.- Canvas size driven by the host page (responsive
<canvas>); pushed slide state binds onto the sharedrender/object-type scenes. - Smoke-test: the web export loads in a plain HTML page, accepts a
LoadSlideJS call, renders a placeholder slide, emits arenderedcallback.
Designer Help — Report an issue MVP designer6 / 6
In-app issue reporting so users on a sealed-down install can flag bugs without a separate browser session. Help → Report an issue opens a form (panel on platforms without multi-window support, dedicated window otherwise) with fields for the user's email, a short summary, a longer description, steps to reproduce, and an optional attachments tray (screenshots / log excerpts). Submission target picked at chunk time: generic HTTPS webhook (rationale in src-tauri/src/services/issue_report.rs — ADO Boards intentionally unused, GitHub N/A, webhook composes with any later ingestion without shipping auth secrets), with a retry-with-clipboard fallback when the endpoint is unconfigured or unreachable.
ReportIssuePageComponent— modal dialog window per the two-tier pattern (DialogStackreport-issueregistration +/window/dialog/report-issueroute, native OS window under Tauri,<qd-dialog-overlay>in browser). Form: email + summary + description + repro steps + attachments tray with remove. Submit gated on email (contains@) + non-empty summary.- Attachment plumbing: new
PlatformAdapter.pickAttachments()capability returning{ filename, bytes, mime, size }[]— Tauri impl = multi-select plugin-dialog open + readFile per path; browser fallback = hidden<input type="file" multiple>(Playwright-drivable). Shared base64 + extension→MIME helpers inplatform/attachment-codec.ts. - Submission backend: HTTPS webhook configured per install via the
QUIZ_ISSUE_WEBHOOKenv var, read only in the Rust shell.submit_issue_reportcommand →services/issue_report.rs(validation + payload shaping +HttpPosterseam,ureq/rustls POST with 15 s timeout, 5 MiB attachment budget). Unconfigured → typedcapabilityenvelope; transport/HTTP failure → newnetworkCommandErrorvariant. No auth secrets exist (the URL is the only credential and never reaches the WebView). - Clipboard fallback: on
capability/networkfailureReportIssueServicecopies a markdown digest (summary/description/repro + attachment names) vianavigator.clipboardand the form shows arole="alert"notice — "Couldn't reach the server — the report has been copied to your clipboard." The form keeps its values for retry. - Help menu wiring —
report-issueitem enabled (placeholderdisabledWhenremoved); handler opens the DialogStack entry. No default shortcut by design. - Jest (service 8 specs: validation, sent, capability/network fallbacks, digest content; page component 5 specs) + Rust unit + IPC contract tests (envelope pins for capability / network / invalid-argument / happy path) + Playwright (validation gating, attachment pipeline via the browser file input, clipboard fallback with real clipboard read-back, happy path against a Playwright-intercepted endpoint, a11y).
Designer library-backed colour palettes MVP designer5 / 5
The colour picker ships with a fixed default palette today; authors want to keep multiple curated palettes in the library and switch which one the picker exposes. Saved palettes also feed the Designer's broader theming surface (per-quiz theme, brand presets) once those land.
PaletteService(Quiz.Designer/src/app/library/palette.service.ts): signal-backed list +activecomputed; CRUD (create / rename / remove / reorder / setActive / addColor with copy-on-write default cloning); cross-window invalidation via the newpalette:changedbus topic (envelope-only, sourceWindowId echo-suppressed).- Library schema:
schemas/library/palette.schema.json(id, name, colors[], updatedAt) +palette-index.schema.json(activeId, order[]), both codegen'd to TS types + ajv validators. Persisted under<appData>/Library/palettes/<id>.json+index.jsonviaPlatformAdapter. Default palette is synthesised in memory, read-only, never written to disk. - Library UI: new
librarypane (LibraryPanelComponent) in the left tab stack listing palettes with rename (inline edit) + delete + CDK drag-reorder + active-radio. In-picker "Manage palettes…" link reveals the pane via the newLayoutService.revealPane(forwarded overpanels:layout-intentfrom floating tool windows); hydrate now adds registered-but-unsaved panes to the closed list so new panes stay reachable on stale persisted layouts. - Wire
ColorPickerComponenttoPaletteService— palette grid + switcher render the service's signals; create/select/add-current route through the service (the localStorage store is deleted). - Jest unit tests for the service (13 specs incl. invalid-file skip, index round-trip, echo suppression, write-failure resilience) + library panel + layout revealPane/hydrate; Playwright e2e covering palette CRUD round-trip, picker following the active swap, Manage-palettes reveal, a11y.
Designer additions for Alpha Alpha designer1 / 2
- Object-type palette includes the Alpha media cohort:
core.image(shipped earlier),core.audio-clip(source pick + autoPlay / loop / volume / showPlayer / fade, with the Reveal-trigger hint for cued/delayed playback),core.video(source pick + autoPlay / loop / muted / volume / showPlayer / fit, first-frame preview). Each lands a palette tile (group "Media"), an on-canvas preview, a slide-thumb paint, and an inspector. (core.buzzermoved to MVP as a question type;core.drawingmoved to Stretch — neither belongs in this bullet anymore.) core.image-reveal— deferred pending contract + Stage runtime. It is a cross-app question type (tracked in project.md — Image-reveal question type), not a media element: its defining behaviour is the obscuring shader clearing over the question duration (aQuiz.Stage/render/runtime that does not exist yet), and its shape (image-reveal display on the host canvas + a multiple-choice input on the client) breaks the currentinputBinding.type == questionTypeinvariant the export validator enforces. The Question Model catalogue locks 8 MVP types; adding image-reveal needs a contract revision first (see Open Questions). Authoring it now would invent spec rather than implement it.
Host0 / 31
Quiz.Stage app surface — Host MVP host0 / 5
apps/host/host_main.tscn— pre-quiz lobby (load.quiz, accept Designer transfers, list connected teams, start session); loaded byboot.gdon thehostfeature tag or--app=host.apps/host/play/slide-runner scene — advances through the quiz, instantiates each slide's Host canvas from the sharedrender/object-type scenes, and dispatches element behaviours for the live session.host-windows+host-macosexport presets — landscape desktop window defaulting to 1920×1080 (TV / projector target), per-app product name / icons / bundle id,hostfeature tag; export filters keep Client- and Preview-only assets out of the Host artefact.- Dual-window operator view (F-HO-25) — when more than one display is connected the audience window owns the chosen display (clean slide canvas) and a second OS-level
Windowrenders the operator surface: previews of the Host canvas + a representative Client canvas for the current slide, the slide's host-notes, the session HUD (timer, scores, slide index), Next / Previous / Show-scoreboard / Reveal-answer controls, and the buzzer Correct / Incorrect adjudication buttons. On a single-display machine the operator surface collapses to a summonable overlay. - Smoke test:
godot --headless --app=hostboots to the lobby with no script errors; GdUnit4 scene test pins the boot dispatch intohost_main.tscn.
Host core logic — engine-free MVP host0 / 3
Plain-RefCounted GDScript under Quiz.Stage/core/ — no Node, no scene-tree access — per Separation of Concerns; every module GdUnit4-unit-tested headless.
core/packaging/—.quizread via Godot's built-inZIPReader; manifest parse + validation against the generated GDScript types; schema-version check on load.core/scoring/— auto-marking + scoring rules as pure functions over the generated types: mark submissions againstquestion.answerKey, applyquestion.scoring(base points, linear speed bonus off the Host receive timestamp,tiebreak) andquestion.timing's late-submission policy. Per Question Model.- Session state machine in
core/protocol/— lobby → joining → in-slide → locked → reveal → ended transitions driving the slide runner (interstitial slides collapse to show → advance); Host-authoritative, serialisable for the crash-recovery snapshot.
Host live-session features MVP host0 / 17
- Open the embedded WebSocket server —
TCPServer+WebSocketPeerhandshake on a dedicated autoload pumping_process— and advertise on the local network via thecore/discovery/mDNS advertiser (F-HO-3). Server + discovery stand-up per project.md — Load-bearing prototype. - Load
.quizfrom local FS via file picker (in addition to Designer push), throughcore/packaging/(F-HO-5) - Loaded packages remain available offline (F-HO-6)
- Resolve every object type the loaded package declares against the built-in registry; refuse on missing or version-incompatible types (F-HO-7)
- Start a session from a loaded package (F-HO-8)
- Join screen lists connected teams (one Client device per team) with team names (F-HO-9)
- On Client connect, eagerly push Client-canvas content + Client-side resources for the whole quiz — with every
question.answerKeystripped at package-load time into a Host-side session answer table; phones never hold answers (per Question Model — answer-key custody) (F-HO-10) - Advance through slides; render each slide's Host canvas — the fixed 1920×1080 virtual canvas scaled to the connected display's resolution via the shared
render/scenes (F-HO-11) - Receive
answerSubmitmessages (question-addressed:slideIdis the question id, resolved throughquestion.inputBinding; last-write-wins until lock,inputLockedprotocol error after), auto-mark against the session answer table, applycore/scoring/rules (including late-submission policy), render leaderboard on triggered reveal (F-HO-14) - Built-in answer reveal: on quizmaster cue (operator button or Remote command) the Host emits
answerReveal { slideId, presentation }and renders the question type's built-in answer presentation on the audience window — driven entirely fromquestion.answerKey, never hand-placed answer text (per Question Model — marking, reveal, results) - Buzzer adjudication — the MVP's only manual-marking surface: first press locks all buzzers (Host receive timestamp authoritative); the quizmaster marks via the operator-window Correct / Incorrect buttons or the Remote's
adjudicatemessage; incorrect excludes the team (optional configurable penalty) and re-opens buzzers for the rest;scoreUpdate.reasoncarries"adjudicated" - Host is authoritative on timer state, driven by
question.timing(core.timerelements are views of it and carry no duration of their own); emits "time-remaining" tick and "lock" message (F-HO-16) - Host operator can override timer live for the current slide (extend / skip / lock / unlock) (F-HO-17)
- Handle Client disconnect/reconnect without ending the session (F-HO-15)
- Host accepts a paired Remote on the control-message-family WebSocket; pairing shows a short numeric code AND a QR encoding it simultaneously (F-HO-20)
- Host streams the downscaled frame stream of its audience display, the current slide's host-notes, and live state (scores, timer remaining, slide index) to the paired Remote over the control family (F-HO-21)
- Host accepts core navigation commands from the paired Remote: advance, go-back (F-HO-21)
Marking queue + session results Alpha host0 / 2
Per Question Model — marking, reveal, results.
- Marking queue on the operator window — lists per-team submissions for the current question (free-text especially) with accept / reject toggles that re-score retroactively via the generalised
adjudicatemessage; editingquestion.answerKeyand re-running the marker are the same operation (F-HO-29 live answer-correction). Audience-visible vs silent re-score semantics tracked in Open Questions #12. - Per-question results record (team → submission, verdict, points, receive timestamp) kept in the session snapshot; Host can export the session as JSON (final standings + per-round + per-question tables) at session end. Export schema shape tracked in Open Questions #21.
Team identity rendering Alpha host0 / 2
Pairs with the Client-side join identity work in client.md; lands alongside the relevant Alpha object types per Applications — Host.
- Render each team's photo (or fallback premade avatar) on leaderboard rows, the join-screen roster, and per-team callouts; photos held in memory + the session snapshot, cleared at session end (F-HO-26)
- Play the per-team buzzer jingle on a first-press win; generic cue fallback when no jingle was picked; operator-global mute without ending the session (F-HO-27)
First-pass animation Alpha host0 / 2
- Animated reveals and transitions on Host slides via
AnimationPlayer/Tween— first pass, not yet brand-true (F-HO-13) - Leaderboard reveal animations — first pass
Client0 / 19
Quiz.Stage app surface — Client MVP client0 / 4
apps/client/client_main.tscn— discovery + join flow (list visible Hosts, enter team name, join); loaded byboot.gdon theclientfeature tag or--app=client.apps/client/play/surface — renders the current slide's Client canvas from the sharedrender/object-type scenes, dispatches input elements, shows the team score.client-android+client-iosexport presets — autorotation (Client supports portrait and landscape on phones and tablets), per-app identity (product name, icons, bundle / application ids), mobile permission manifests; export filters strip Host- and Preview-only scenes to keep the mobile artefact lean.- Smoke test:
godot --headless --app=clientboots to the join screen with no script errors; GdUnit4 scene test pins the boot dispatch intoclient_main.tscn.
Client team-play features MVP client0 / 12
- Discover hosts via the
core/discovery/mDNS browser overPacketPeerUDPmulticast (F-CL-1) — the iOS multicast-entitlement decision is tracked in Open Questions; QR / manual-IP join is the universal fallback - Enter team name and join — one shared device per team; no per-individual identity (F-CL-2)
- Receive eager push from Host on join; cache slide content + resources for the session — the push carries the stripped package (no
question.answerKeys; per Question Model — answer-key custody), so every mark comes back viascoreUpdate(F-CL-3) - Late-join progress UI + jump-to-current-slide on completion (F-CL-3)
- Resolve every object type the package declares; report a fatal mismatch to the host on missing or version-incompatible types (F-CL-4)
- Render the current slide's Client canvas with its responsive layout — Control anchors / containers adapting across phone, tablet, portrait, and landscape (F-CL-5)
core.multiple-choiceinput element: tap input, submit answer — marked Host-side, no local validation (F-CL-6); the remaining MVP question-type inputs land via the MVP object-type cohortcore.free-textinput element: text entry, submit answer — marked Host-side (F-CL-6)- Render Host's authoritative timer state (a view of
question.timing— the timer element carries no duration of its own); lock input on Host "lock" message (F-CL-11) - Render the question type's built-in answer presentation on the Host's
answerReveal { slideId, presentation }message (per Question Model — marking, reveal, results) - Show team score and standings (F-CL-8)
- Reconnect after a disconnect (F-CL-9)
Team identity at join — photo + jingle Alpha client0 / 3
Pairs with the Host-side rendering work in host.md. Per Tech Stack — camera capture, CameraServer maturity on Android is unproven — the capture spike runs first in this chunk.
- Early spike:
CameraServerframe capture on a real Android device and a real iOS device; if frames are unavailable or unreliable, scope the thin native capture plugin instead (risk tracked in Open Questions) - Team photo at join (F-CL-14): capture frame via
CameraServer→ centre-crop to square → bilinear downscale to 256 × 256 → JPEG-encode q75 (re-encode q60 over the 96 KB hard cap) → upload in the join message; premade-avatar fallback when camera permission is denied or unavailable - Buzzer-jingle pick at join (F-CL-15): list the quiz's bundled jingles, tap-to-preview, transmit the choice in the join message; picker hidden when the quiz bundles none
Remote0 / 16
Tauri 2 + Angular Remote scaffold MVP remote0 / 5
- Extract the Designer's shared Angular infrastructure from
Quiz.Designer/src/lib/into a pnpm workspace package at the repo root (design tokens + Studio chrome components, typedMessageBusseams where applicable, WebSocket client,command-errordiscriminated-union handling) per Repository Layout; Designer imports switch to the package in the same change — single source, no copies. - Create
Quiz.Remote/as a Tauri 2 + Angular workspace consuming the shared package; Tauri mobile targets configured for iOS + Android; portrait-locked orientation; per-window capability files default-deny following the Designer's pattern. - Wire the TypeScript schema codegen output (
pnpm schema:gen) into the Remote workspace — generated control-family envelope types +ajvvalidators, git-ignored. - Jest + Playwright + a11y test rigs mirrored from the Designer (same gates, same helpers).
- Smoke test:
tauri dev(desktop dev window) andtauri android dev/tauri ios devboot to the app shell without errors.
Remote discovery + pairing UI MVP remote0 / 2
- Host discovery via the
mdns-sdRust crate behind a Tauri command, surfaced through an Angular discovery service as a signal of visible Hosts (F-RE-1); iOS multicast-entitlement caveat tracked in Open Questions, manual-IP entry as fallback. - Pairing screen — pick a discovered Host, then pair by typing the Host's short numeric code or scanning its QR via the device camera (F-RE-2).
Remote control surface MVP remote0 / 7
- Control-family WebSocket client — browser
WebSocketAPI viarxjs/webSocket, typed envelopes validated against the generated TS schema types; reconnects with the same Remote identity after a Wi-Fi blip (F-RE-3, F-RE-8). - Frame-stream mirror viewer — renders the downscaled Host-canvas frames pushed over the control family; the Remote never re-implements slide rendering (F-RE-4).
- Host-notes pane — the current slide's host-notes (markdown rendered) on Studio chrome (F-RE-5).
- Session HUD — scores, authoritative timer remaining (a view of
question.timingticked by the Host), slide index, driven by the control-family live-state messages (F-RE-6). - Navigation controls — advance / go-back command dispatch (F-RE-7).
- Reveal-answer control — dispatches the quizmaster's answer-reveal cue; the Host responds by emitting
answerRevealand rendering the question type's built-in answer presentation (per Question Model — marking, reveal, results). - Buzzer adjudication controls — Correct / Incorrect buttons dispatching
adjudicate { slideId, teamId, verdict }forcore.buzzerquestions; the MVP's only manual-marking surface, mirrored on the Host operator window.
Remote mobile builds MVP remote0 / 2
- iOS + Android artefacts produced by
.azure-pipelines/quiz-remote.yml(pipeline definition tracked in project.md — CI / CD); signing wired through the Mac mini agent for iOS. - Manual validation on a real iPhone and a real Android phone: discovery, pairing, mirror, host-notes, HUD, navigation.