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

Overall
94/36925%
Project
20/2179%
Designer
74/8686%
Host
0/310%
Client
0/190%
Remote
0/160%
Project (cross-cutting)20 / 217
Foundation MVP project1 / 2
  • Repo initialized with the Quiz.Designer/ Tauri 2 + Angular 21 workspace and the schemas/ JSON Schema source-of-truth tree. On-disk layout in Repository Layout.
  • Pre-commit hooks for GDScript format + lint (gdformat / gdlint via gdtoolkit) covering Quiz.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.gd entry dispatch — loads apps/<app>/<app>_main.tscn from 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.cfghost-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). The preview-web preset exports single-threaded so the embed needs no COOP/COEP headers inside the Designer's WebView.
  • core/ skeleton — engine-free plain-RefCounted GDScript (no Node, 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 --headless driving the GdUnit4 CLI) over tests/ (unit tests for core/, scene tests for render/ + 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 Pipelines agent pool authorised for the project (default for any pipeline not pinned to MacMini).
  • 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.yml required, no direct pushes to main.

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 for quiz-designer.yml to embed.
  • .azure-pipelines/quiz-remote.ymlQuiz.Remote/ Tauri mobile builds: pnpm install + ng build + Jest + tauri android build and tauri ios build (Mac mini for iOS); signed artefacts published.
  • .azure-pipelines/quiz-schemas.yml — schema codegen (schemas/codegen/gen_gdscript.py → GDScript into Quiz.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 runs npm ci + ng build + ng test (Jest) + Playwright screenshot pass against ng serve + tauri build to produce the signed Windows installer; on the Mac mini runs the same npm ci + ng build + tauri build to 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 on main (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.yml runs and passes on every relevant pipeline.
  • PR-validation red path: open a PR with a deliberately failing test, confirm pr-validation.yml fails and blocks merge per the branch policy.
  • Mac mini cold start: clear the MacMini agent'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.yml publishes the web-export bundle and quiz-designer.yml consumes it; round-trip works on main.
  • release.yml dry 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 docs skill.
Shared schema MVP project7 / 9

Authored as JSON Schema under schemas/; codegen produces GDScript types + validators into Quiz.Stage/core/generated/ and TypeScript types + ajv validators into the Angular Designer / Remote. See Repository Layout — Cross-language contract. The codegen wiring itself is tracked under Schemas + codegen.

  • Quiz manifest, slide, canvas, and element schemas authored under schemas/package-format/ (per Quiz Package Format)
  • Round (slide-grouping) schema (title, scoring metadata, contiguous slide range)
  • Per-slide host-notes field in the slide schema (used by F-DE-19 and rendered by the Remote — both MVP)
  • Per-element reveal-trigger field in the element schema (on slide entry / on quizmaster trigger / after delay) — F-DE-20
  • Per-slide timing config: lock-on-time-up, late-submission rule, quizmaster-override-allowed (F-DE-10)
  • WebSocket message envelope schemas under schemas/live-play/ (typed; validated by both the TypeScript and GDScript consumers) with extension hooks for object-type-specific messages, distinct families for Designer transfer / Client live-play / Remote control
  • Schema version field on quiz packages, validation on load (F-X-2)
  • Object-type registry interface — GDScript side in Quiz.Stage/core/object_types/ (ObjectTypeRegistry + ObjectType base + capabilities, with version negotiation), TypeScript side in the Angular Designer (ObjectTypeRegistry service + DesignerEditor descriptor) — declaring type id, schema version, and the surface contracts referenced by the apps
  • Round-trip serialisation tests for every schema, asserted from both the GdUnit4 (GDScript) and Jest (TS) sides against shared fixtures under schemas/fixtures/
Object-type plugin contract MVP projectdesignerhostclient5 / 8
  • ObjectType contract in engine-free GDScript under Quiz.Stage/core/object_types/ (object_type.gd — id, schema version, capabilities, default_data/validate/migrate; object_type_data.gd base) — see Object-Type Architecture. Headless GdUnit4 coverage in tests/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] — the render/ 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_runtime duck-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 ObjectType contract — ObjectTypeCapabilities.is_stateful flags types that own live-session state; the Host crash-recovery snapshot path queries them (the capture_state/restore_state runtime 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.gd build() registers + seals the cohort (core.text now) and is called at boot by the host / client / preview _main scenes; Designer side seeds its ObjectTypeRegistry service 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.text element 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.json declares inputs, output paths, and naming conventions for both codegen targets (TypeScript + GDScript — outputs.gdscript.outputDir).
  • GDScript codegen target — schemas/codegen/gen_gdscript.py produces typed GDScript data classes (from_dict/to_dict/validate) for the package-format schemas into Quiz.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, and allOf/if-then conditionals are deferred to hand-written ObjectType.validate. Output is git-ignored; the gate (tools/gate.ps1 step 0) + CI regenerate before the Godot import. Round-trip + validation covered by tests/core/schema/schema_roundtrip_test.gd against the mirrored shared fixtures.
  • TypeScript codegen target — quicktype produces interfaces; ajv produces compiled validators; both land in Quiz.Designer/src/app/generated/. Output is git-ignored; npm run schema:gen is invoked by ng build and 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.yml publishes both code drops as artefacts.
Load-bearing prototype MVP projecthostclient0 / 5
  • In-process WebSocket server runs in the Host on Godot built-ins — TCPServer + WebSocketPeer server-side handshake on a dedicated autoload pumping _process, protocol interpretation in engine-free core/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/ (PacketPeerUDP multicast) 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-sd Rust 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 in core/scoring/). What remains per type is the render/ 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-choice end-to-end (Client tap-one-option input; Host auto-marks against answerKey.correctOptionId; built-in reveal highlights the correct option)
  • core.free-text end-to-end (Client text entry; Host auto-marks against answerKey.acceptedAnswers + matchMode; built-in reveal shows the canonical accepted answer)
  • core.first-letter end-to-end (Client single-pick A–Z grid, optional constrained subset; Host auto-marks against answerKey.correctLetter)
  • core.numeric end-to-end (Client numeric entry; Host auto-marks against answerKey.targetValue with proximity exact / closestAbsolute / closestRelative; built-in reveal shows the target value)
  • core.true-false end-to-end (Client ✓ / ✗ tap; Host auto-marks against answerKey.correctAnswer)
  • core.ranking end-to-end (Client drag-to-reorder; items text / image / both; Host auto-marks against answerKey.correctOrder with all-or-nothing or per-position credit)
  • core.eliminator end-to-end (options vanish per answerKey.eliminationSchedule or quizmaster cue; teams answer at any point, earlier correct answers score more via the speed bonus; quizmaster-cued final reveal)
  • core.buzzer end-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.timer end-to-end (display element, either canvas; a view of question.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.leaderboard end-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 .quiz file (manifest + slides) — SaveQuizCommand bundles AuthoringSession state into a QuizPackage; PersistenceService.saveAsync atomic-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 .quiz file from disk — MenuActions.openAsyncPlatformAdapter.pickFilePersistenceService.openAsyncReplaceActiveQuiz command. (F-DE-13)
  • Designer discovers Hosts on the local network via mDNS — Tauri mdns-sd Rust command surfaced through TransferService (F-DE-14)
  • Designer can push a saved .quiz file to a chosen Host over the WebSocket transport — uses the standard browser WebSocket API (via rxjs/webSocket) from the Angular process (F-DE-14)
  • Host accepts incoming .quiz transfers, 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.externalBin in tauri.conf.json so the Designer ships with the Host binary inside its app bundle.
  • TauriPlatformAdapter.spawnHost invokes 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 .quiz launches 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, JavaScriptBridge round-trip, multi-window via Tauri WebviewWindow API, 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-clip element (Host canvas) — paired with the core.free-text question 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.image element (display on either canvas)
  • core.video element (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 .quiz and 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/Remote mdns-sd browse) on iOS 16+, Android 12+, Windows 10/11, macOS 12+ over a typical pub-grade Wi-Fi router. iOS raw multicast requires the restricted com.apple.developer.networking.multicast entitlement — 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 (NWBrowser on iOS, NsdManager on 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-reveal end-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-pairs end-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-scramble end-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's answerKey with an optional multi-answer mode (multiple correct options must all be selected; partial credit configurable)
  • Additional question.scoring.speedBonus.curve values (easeOut, stepped) beyond the MVP linear
  • 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.jpg sampling: magenta #FF009F, electric-blue #16B2EB, deep-purple #961EEF, white #FFFFFF, near-black #0F0B1A
  • Mascot animation rig — rigged once under Quiz.Stage/assets/ with an AnimationPlayer pose 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 theme field (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-game element (F-CL-7)
  • Mini-game registry (id → mini-game module) resolved by the core.mini-game element at slide load
  • mini-game.internal-clock shipped 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-shoot shipped 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-modifier shipped as a built-in (post-answer flourish; spinning wheel with ± value segments; 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 the cloudflare-pages skill.
  • Wire each Designer Help-menu entry (Documentation, Keyboard shortcuts, Release notes, Report an issue, About Quiz Designer) to its live URL. Replace the interim docs.quiz.adamlewis.me placeholder 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 new dialog 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_versions tables (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.categories end-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-app then ng new into 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-tags and use the latest); pin Tauri to 2.x stable.
  • Configure src-tauri/tauri.conf.json with decorations: false, titleBarStyle: "Overlay" on macOS, windowEffects: { effects: ["mica"] } on Windows 11, an initial window size matching the mockups, and a Quiz.Designer app identifier.
  • Wire tauri-plugin-dialog, tauri-plugin-fs, tauri-plugin-process, Tauri 2's built-in menu API (src-tauri/src/menu.rs), and the mdns-sd Rust crate via Tauri commands under src-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 splash WebviewWindow first, loading the Angular SplashComponent. 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: drag for the drag region; setTrafficLightPosition for 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.

  • PlatformAdapter interface in src/app/platform/ declares file-dialog, filesystem, mDNS, subprocess-spawn, and OS-menu capabilities.
  • TauriPlatformAdapter implementation wires each capability to the matching Tauri command.
  • AuthoringSession Angular service — activeQuiz, selection, isDirty exposed as signals + observables; QuizChanged / SelectionChanged / DirtyChanged events.
  • CommandDispatcher Angular service — execute() + undo() pipeline; per-quiz undo / redo stacks. First two concrete commands — CreateNewQuiz and ReplaceActiveQuiz — validate the contract end-to-end.
  • PropertyDispatcher helper 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 with core.text initially), and the platform adapter.
Designer persistence + autosave MVP designer11 / 11
  • PersistenceService Angular service — .quiz save / load via TauriPlatformAdapter's fs commands + the zip Rust crate; recent-files index in the platform app-data folder.
  • Atomic write protocol for every disk write — delegated to tauri-plugin-fs's writeFile (plugin handles the safe-write semantics; no custom Rust command). Stranded .tmp GC 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 activeQuiz with isDirty = true.
  • Corruption detection on Open — when a .quiz fails 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 .tmp files, evicts past-cap backups, removes AutoSaves/<quizId>/ folders whose source has not been opened in 60 days, removes AutoSaves/<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 .quiz archive's resources/; 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
  • LibraryService Angular service — device-wide media catalog at platform app-data folder; SHA-256 content-hash de-dup (Web Crypto API in the renderer); LibraryAsset record.
  • File-picker capability on PlatformAdapter — Tauri impl wraps tauri-plugin-dialog.
  • macOS entitlements / Info.plist for file access — declare app-sandbox, network.client, network.server, files.user-selected.read-write.
  • Windows installer manifest opts the app out of Program Files placement constraints that would block library writes.
Designer object types (Designer side) MVP designer2 / 2
  • ObjectTypeRegistry Angular service — editor-surface registry on the Designer side of the plugin contract. First registered type is core.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 the PropertyDispatcher helper.
Designer transfer skeleton MVP designer1 / 1
  • TransferService Angular service — wraps the LAN-discovery + WebSocket push path. Skeleton lands as StubTransferService that throws on pushAsync / 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 keydown listener that routes Ctrl/Cmd-Z / Shift-Z / S / O / Delete / arrows to CommandDispatcher or component event outputs. Ignores keypresses inside inputs / contentEditable so typing into the inspector still works.
  • PowerPoint-style preview shortcut bindings: F5 / Shift+F5 / Ctrl+F5 are registered in ShortcutRegistry, 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 via DestroyRef.
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 PreferencesService slice keyed quiz.designer.layout.v1 (the service lands alongside the autosave-retention chunk).
  • Pane registryLayoutState enumerates the panes (slide-list, canvas, right-rail) and the dock zones (left / center / right / popped); a PaneHostComponent resolves a pane id to its component. The shell renders each zone from LayoutService.state so 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 quiz CommandDispatcher (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 via PreferencesService lands 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 the pop_out_pane command; closing the pop-out re-docks the pane to its previous slot via the pane:redock event the backend emits on WindowEvent::Destroyed. Web-shell binding to window.open lands with the BrowserPlatformAdapter (Web Designer Stretch).
  • Layout reset — the titlebar View → Reset Layout menu item calls LayoutService.reset, restoring the default slide-list / canvas / right-rail shape; 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 — CreateNewQuizCommand is wired to the slide-rail + New quiz button + the File menu. (F-DE-2)
  • New-quiz title / description / tags entry dialog so the user picks an identity instead of CreateNewQuizCommand hard-coding "Untitled". (F-DE-2)
  • Add, edit, reorder, delete slides — AddSlideCommand + DeleteSlideCommand + click-to-select + CDK drag-reorder via ReorderSlidesCommand are wired into SlideList. (F-DE-3)
  • Per-slide rename — inline-edit affordance on each slide-list row, dispatching SetSlideTitleCommand directly through CommandDispatcher (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 .quiz format — quiz-package-codec.ts (encodeQuiz / decodeQuiz) + PersistenceService write + 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") + a question node (prompt, answerKey, scoring, timing, inputBinding); scoring/timing move off the slide onto the question node; round/manifest restructure — manifest.slideOrder is the sole order authority, slide.roundId the sole membership authority, manifest.rounds[] metadata-only (no slideIds; slide.index deleted). Regenerate codegen; update the persistence codec (quiz-package-codec.ts) + validators. Pre-alpha: no compat shims — .quiz files 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 via question.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.ts drives both the rail badges and the PersistenceService save gate — QuizValidationError surfaces 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 (AddElementToCanvasCommand refuses 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.leaderboard display-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-tauri command 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 no docs/ui-mockups/designer/ counterpart to diff against yet — the mockups + visual pass land with the frontend-design polish 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-web preset) under Quiz.Designer/src/assets/preview/ (consumed from the quiz-stage.yml CI artefact at build time) and load it inline as a <canvas> via the Godot web loader.
  • PreviewBridge Angular service — pushes slide JSON to the embedded canvas through the export's JavaScriptBridge window-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.text slide, push state, observe re-render in the embedded canvas, log the rendered callback.
  • Reconnect strategy: if the canvas reports error or 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 dev on Windows and macOS — splash → main shell with embedded preview canvas visible and a preview ready event 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 by boot.gd on the preview feature tag.
  • apps/preview/preview_main.gd — registers JavaScriptBridge callbacks for the window-level messages (LoadSlide, UpdateProperty, SetSelection); raises ready / rendered / error back into the host page. Message dispatch lives below the JavaScriptBridge seam so it is GdUnit4-testable headless.
  • Canvas size driven by the host page (responsive <canvas>); pushed slide state binds onto the shared render/ object-type scenes.
  • Smoke-test: the web export loads in a plain HTML page, accepts a LoadSlide JS call, renders a placeholder slide, emits a rendered callback.

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 (DialogStack report-issue registration + /window/dialog/report-issue route, 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 in platform/attachment-codec.ts.
  • Submission backend: HTTPS webhook configured per install via the QUIZ_ISSUE_WEBHOOK env var, read only in the Rust shell. submit_issue_report command → services/issue_report.rs (validation + payload shaping + HttpPoster seam, ureq/rustls POST with 15 s timeout, 5 MiB attachment budget). Unconfigured → typed capability envelope; transport/HTTP failure → new network CommandError variant. No auth secrets exist (the URL is the only credential and never reaches the WebView).
  • Clipboard fallback: on capability/network failure ReportIssueService copies a markdown digest (summary/description/repro + attachment names) via navigator.clipboard and the form shows a role="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-issue item enabled (placeholder disabledWhen removed); 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 + active computed; CRUD (create / rename / remove / reorder / setActive / addColor with copy-on-write default cloning); cross-window invalidation via the new palette:changed bus 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.json via PlatformAdapter. Default palette is synthesised in memory, read-only, never written to disk.
  • Library UI: new library pane (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 new LayoutService.revealPane (forwarded over panels:layout-intent from floating tool windows); hydrate now adds registered-but-unsaved panes to the closed list so new panes stay reachable on stale persisted layouts.
  • Wire ColorPickerComponent to PaletteService — 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.buzzer moved to MVP as a question type; core.drawing moved to Stretch — neither belongs in this bullet anymore.)
  • core.image-revealdeferred 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 (a Quiz.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 current inputBinding.type == questionType invariant 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 by boot.gd on the host feature tag or --app=host.
  • apps/host/play/ slide-runner scene — advances through the quiz, instantiates each slide's Host canvas from the shared render/ object-type scenes, and dispatches element behaviours for the live session.
  • host-windows + host-macos export presets — landscape desktop window defaulting to 1920×1080 (TV / projector target), per-app product name / icons / bundle id, host feature 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 Window renders 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=host boots to the lobby with no script errors; GdUnit4 scene test pins the boot dispatch into host_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/.quiz read via Godot's built-in ZIPReader; 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 against question.answerKey, apply question.scoring (base points, linear speed bonus off the Host receive timestamp, tiebreak) and question.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 + WebSocketPeer handshake on a dedicated autoload pumping _process — and advertise on the local network via the core/discovery/ mDNS advertiser (F-HO-3). Server + discovery stand-up per project.md — Load-bearing prototype.
  • Load .quiz from local FS via file picker (in addition to Designer push), through core/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.answerKey stripped 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 answerSubmit messages (question-addressed: slideId is the question id, resolved through question.inputBinding; last-write-wins until lock, inputLocked protocol error after), auto-mark against the session answer table, apply core/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 from question.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 adjudicate message; incorrect excludes the team (optional configurable penalty) and re-opens buzzers for the rest; scoreUpdate.reason carries "adjudicated"
  • Host is authoritative on timer state, driven by question.timing (core.timer elements 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 adjudicate message; editing question.answerKey and 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 by boot.gd on the client feature tag or --app=client.
  • apps/client/play/ surface — renders the current slide's Client canvas from the shared render/ object-type scenes, dispatches input elements, shows the team score.
  • client-android + client-ios export 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=client boots to the join screen with no script errors; GdUnit4 scene test pins the boot dispatch into client_main.tscn.
Client team-play features MVP client0 / 12
  • Discover hosts via the core/discovery/ mDNS browser over PacketPeerUDP multicast (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 via scoreUpdate (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-choice input 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 cohort
  • core.free-text input 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: CameraServer frame 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, typed MessageBus seams where applicable, WebSocket client, command-error discriminated-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 + ajv validators, git-ignored.
  • Jest + Playwright + a11y test rigs mirrored from the Designer (same gates, same helpers).
  • Smoke test: tauri dev (desktop dev window) and tauri android dev / tauri ios dev boot to the app shell without errors.
Remote discovery + pairing UI MVP remote0 / 2
  • Host discovery via the mdns-sd Rust 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 WebSocket API via rxjs/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.timing ticked 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 answerReveal and 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 } for core.buzzer questions; 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.