Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 63 additions & 44 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,25 @@ jobs:
node-version: '22'
cache: 'npm'
- run: npm ci
- run: npm run test:unit
- run: npm run test:integration
- name: Upload coverage
# Coverage runs enforce the thresholds in vitest.config.ts / vitest.integration.config.ts.
# The thresholds are a ratchet: set at the measured level, raise them as tests are added.
- run: npm run test:coverage:unit
- name: Upload unit coverage
uses: codecov/codecov-action@v4
if: always()
with:
files: ./coverage/lcov.info
flags: unit
- run: npm run test:coverage:integration
- name: Upload integration coverage
uses: codecov/codecov-action@v4
if: always()
with:
files: ./coverage/lcov.info
flags: integration
# Structural gap check: fails if the number of source files without a test grows.
- name: Test-gap ratchet
run: npm run test:gaps -- --max-gaps 25

build:
name: Build & Bundle Size Check
Expand All @@ -57,53 +69,60 @@ jobs:
name: dist
path: dist/

# e2e:
# name: E2E Tests
# runs-on: ubuntu-latest
# needs: build
# steps:
# - uses: actions/checkout@v4
# - uses: actions/setup-node@v4
# with:
# node-version: '22'
# cache: 'npm'
# - run: npm ci
# - name: Download build artifacts
# uses: actions/download-artifact@v4
# with:
# name: dist
# path: dist/
# - name: Install Playwright browsers
# run: npx playwright install chromium --with-deps
# - run: npm run test:e2e
# - name: Upload test results
# uses: actions/upload-artifact@v4
# if: always()
# with:
# name: playwright-report
# path: playwright-report/
# E2E tests temporarily disabled - will be re-enabled in future PR

chromatic:
name: Visual Regression Tests
e2e:
name: E2E Tests (${{ matrix.mode }})
runs-on: ubuntu-latest
# Only run on push events to main branch
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
strategy:
fail-fast: false
matrix:
mode: [default, encrypted]
env:
# Playwright's webServer starts Storybook; CI=true stops it opening a browser.
CI: 'true'
PLAYWRIGHT_BROWSERS_PATH: ~/.cache/ms-playwright
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- name: Run Chromatic
uses: chromaui/action@latest
# Don't fail if CHROMATIC_PROJECT_TOKEN is not configured
continue-on-error: true
- name: Install Playwright Chromium
run: npx playwright install chromium --with-deps
# The E2E pages under dita-demo/ load the bundle from the copied resources, so build
# and copy the bundle for the mode under test (the copy is not committed back).
- name: Build bundle (default)
if: matrix.mode == 'default'
run: npm run build:dita
- name: Build bundle (encrypted)
if: matrix.mode == 'encrypted'
run: npm run build:dita:encrypted
- name: Run Playwright (default)
if: matrix.mode == 'default'
run: npm run test:e2e
- name: Run Playwright (encrypted)
if: matrix.mode == 'encrypted'
run: npm run test:e2e:encrypted
- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: always()
with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
exitZeroOnChanges: true
# Don't fail the build on Chromatic errors during Phase 0
onlyChanged: true
name: playwright-report-${{ matrix.mode }}
path: playwright-report/
retention-days: 7

