A static, single-page web app that delivers a multi-challenge assessment loaded from JSON — five question types, free navigation, partial-credit scoring, progress persistence, and a results view, styled to DeepLearning.AI's brand.
See SPEC.md for the full specification and PLAN.md for
the milestone plan. This repo is being built milestone by milestone.
M0 — Scaffold, data layer & infra: complete. App shell + screen router, seeded PRNG/shuffle utilities, and the assessment data layer (load → validate → normalize → flatten, with stable item ids and a content fingerprint).
M1 — Instructions screen: complete. Full instructions screen (summary, data-derived type legend, how-it-works), session-seed ownership (mint-or-restore, fingerprint-scoped), a safe localStorage wrapper with in-memory fallback, and a GitHub Pages deploy workflow.
M2 — Quiz shell & navigation: complete. Keyed state-mutation API
(currentIndex/answers/resolvedOrderings, patched in place — no
full re-render), question panel (scenario banner, challenge/point meta,
prompt), Back/Next + a compact full-width horizontal navigator (grouped by
challenge, roving tabindex, status icons, jump-to), and a progress indicator.
M3 — Question types: core complete. Per-type module registry
(src/questions/*) with a uniform render/statusOf/score interface, mounted
through a lazy mount-once-and-cache widget lifecycle in the quiz screen
(answers rehydrate from app.answers on revisit). All five types are
interactive: single/multi-select (radios/checkboxes); grouping (group bins + an
"unplaced" chip pool, click-to-place); matching (two columns — items vs options
— click-to-match, one-to-one, auto-matches the final pair); and ordering (move
up/down). All click-to-select-then-place — no native drag, so it's keyboard- and
touch-operable. Per-type status (not-answered / in-progress / answered) and
partial-credit scoring are implemented and unit tested.
M4 — Scoring & results: complete. Submit appears on the last question and
opens a confirmation modal (warns about incomplete questions; focus-trapped,
Escape/backdrop to cancel). On confirm, scoring.js grades every question
(delegating to the per-type score), rolls up totals, percentage, and
per-challenge subtotals, and the results screen shows a score summary plus a
per-question review (your answer, the canonical correct answer, and outcome as
shape+label — ✓ correct / ◐ partial / ✕ incorrect / ○ not answered). Retake
clears state and returns to the start.
M5 — Persistence: complete. In-quiz state (answers, resolved orderings,
current index, submitted) auto-saves to localStorage on every
change, in a blob namespaced by the content fingerprint (seed in its own key).
On load, a session with real progress restores and the instructions screen
offers Resume / Start over; a submitted session reloads straight to results.
Retake / Start over clear storage. Falls back to in-memory with a notice when
localStorage is unavailable; stale blobs are discarded on
fingerprint/schemaVersion mismatch.
M6 — Polish, a11y, CI, deploy: complete. CI (.github/workflows/ci.yml)
runs the test suite on every push/PR. Accessibility pass: status/outcome signals
use shape + text label (not color alone), with in-progress now amber vs the
teal answered dot so they differ by color too; the submit modal uses a focus
trap + inert background; roving-tabindex navigator; two
aria-live regions; lang, skip link, visible focus, and
prefers-reduced-motion all in place; 44px primary touch targets; contrast
verified AA (point chip, status, outcome colors). The error screen has a Try
again retry (§10). CTA semantics hold (one coral primary per screen).
The header shows the official DeepLearning.AI logo (
dlai-logo.webp) at 28px tall, withalt="DeepLearning.AI".
Live: https://roberto-dlai.github.io/assessment-demo/ (auto-deploys from
main via .github/workflows/deploy-pages.yml; tests gated by CI).
data/assessments.json is a manifest listing the selectable assessments
({ id, title, description, file }). On load:
- with multiple assessments and no selection, a picker screen lists them;
?a=<id>deep-links straight to one (and the URL updates when you pick);- a single manifest entry (or no manifest) skips the picker and loads the default.
Each assessment keeps its own progress — the saved session is namespaced by
id + content-fingerprint, so distinct assessments never collide (even with
identical content) and a content change still invalidates stale saves. Add an
assessment by dropping its JSON in data/ and adding a manifest entry.
ES modules and fetch() are blocked under file://, so you must use a
local static server — don't open index.html directly.
# option A (Node)
npm run serve # runs `npx serve .`
# option B (Python)
python3 -m http.server 8000Then visit the printed URL (e.g. http://localhost:8000). The app boots to the instructions screen; load failures surface on an error screen (and the console).
Pure modules (PRNG, shuffle, data normalization) are covered by Node's built-in test runner — no dependencies:
npm test # node --testScoring, persistence, and per-type question behavior are all covered.
index.html # entry; loads fonts, theme.css, styles, src/main.js
theme.css # DeepLearning.AI brand tokens
styles/app.css # app layout/styles
data/
assessments.json # manifest of selectable assessments
spec-driven-development-assessment.json # example assessment(s)
src/
main.js # bootstrap + screen router
state.js # app state + in-quiz answer state (status via registry)
session.js # per-assessment seed + session blob (fingerprint-scoped)
scoring.js # grade(app): totals + per-challenge subtotals + outcomes
data/loadAssessment.js # load, validate, normalize, flatten, fingerprint
util/prng.js # mulberry32 seeded PRNG + helpers
util/shuffle.js # deterministic shuffles
util/storage.js # safe localStorage wrapper (in-memory fallback)
util/a11y.js # announce / focusHeading / createFocusTrap
questions/ # per-type modules: render / statusOf / score
registry.js # type -> module
single.js multi.js grouping.js matching.js ordering.js
screens/
dom.js # shared el()/mountScreen()/heading() helpers
picker.js modal.js instructions.js quiz.js results.js error.js
test/
util.test.js state.test.js persistence.test.js persist-session.test.js
questions.test.js scoring.test.js (53 tests)
All asset paths are relative so the app works from a GitHub Pages project
subpath (/<repo>/).
Static site — no backend. Published to GitHub Pages via GitHub Actions
(.github/workflows/deploy-pages.yml, serving the repo root); ci.yml runs the
test suite on every push/PR.