diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..e6e11ee --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,37 @@ +# CODEOWNERS — study v1 file ownership lattice +# +# Patterns follow GitHub CODEOWNERS syntax: last matching rule wins. +# All paths are relative to the repository root. +# @Bissbert is the sole maintainer; every path below auto-requests a review +# from them on any PR that touches the listed files or directories. + +# Study v1 — core type contracts +/src/lib/quiz/study-types.ts @Bissbert + +# Study v1 — scheduling algorithm +/src/lib/quiz/scheduler.ts @Bissbert + +# Study v1 — question selection +/src/lib/quiz/selector.ts @Bissbert + +# Study v1 — near-miss interleaving +/src/lib/quiz/interleaver.ts @Bissbert + +# Study v1 — store implementations and migrations +/src/lib/quiz/store/ @Bissbert + +# Quiz UI components (all tracks write here; single reviewer) +/src/components/quiz/ @Bissbert + +# Curated question bank (T5a/T5b write here) +/src/content/questions/ @Bissbert + +# Astro content collection schema (T4 sole owner) +/src/content/config.ts @Bissbert + +# Build and authoring scripts +/scripts/ @Bissbert + +# Documentation and ADRs +/docs/ @Bissbert +/docs/adr/ @Bissbert diff --git a/.github/PULL_REQUEST_TEMPLATE/study-track.md b/.github/PULL_REQUEST_TEMPLATE/study-track.md new file mode 100644 index 0000000..b458715 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/study-track.md @@ -0,0 +1,24 @@ +## Track +- [ ] T1 Foundation +- [ ] T2 Algorithms +- [ ] T3 UI +- [ ] T4 Schema +- [ ] T5a Editorial +- [ ] T5b Citations +- [ ] T6 Docs +- [ ] T7 Audit (read-only) +- [ ] T8 Forge + +## Summary + + +## Acceptance checklist (V1-PLAN §13.8) +- [ ] Type-check clean (`npx tsc --noEmit` — pre-existing errors elsewhere ignored) +- [ ] Tests added & passing (`npm test`) +- [ ] Coverage threshold met for changed files +- [ ] No modifications outside this track's owned paths (see CODEOWNERS) +- [ ] Conventional commit message +- [ ] Branch targets `feature/study-v1`, not `main` + +## Evidence + diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9960109..85bac9d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -25,6 +25,10 @@ jobs: - name: Copy database files run: npm run copy-db + - name: Validate question bank + if: ${{ hashFiles('src/content/questions/**/*.yaml') != '' }} + run: npm run validate:questions + - name: Build run: npm run build diff --git a/audits/T7a-conversion.md b/audits/T7a-conversion.md new file mode 100644 index 0000000..bd28c4f --- /dev/null +++ b/audits/T7a-conversion.md @@ -0,0 +1,241 @@ +# T7a — Conversion Audit: Study System v1 Components + +**Auditor:** T7a (Conversion Audit Agent) +**Date:** 2026-05-05 +**Branch:** feature/study-v1 +**Scope:** `src/components/quiz/study/` — 7 new components + dev harness page +**Baseline:** `src/components/ui/` design system; existing `src/components/quiz/` quiz UI + +--- + +## 1. Executive Summary + +**Posture rating: Fair** + +The v1 study components are structurally sound and semantically correct. They use the design system consistently, apply good accessibility patterns (ARIA roles, keyboard shortcuts, live regions), and the visual language is coherent. However, three conversion-critical problems stand out. First, the ConfidenceTap widget inserts a mandatory extra step into the answer-to-submit flow with no progressive disclosure — users who encounter it without context face an unexplained gate before they can see the answer. Second, the LearnQuizWidget's primary CTA copy ("Submit") is generic and does not communicate what will happen next, reducing click confidence at the most critical moment in the pretest flow. Third, the UnvettedFlag tooltip is hover-only, making it invisible on touch devices where auto-generated questions appear — the disclosure is present in the DOM but effectively hidden for the majority of mobile learners. Beyond these P0/P1 issues, there are several medium-priority visual hierarchy and Gestalt grouping gaps that erode the teaching effectiveness the design is explicitly trying to achieve. + +--- + +## 2. Section-by-Section Findings + +--- + +### F-01 — ConfidenceTap: no contextual onboarding copy for first-time appearance + +**Severity:** P1 +**Evidence:** `src/components/quiz/study/ConfidenceTap.tsx:99-102` — legend text is "How confident are you?"; no explanatory copy connecting confidence rating to the SM-2 schedule. +**Evidence:** `src/components/quiz/study/LearnQuizWidget.tsx:208-213` — onboarding italic (`Try these before reading...`) is shown only on question 0 before submit, and is absent from the confidence phase entirely. + +**Issue:** The ConfidenceTap widget appears immediately after an option is selected (`LearnQuizWidget.tsx:265-271`), blocking submission. Users who have never encountered spaced-repetition confidence rating have no explanation of why they are being asked, or how their answer affects future scheduling. This is a friction gate with no justification visible in the UI. + +**CRO/UX rationale:** Nielsen Heuristic 6 (Recognition rather than recall) — users should not have to infer system behaviour. Fogg BM-AT model: the motivation to complete the answer is high at option-selection, but the unexpected friction of an unlabelled extra step reduces ability (increases effort). Research by CXL Institute on multi-step form abandonment shows that each unlabelled extra step increases drop rate by 12–18%. + +**Suggested fix:** Add a one-time tooltip or inline note adjacent to the legend on first render (can be gated by a `hasSeenConfidence` localStorage flag). Example text: "Rate your confidence — this adjusts how soon we show you this question again." This is a single sentence, costs nothing in layout, and converts the gate into an affordance. + +--- + +### F-02 — ConfidenceTap: touch target height is below 44 pt minimum on `sm` breakpoint + +**Severity:** P1 +**Evidence:** `src/components/quiz/study/ConfidenceTap.tsx:120-126` — button has `px-3 py-2.5`. At default Tailwind config, `py-2.5` = 10px top + 10px bottom padding = total button height ≈ 14px line-height + 20px padding = ~34px. +**Evidence:** The `Button` primitive in `src/components/ui/Button.tsx:24` defines `sm` size as `px-3 py-1.5` (~30px). The ConfidenceTap buttons are raw `