storybook:
name: Storybook Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci
# Every story must compile. A story importing a deleted module used to break
# this silently (it was hidden behind continue-on-error on the old Chromatic
# job), which also took down the Storybook half of PR previews. Running it on
# pull requests catches that before merge.
- run: npm run build-storybook
1 change: 0 additions & 1 deletion .storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const config = {
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@chromatic-com/storybook'
],
framework: {
name: '@storybook/web-components-vite',
Expand Down
33 changes: 20 additions & 13 deletions ARCHITECTURE_FLOWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,19 @@ graph LR

| Event | Producer | Consumers | Payload | Purpose |
|-------|----------|-----------|---------|---------|
| `qd:login` | qd-login | index.ts, home-badges, storage-monitor | SessionData | User authentication |
| `qd:logout` | qd-status | index.ts, home-badges, storage-monitor | {serviceId} | Session termination |
| `qd:answer-saved` | quiz-table enhancer | index.ts, storage-monitor | {pageId, answer} | Quiz progress |
| `qd:state-changed` | index.ts | home-badges, storage-monitor | {pageId, state} | Page completion state |
| `qd:instructor-unlock` | qd-instructor | storage-monitor, quiz tables | {timestamp} | Reveal answers |
| `qd:instructor-login` | qd-login, qd-status | index.ts | {password} | Instructor auth |
| `qd:data-cleared` | qd-instructor | storage-monitor | {timestamp} | Data erasure |
| `qd:login` | qd-login, qd-instructor-login, SessionService | event-coordinator, bootstrap, qd-status, qd-instructor, quiz-table | {serviceId, name, release, role} | Student or instructor authentication (instructor login carries `role: 'instructor'`) |
| `qd:logout` | qd-status, qd-instructor, SessionService | event-coordinator, qd-status, qd-login, qd-instructor, home-badges | {serviceId} | Session termination |
| `qd:answer-saved` | quiz-answer-persistence | event-coordinator (→ `qd:cache-update`) | {pageId, answer} | Quiz progress |
| `qd:state-changed` | quiz-answer-persistence | event-coordinator (→ `qd:badge-update`), qd-status, home-badges | {pageId, state} | Page completion state |
| `qd:analysis-saved` | analysis-persistence | (none) | {pageId, tableId} | Analysis cell saved |
| `qd:instructor-unlock` / `-lock` | qd-instructor-unlock, SessionService | event-coordinator (log only) | {} | Toolbar unlock / lock |
| `qd:instructor-show-answers` / `-hide-answers` | qd-instructor | quiz-table, analysis-table, instructor-answer-reveal | {} | Per-student overlay toggle |
| `qd:data-cleared` | qd-instructor-manage | event-coordinator (→ `qd:cache-clear`) | {timestamp} | Data erasure |
| `qd:cache-rebuild` / `-update` / `-clear`, `qd:badge-update` | event-coordinator | qd-status, home-badges | varies | Cache and badge coordination |

All of these are dispatched on `document` (not `window`); see `src/init/event-coordinator.ts` and
`Technical_Design.md` §19 for the full list including component‑local events. There is no
`qd:instructor-login` event: instructor login emits `qd:login` with `role: 'instructor'`.

---

Expand Down Expand Up @@ -113,12 +119,13 @@ sequenceDiagram
SessionService->>Storage: Save to sessionStorage
index.ts->>DOM: Rebuild cache from IndexedDB
else Path 2: Instructor Login
User->>qd-status: Click instructor button
qd-status->>EventBus: Dispatch qd:instructor-login
EventBus->>index.ts: Show instructor modal
User->>qd-instructor: Enter password
qd-instructor->>EventBus: Dispatch qd:instructor-unlock
EventBus->>DOM: Reveal correct answers
User->>qd-login: Click "Instructor" button
qd-login->>qd-instructor-login: Open password modal
User->>qd-instructor-login: Enter password (rate limited)
qd-instructor-login->>instructor-auth: verifyInstructorPassword (#qd-instructor-hash)
qd-instructor-login->>SessionService: createSession('INSTRUCTOR') + qd/instructor flag
qd-instructor-login->>EventBus: Dispatch qd:login (role: instructor)
EventBus->>bootstrap: revealInstructorAnswers for each quiz table
else Path 3: Session Resume
User->>DOM: Return to page
DOM->>SessionService: Check sessionStorage
Expand Down
19 changes: 10 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

BrowserTest (internally "Sonar Quiz System") is an offline-first interactive quiz and analysis platform that progressively enhances DITA-published HTML training materials. The system operates entirely from `file://` URLs with no network dependencies, targeting air-gapped training environments.

**Current Phase**: Phase 1 Complete (Foundation Layer) → Starting Phase 2 (Components & Integration)
**Current Phase**: All eight delivery phases and specs 001–012 are complete (June 2026). The project was dormant until the September 2026 consolidation; see `docs/PROJECT_STATE.md` for the audit and `docs/MAINTAINERS.md` for the maintainer guide.

## Core Architecture

Expand All @@ -27,7 +27,7 @@ User Input → DOM Handler → Service Layer → Storage Adapter
**Storage Strategy**:
- **IndexedDB**: Primary persistence with composite keys `qd/{release}/u{serviceId}`
- Database name: `BrowserTest` (defined in `src/services/storage/indexeddb.ts`)
- Object stores: `students`, `backups`
- Object stores: `students`, `backups`, `auditLog` (DB version 3, see `src/services/storage/idb-connection.ts`)
- **sessionStorage**: Active session + R/A/G cache (expires 30 min)
- **No network**: All data remains local, no telemetry/CDN/remote config

Expand Down Expand Up @@ -76,7 +76,7 @@ Consolidated control center with:
- For analysis tables: Shows student entries in comparison format
- **Button**: "View All Scores" → Opens `<qd-scores-modal>`
- **Button**: "Export to CSV" → Downloads detailed answer data
- **Button**: "Erase All Data" → Clears all storage (with confirmation dialog)
- **Button**: "Erase All Data" → Clears IndexedDB (students, backups, audit log) and all `qd/*` sessionStorage keys (typed confirmation required)
- **Button**: "Logout"

#### **Scores Modal** (`<qd-scores-modal>`)
Expand Down Expand Up @@ -116,7 +116,6 @@ npm test # Run all tests
npm run test:unit # Vitest unit tests
npm run test:integration # DOM upgrade integration tests
npm run test:e2e # Playwright E2E tests (auto-starts/stops Storybook)
npm run chromatic # Visual regression tests

# Coverage & Gap Analysis
npm run test:coverage # Unit tests with v8 coverage report
Expand Down Expand Up @@ -204,7 +203,7 @@ python3 -m http.server 8000
# Visit: http://localhost:8000/demo/quiz-index.html
```

All demo files load the built bundle from `dist/sonar-quiz.iife.js` with configuration provided via hidden `<span>` elements. Debug mode is controlled by `DEBUG_MODE` constant in `src/index.ts`. See **demo/README.md** for:
All demo files load the built bundle from `dist/sonar-quiz.iife.js` with configuration provided via hidden `<span>` elements. Debug logging is off by default and can be switched on at runtime with `setDebugMode(true)` from `src/utils/logger.ts` (exported on `window.SonarQuiz`). See **demo/README.md** for:
- Detailed test scenarios (login, quiz interaction, analysis tables, session management)
- Browser DevTools inspection tips (IndexedDB, sessionStorage, custom events)
- Configuration examples using hidden span elements
Expand Down Expand Up @@ -289,7 +288,7 @@ npm run build
- `#qd-title-selector`: CSS selector for publication title/Release ID (default: `.wh_publication_title .title`)
- `#qd-db-name`: IndexedDB database name (**REQUIRED** - no default, throws error if missing/empty)
- `#qd-instructor-hash`: SHA-256 hash of instructor password (optional)
- Debug mode controlled by `DEBUG_MODE` constant in `src/index.ts` (single toggle point)
- Debug logging controlled by `setDebugMode()` in `src/utils/logger.ts` (off by default; no `data-qd-debug` attribute is read)
- No setup, no manual config, no network dependencies

**Example HTML Structure** (injected by DITA/Oxygen transform):
Expand Down Expand Up @@ -435,8 +434,8 @@ Before committing any code changes, ALL of the following MUST pass:

### Phase Exit Gates
- **Phase 0**: Contracts published, Storybook renders, CI green
- **Phase 1**: Chromatic interactions pass, parsing unit tests
- **Phase 2**: Visual baselines stable, cell mapping tests
- **Phase 1**: Storybook interactions pass, parsing unit tests
- **Phase 2**: Storybook renders all components, cell mapping tests
- **Phase 3**: A11y checks pass, event emission verified
- **Phase 4**: Session switch tests, expiry unit tests
- **Phase 5**: E2E file:// saves/reloads, CSV validation
Expand Down Expand Up @@ -493,7 +492,9 @@ Enable via `data-qd-debug` attribute on quiz/analysis tables:
- **Technical_Design.md**: Architecture, packaging, integration patterns
- **ARCHITECTURE_FLOWS.md**: Event flows, login processes, DOM patterns, service interactions
- **Contracts.md**: Frozen types and interfaces
- **Delivery_Plan.md**: 8-phase plan with exit gates
- **docs/MAINTAINERS.md**: Maintainer guide — setup, repo map, where the truth lives, known issues
- **docs/SECURITY.md**: What the project actually does for security, and its limits
- **docs/history/**: Archived design reviews and proposals (historical, not current)
- **demo/README.md**: Demo HTML files, testing workflows, troubleshooting guide
- **specs/001-sonar-quiz-system/**: Feature spec, plan, data model, contracts

Expand Down
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A browser-based quiz tool, capable of storing scores across a range of players a

BrowserTest is an interactive quiz and analysis platform designed for offline use. It progressively enhances HTML documents with quiz functionality, student progress tracking, and instructor review capabilities—all without requiring a network connection or server backend.

**Status:** In active development
**Status:** Feature-complete for the original eight-phase plan (June 2026); consolidated for restart in September 2026. See [docs/PROJECT_STATE.md](./docs/PROJECT_STATE.md) for the audit and [docs/MAINTAINERS.md](./docs/MAINTAINERS.md) for the maintainer guide.

## Key Features

Expand All @@ -20,7 +20,7 @@ BrowserTest is an interactive quiz and analysis platform designed for offline us
- **Framework:** Lit 3 (Web Components)
- **Build:** Vite + TypeScript
- **Storage:** IndexedDB (primary), sessionStorage (session cache)
- **Testing:** Vitest (unit), Playwright (E2E), Storybook + Chromatic (visual regression)
- **Testing:** Vitest (unit + integration), Playwright (E2E), Storybook (component development)
- **Target:** Chromium ≥96, Firefox ≥102, offline-capable

## Prerequisites
Expand Down Expand Up @@ -101,10 +101,10 @@ npm run test:e2e:debug # Debug mode with Playwright Inspector

**No manual Storybook management required** - just run `npm run test:e2e`.

### Visual Regression Tests
### Storybook
```bash
npm run storybook # Start Storybook for local development
npm run chromatic # Run visual regression tests (requires API token)
npm run build-storybook # Verify every story compiles (also a CI gate)
```

### All Tests
Expand Down Expand Up @@ -143,14 +143,16 @@ See [docs/RELEASE.md](./docs/RELEASE.md) for complete release instructions.
| [Technical_Design.md](./Technical_Design.md) | Architecture, technology decisions, packaging, integration, and acceptance criteria |
| [ARCHITECTURE_FLOWS.md](./ARCHITECTURE_FLOWS.md) | Event flows, login processes, DOM patterns, and service interactions |
| [Contracts.md](./Contracts.md) | Core types, interfaces, events, constants, and validation rules (frozen API) |
| [Delivery_Plan.md](./Delivery_Plan.md) | Phase-by-phase development plan with exit gates and testing strategy |
| [Storybook_Plan.md](./Storybook_Plan.md) | Component stories, controls, interaction tests, and visual regression policy |
| [docs/MAINTAINERS.md](./docs/MAINTAINERS.md) | Maintainer guide: setup, repo map, where the truth lives, known issues |
| [docs/PROJECT_STATE.md](./docs/PROJECT_STATE.md) | September 2026 consolidation audit and restart backlog |
| [docs/SECURITY.md](./docs/SECURITY.md) | What the project does for security, and its limits |
| [docs/README.md](./docs/README.md) | Index of all documentation, including archived material |

## Development Phases

The project follows an 8-phase delivery plan:

- **Phase 0:** Bootstrap + Contracts (toolchain, frozen interfaces) ← *Current phase*
- **Phase 0:** Bootstrap + Contracts (toolchain, frozen interfaces)
- **Phase 1:** Quiz Core (interactive quizzes, no login/IDB)
- **Phase 2:** Analysis Workbook (editable tables)
- **Phase 3:** Instructor Unlock (supervisor mode)
Expand All @@ -160,11 +162,11 @@ The project follows an 8-phase delivery plan:
- **Phase 7:** Beta Deployment & Feedback
- **Phase 8:** Security/Enhancements (optional)

See [Delivery_Plan.md](./Delivery_Plan.md) for detailed phase descriptions and exit criteria.
All eight phases are complete. Later work (specs 001–012 under `specs/`) added security hardening, PIN authentication, help popups, storage obfuscation, release automation and a hot-spot refactor.

## Architecture

- **Runtime:** Single JavaScript bundle (IIFE ≤25 KB gzipped) that progressively enhances DITA-published HTML
- **Runtime:** Single JavaScript bundle (IIFE ≤40 KB gzipped, enforced in CI) that progressively enhances DITA-published HTML
- **Pattern:** DOM upgrades for tables + Lit 3 custom elements for UI overlays
- **Data flow:** User actions → sessionStorage cache → IndexedDB persistence
- **Isolation:** Shadow DOM for components, scoped CSS for upgraded tables
Expand Down
Loading
Loading