Repository navigation
feat(study): v1.0 launch — SM-2, curated bank, study UI, audits - #14
Merged
Merged
Conversation
Publishes the type definitions and module signatures all parallel tracks
develop against:
- src/lib/quiz/study-types.ts — Confidence, ResponseRecord, ScheduleEntry,
StudySettings, StudyStore interface, storage key constants, capacity guard
- src/lib/quiz/scheduler.ts — qualityOf() (impl) + applySM2() (T2 stub)
- src/lib/quiz/selector.ts — selectQuestionsV2() (T2 stub)
- src/lib/quiz/interleaver.ts — interleaveNearMisses() (T2 stub)
- src/lib/quiz/store/{index,local}.ts — LocalStudyStore class shell (T1 stub)
- src/content/config.ts — questionsCollection Zod schema (full)
- src/lib/quiz/index.ts — re-exports
Stubs throw with explicit "deferred to track Tn" messages so missing wiring
is loud rather than silent. Tracks T1 (foundation), T2 (algorithms),
T3 (UI), T4 (schema/tooling), T5 (editorial), T6 (docs) all branch from
this commit.
Refs V1-PLAN.md §3, §4.1, §5, §13.5.
Implements V1-PLAN §3.6 (StudyStore interface) and §A.2 (LocalStudyStore) with full localStorage persistence. Adds v0→v1 migration preserving existing gemmology-quiz-progress data. Wires studyStore.appendResponse and progressTracker.updateProgress into useQuiz and useExam. - LocalStudyStore: all StudyStore methods backed by localStorage, 10 000-record RESPONSE_LOG_CAP trim guard, import/merge strategy (responses append-dedupe, schedule upsert, progress overwrite-if-newer, settings always overwrite) - migrations.ts: idempotent v0→v1 migration, version sentinel at gemmology-study-version, lifts gemmology-quiz-progress into versioned envelope - useQuiz.submitAnswer: logs ResponseRecord (confidence defaults 'fairly-sure' pending T3 ConfidenceTap), updateProgress on submitQuiz - useExam.submitExam: logs one ResponseRecord per answered question, supports per-question confidenceMap (defaults 'fairly-sure' pending T3 ConfidenceTap) - Adds Vitest + @vitest/coverage-v8 (devDeps); 50 tests, 87 %+ coverage on store/**, 100 % function coverage - Fixes Quiz.tsx onClick type error introduced by optional-Confidence signature
Adds the complete T4 deliverables for the study system v1: - scripts/_questions-schema.mjs: standalone Zod mirror of the questionsCollection schema from src/content/config.ts, usable without the Astro runtime - scripts/new-question.mjs: idempotent stub generator (--category, --slug, --type) - scripts/validate-questions.mjs: YAML validator with per-category coverage summary - scripts/questions-coverage.mjs: markdown coverage table - scripts/generate-question-schema.mjs: emits schemas/question.json via zod-to-json-schema - scripts/validate-questions.test.mjs: drift-detection unit tests (node --test) - schemas/question.json: JSON Schema for IDE YAML autocomplete - src/content/questions/_example/example-mcq.yaml: annotated curator reference template - package.json: adds question:new, validate:questions, questions:coverage, schema:questions scripts - .github/workflows/deploy.yml: adds "Validate question bank" step (conditional on YAML presence)
…interleaver (T2) Full SM-2 algorithm in applySM2: lapse resets repetitions/interval, recall grows interval (1→6→round(prev*EF)), EF updated unconditionally and clamped to ≥1.3. selectQuestionsV2: due-first (most-overdue first), fresh-fill per category budget, stale-fallback (excludes un-budgeted due items), round-robin category interleaving. interleaveNearMisses: greedy deterministic reorder enforcing minSpacing between confusion pairs, gracefully accepts violations when constraint is unsatisfiable. Adds vitest + @vitest/coverage-v8 as dev deps; vitest.config.ts minimal setup.
…% coverage scheduler.test.ts: qualityOf mapping, lapse invariants (reset rep/interval, lapses++), recall invariants (rep/interval progression), EF clamping to ≥1.3, hand-rolled property test asserting non-decreasing intervals on any correct-answer streak, concrete q=5 example verifying 1→6→~15→~38 progression per V1-PLAN §5.1. selector.test.ts: in-memory FakeStore; covers empty store, due-first ordering, reviewMixRatio ceiling, per-category budgets, stale-fallback, round-robin interleaving, edge cases (orphan due IDs, null schedule in stale path, categories outside budgets). interleaver.test.ts: spacing enforcement, determinism, graceful violation fallback, impossible constraints, 20-item multi-pair scenario. Coverage: 90.62% branches, 100% functions/lines across all three modules.
Add ConfidenceTap, RationalePanel, ScheduleBadge, UnvettedFlag, LearnQuizWidget, StudySettingsPanel, ExportImportPanel to src/components/quiz/study/. Each component is typed against study-types.ts contracts, uses only existing ui/ primitives (Button, Card, Badge, IconBox), and ships a full RTL test suite. Dev harness at src/pages/_dev/study-components.astro (DEV-gated 404 in production). Vitest + jsdom test infrastructure added; 89 tests pass. Design-token gaps documented in docs/design-needs.md.
- docs/study-system.md — system overview with three Mermaid diagrams (data flow, SM-2 state machine, component tree) - docs/authoring-questions.md — curator-facing 5-step workflow guide - docs/adr/0001-sm2-vs-fsrs.md — architecture decision record for the SM-2 → FSRS upgrade path Refs V1-PLAN.md §1, §3, §5, §6, §10, §13.
# Conflicts: # vitest.config.ts
# Conflicts: # package-lock.json # vitest.config.ts
… scaffolder + CI # Conflicts: # package.json
… checklist - .github/CODEOWNERS: auto-request @Bissbert on PRs touching study-v1 paths (study-types, scheduler, selector, interleaver, store/, quiz components, questions collection, content config, scripts, and docs) - .github/PULL_REQUEST_TEMPLATE/study-track.md: opt-in template (via ?template=study-track.md) with per-track checkboxes and the V1-PLAN §13.8 acceptance checklist - docs/release-checklist-study-v1.md: ordered pre-flight steps for the feature/study-v1 → main release PR, covering integration green, §12 acceptance criteria, bundle-size gate (<=+25 KB gz), T7 audit sign-off, tagging, IndexNow verification, Cloudflare live check, and worktree cleanup
…ter patterns Covers all seven Sturman shadow-edge patterns (I–VII) including isotropic, uniaxial (Patterns I–III, spot method), and biaxial (Patterns IV–VII) scenarios, with optic-sign determination, above-range readings, and paired ruby/spinel discrimination. Includes concept-tags controlled vocabulary (docs/concept-tags.md).
… and cleavage Covers all seven crystal systems, point groups (3̄m corundum, 32 quartz, 3m tourmaline), cleavage vs parting distinction in corundum, Miller index four-index notation for trigonal/hexagonal, and a five-gem optic-character sorting exercise.
…near-miss discrimination Covers near-miss property matching for fluorite vs topaz, diamond vs quartz, jadeite vs peridot, spinel calibration values, orange tourmaline vs peridot vs spessartine, blue spinel vs blue sapphire, green beryl vs tourmaline, kyanite directional hardness, nephrite vs jadeite, and moonstone adularescence discrimination.
…ination clusters Red stone cluster (ruby/spinel/garnet/red-glass/synthetic-ruby): corundum-vs-spinel-001 through corundum-vs-spinel-003, red-stone-garnet-001, red-stone-glass-001. Green stone cluster (emerald/chrome-tourmaline/green-glass/demantoid/peridot): green-stone-001 through green-stone-005. Cross-referencing confusionPairs throughout.
…ognition Silk dissolution and treatment detection (silk-001, silk-002), natural fingerprint vs flux fingerprint (fingerprint-001), fingerprint vs fissure discrimination (fissure-001), three-phase inclusions and Colombian origin (three-phase-001), growth tubes in tourmaline (growth-tube-001), zircon halos and heat treatment (zircon-halo-001), negative crystals in spinel (negative-crystal-001), two-phase inclusions and synthetic discrimination (two-phase-001), demantoid horsetail inclusions (horsetail-001).
Security (T7b): - Add isResponseRecord type guard in importAll; skip invalid records (T7b-01) - Wrap _write in try/catch with QuotaExceededError handling and trim-then-retry for the response store; introduce StorageFullError (T7b-02) - Migration runner now no-ops when stored version >= CURRENT_VERSION, preventing downgrade clobber from v2-aware tabs (T7b-05) - clearQuizStorage now removes all four v1 study keys plus migration version stamp (T7b-07) SEO/UX (T7c): - noindex /quiz while LockGate gates it (F-01) - Wire LearnQuizWidget into /learn/[slug] above article body when curated questions reference the article (F-03) - New /study/review and /study/settings routes wrapping existing study components in proper LockGate-protected page shells (F-06) - Remove duplicate Sitemap directive that pointed at llms.txt (F-10) Audit deliverables also added under audits/ for transparency. Tests: 210/210 pass. Build: 912 pages.
…itations Add [ref:id] inline markers at every specific factual claim across all 50 question YAML files (fundamentals, equipment, species, identification). Replace or remove three unverifiable citations from T5a's original draft (Schwarzenbach 2003, Pignatelli 2024, Sturman 1987 journal ref); the Sturman pattern classification is re-anchored to Anderson (1990) Gem Testing 10th ed. All 50 files validate at 50/50 with zero unvetted and zero missing references.
|
🚀 Preview deployed to: https://e1b83c9b.gemmology-dev.pages.dev |
|
🚀 Preview deployed to: https://bc4f8042.gemmology-dev.pages.dev |
Bissbert
added a commit
that referenced
this pull request
May 11, 2026
* feat(study): contracts for v1 study system (parallel-team scaffold)
Publishes the type definitions and module signatures all parallel tracks
develop against:
- src/lib/quiz/study-types.ts — Confidence, ResponseRecord, ScheduleEntry,
StudySettings, StudyStore interface, storage key constants, capacity guard
- src/lib/quiz/scheduler.ts — qualityOf() (impl) + applySM2() (T2 stub)
- src/lib/quiz/selector.ts — selectQuestionsV2() (T2 stub)
- src/lib/quiz/interleaver.ts — interleaveNearMisses() (T2 stub)
- src/lib/quiz/store/{index,local}.ts — LocalStudyStore class shell (T1 stub)
- src/content/config.ts — questionsCollection Zod schema (full)
- src/lib/quiz/index.ts — re-exports
Stubs throw with explicit "deferred to track Tn" messages so missing wiring
is loud rather than silent. Tracks T1 (foundation), T2 (algorithms),
T3 (UI), T4 (schema/tooling), T5 (editorial), T6 (docs) all branch from
this commit.
Refs V1-PLAN.md §3, §4.1, §5, §13.5.
* feat(study): foundation — LocalStudyStore, migrations, hook wiring (T1)
Implements V1-PLAN §3.6 (StudyStore interface) and §A.2 (LocalStudyStore)
with full localStorage persistence. Adds v0→v1 migration preserving existing
gemmology-quiz-progress data. Wires studyStore.appendResponse and
progressTracker.updateProgress into useQuiz and useExam.
- LocalStudyStore: all StudyStore methods backed by localStorage, 10 000-record
RESPONSE_LOG_CAP trim guard, import/merge strategy (responses append-dedupe,
schedule upsert, progress overwrite-if-newer, settings always overwrite)
- migrations.ts: idempotent v0→v1 migration, version sentinel at
gemmology-study-version, lifts gemmology-quiz-progress into versioned envelope
- useQuiz.submitAnswer: logs ResponseRecord (confidence defaults 'fairly-sure'
pending T3 ConfidenceTap), updateProgress on submitQuiz
- useExam.submitExam: logs one ResponseRecord per answered question, supports
per-question confidenceMap (defaults 'fairly-sure' pending T3 ConfidenceTap)
- Adds Vitest + @vitest/coverage-v8 (devDeps); 50 tests, 87 %+ coverage on
store/**, 100 % function coverage
- Fixes Quiz.tsx onClick type error introduced by optional-Confidence signature
* feat(study): T4 schema & tooling — question bank scaffolding and CI gate
Adds the complete T4 deliverables for the study system v1:
- scripts/_questions-schema.mjs: standalone Zod mirror of the questionsCollection
schema from src/content/config.ts, usable without the Astro runtime
- scripts/new-question.mjs: idempotent stub generator (--category, --slug, --type)
- scripts/validate-questions.mjs: YAML validator with per-category coverage summary
- scripts/questions-coverage.mjs: markdown coverage table
- scripts/generate-question-schema.mjs: emits schemas/question.json via zod-to-json-schema
- scripts/validate-questions.test.mjs: drift-detection unit tests (node --test)
- schemas/question.json: JSON Schema for IDE YAML autocomplete
- src/content/questions/_example/example-mcq.yaml: annotated curator reference template
- package.json: adds question:new, validate:questions, questions:coverage, schema:questions scripts
- .github/workflows/deploy.yml: adds "Validate question bank" step (conditional on YAML presence)
* feat(study): implement SM-2 scheduler, due-aware selector, near-miss interleaver (T2)
Full SM-2 algorithm in applySM2: lapse resets repetitions/interval, recall grows
interval (1→6→round(prev*EF)), EF updated unconditionally and clamped to ≥1.3.
selectQuestionsV2: due-first (most-overdue first), fresh-fill per category budget,
stale-fallback (excludes un-budgeted due items), round-robin category interleaving.
interleaveNearMisses: greedy deterministic reorder enforcing minSpacing between
confusion pairs, gracefully accepts violations when constraint is unsatisfiable.
Adds vitest + @vitest/coverage-v8 as dev deps; vitest.config.ts minimal setup.
* test(study): scheduler/selector/interleaver test suites, 71 tests ≥90% coverage
scheduler.test.ts: qualityOf mapping, lapse invariants (reset rep/interval, lapses++),
recall invariants (rep/interval progression), EF clamping to ≥1.3, hand-rolled
property test asserting non-decreasing intervals on any correct-answer streak,
concrete q=5 example verifying 1→6→~15→~38 progression per V1-PLAN §5.1.
selector.test.ts: in-memory FakeStore; covers empty store, due-first ordering,
reviewMixRatio ceiling, per-category budgets, stale-fallback, round-robin interleaving,
edge cases (orphan due IDs, null schedule in stale path, categories outside budgets).
interleaver.test.ts: spacing enforcement, determinism, graceful violation fallback,
impossible constraints, 20-item multi-pair scenario.
Coverage: 90.62% branches, 100% functions/lines across all three modules.
* feat(study): T3 — 7 study UI components with tests and dev harness
Add ConfidenceTap, RationalePanel, ScheduleBadge, UnvettedFlag,
LearnQuizWidget, StudySettingsPanel, ExportImportPanel to
src/components/quiz/study/. Each component is typed against
study-types.ts contracts, uses only existing ui/ primitives (Button,
Card, Badge, IconBox), and ships a full RTL test suite.
Dev harness at src/pages/_dev/study-components.astro (DEV-gated 404
in production). Vitest + jsdom test infrastructure added; 89 tests
pass. Design-token gaps documented in docs/design-needs.md.
* docs(study): add study-system overview, authoring guide, SM-2 ADR
- docs/study-system.md — system overview with three Mermaid diagrams
(data flow, SM-2 state machine, component tree)
- docs/authoring-questions.md — curator-facing 5-step workflow guide
- docs/adr/0001-sm2-vs-fsrs.md — architecture decision record for the
SM-2 → FSRS upgrade path
Refs V1-PLAN.md §1, §3, §5, §6, §10, §13.
* chore(study): integration fixes — unify vitest env to jsdom, refresh lockfile
* chore(forge): add CODEOWNERS, study-track PR template, and v1 release checklist
- .github/CODEOWNERS: auto-request @Bissbert on PRs touching study-v1 paths
(study-types, scheduler, selector, interleaver, store/, quiz components,
questions collection, content config, scripts, and docs)
- .github/PULL_REQUEST_TEMPLATE/study-track.md: opt-in template (via
?template=study-track.md) with per-track checkboxes and the V1-PLAN §13.8
acceptance checklist
- docs/release-checklist-study-v1.md: ordered pre-flight steps for the
feature/study-v1 → main release PR, covering integration green, §12
acceptance criteria, bundle-size gate (<=+25 KB gz), T7 audit sign-off,
tagging, IndexNow verification, Cloudflare live check, and worktree cleanup
* feat(study-content): add 10 equipment questions on Sturman refractometer patterns
Covers all seven Sturman shadow-edge patterns (I–VII) including isotropic,
uniaxial (Patterns I–III, spot method), and biaxial (Patterns IV–VII) scenarios,
with optic-sign determination, above-range readings, and paired ruby/spinel
discrimination. Includes concept-tags controlled vocabulary (docs/concept-tags.md).
* feat(study-content): add 10 fundamentals questions on crystal systems and cleavage
Covers all seven crystal systems, point groups (3̄m corundum, 32 quartz, 3m
tourmaline), cleavage vs parting distinction in corundum, Miller index four-index
notation for trigonal/hexagonal, and a five-gem optic-character sorting exercise.
* feat(study-content): add 10 fundamentals questions on RI/SG/hardness near-miss discrimination
Covers near-miss property matching for fluorite vs topaz, diamond vs quartz, jadeite
vs peridot, spinel calibration values, orange tourmaline vs peridot vs spessartine,
blue spinel vs blue sapphire, green beryl vs tourmaline, kyanite directional hardness,
nephrite vs jadeite, and moonstone adularescence discrimination.
* feat(study-content): add 10 species questions on five-mineral discrimination clusters
Red stone cluster (ruby/spinel/garnet/red-glass/synthetic-ruby): corundum-vs-spinel-001
through corundum-vs-spinel-003, red-stone-garnet-001, red-stone-glass-001.
Green stone cluster (emerald/chrome-tourmaline/green-glass/demantoid/peridot):
green-stone-001 through green-stone-005. Cross-referencing confusionPairs throughout.
* feat(study-content): add 10 identification questions on inclusion recognition
Silk dissolution and treatment detection (silk-001, silk-002), natural fingerprint
vs flux fingerprint (fingerprint-001), fingerprint vs fissure discrimination
(fissure-001), three-phase inclusions and Colombian origin (three-phase-001),
growth tubes in tourmaline (growth-tube-001), zircon halos and heat treatment
(zircon-halo-001), negative crystals in spinel (negative-crystal-001), two-phase
inclusions and synthetic discrimination (two-phase-001), demantoid horsetail
inclusions (horsetail-001).
* fix(study): address T7 audit P1/High findings
Security (T7b):
- Add isResponseRecord type guard in importAll; skip invalid records (T7b-01)
- Wrap _write in try/catch with QuotaExceededError handling and trim-then-retry
for the response store; introduce StorageFullError (T7b-02)
- Migration runner now no-ops when stored version >= CURRENT_VERSION,
preventing downgrade clobber from v2-aware tabs (T7b-05)
- clearQuizStorage now removes all four v1 study keys plus migration
version stamp (T7b-07)
SEO/UX (T7c):
- noindex /quiz while LockGate gates it (F-01)
- Wire LearnQuizWidget into /learn/[slug] above article body when
curated questions reference the article (F-03)
- New /study/review and /study/settings routes wrapping existing study
components in proper LockGate-protected page shells (F-06)
- Remove duplicate Sitemap directive that pointed at llms.txt (F-10)
Audit deliverables also added under audits/ for transparency.
Tests: 210/210 pass. Build: 912 pages.
* feat(study-content): annotate rationales with reference markers and citations
Add [ref:id] inline markers at every specific factual claim across all 50
question YAML files (fundamentals, equipment, species, identification).
Replace or remove three unverifiable citations from T5a's original draft
(Schwarzenbach 2003, Pignatelli 2024, Sturman 1987 journal ref); the Sturman
pattern classification is re-anchored to Anderson (1990) Gem Testing 10th ed.
All 50 files validate at 50/50 with zero unvetted and zero missing references.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ships gemmology.dev study-system v1.0 per
V1-PLAN.md. Eight parallel agent tracks (T1–T8) plus integration and audit fixes — 24 commits, 210 passing tests, 912 pages built, ~21 KB gz of new study chunks.What's in
LocalStudyStore, versioned migrations, hook wiring (useQuiz/useExam), 50 tests, 87% coverageConfidenceTap,RationalePanel,ScheduleBadge,UnvettedFlag,LearnQuizWidget,StudySettingsPanel,ExportImportPanel) + dev harness, 89 testsquestionsCollection, scaffolder, validator, JSON-schema generator, CI gateconfusionPairsclustering[ref:id]markers andreferences:blocks; sources verified against Read (2008), Anderson (1990), Gübelin & Koivula vols. 1 & 3docs/study-system.md,docs/authoring-questions.md,docs/adr/0001-sm2-vs-fsrs.mdCODEOWNERS, study-track PR template, release checklistNew routes
/study/review— express SM-2 review (auth-gated, noindex)/study/settings— study preferences + JSON export/import (auth-gated, noindex)/learn/<slug>— pretest widget renders above article body when curated questions reference the slugAudit fixes (in this branch)
Security (T7b):
isResponseRecordguard inimportAllrejects malformed records (T7b-01)_writecatchesQuotaExceededErrorwith trim-then-retry; introducesStorageFullError(T7b-02)CURRENT_VERSION(T7b-05)clearQuizStoragepurges all v1 study keys + migration stamp (T7b-07)SEO (T7c):
noindexon/quizwhile LockGate gates it (F-01)LearnQuizWidgetwired into/learn/[slug](F-03)/study/reviewand/study/settingsroutes created (F-06)robots.txtduplicateSitemap:directive removed (F-10)Verification
npm test— 210/210 passnpm run build— 912 pages, exit 0npm run validate:questions— 50/50 valid, 0 unvetted, every rationale has referencesnpm run questions:coverage— 4 active categories ≥10 vetted items eachaudits/for reviewTest plan
/quiz(auth-gated → setup → 5-question practice)/study/reviewempty-state and post-answer state/study/settings, toggle each setting, verify persistence/learn/equipment/refractometerand verify pretest widget appears/quiz(Perf ≥85, A11y ≥95, BP ≥90, SEO ≥95)Post-merge
Tag
v1.0.0-studyonmainand letindexnow.ymlping/study/*URLs.