Skip to content

feat(study): v1.0 launch — SM-2, curated bank, study UI, audits - #14

Merged
Bissbert merged 24 commits into
mainfrom
feature/study-v1
May 5, 2026
Merged

Bissbert merged 24 commits into
mainfrom
feature/study-v1

Conversation

@Bissbert

@Bissbert Bissbert commented May 5, 2026

Copy link
Copy Markdown
Contributor

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

  • T1 Foundation — LocalStudyStore, versioned migrations, hook wiring (useQuiz/useExam), 50 tests, 87% coverage
  • T2 Algorithms — pure SM-2 scheduler, due-aware selector, near-miss interleaver, 71 tests, 98.6% coverage
  • T3 UI — 7 study components (ConfidenceTap, RationalePanel, ScheduleBadge, UnvettedFlag, LearnQuizWidget, StudySettingsPanel, ExportImportPanel) + dev harness, 89 tests
  • T4 Schema — questionsCollection, scaffolder, validator, JSON-schema generator, CI gate
  • T5a Editorial — 50 curated questions (fundamentals 20, equipment 10, species 10, identification 10) with confusionPairs clustering
  • T5b Citations — every rationale annotated with [ref:id] markers and references: blocks; sources verified against Read (2008), Anderson (1990), Gübelin & Koivula vols. 1 & 3
  • T6 Docs — docs/study-system.md, docs/authoring-questions.md, docs/adr/0001-sm2-vs-fsrs.md
  • T7 Audits — conversion (T7a), security (T7b), SEO (T7c) — High/P1 findings remediated
  • T8 Forge — CODEOWNERS, study-track PR template, release checklist

New 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 slug

Audit fixes (in this branch)

Security (T7b):

  • isResponseRecord guard in importAll rejects malformed records (T7b-01)
  • _write catches QuotaExceededError with trim-then-retry; introduces StorageFullError (T7b-02)
  • Migration runner no-ops when stored version >= CURRENT_VERSION (T7b-05)
  • clearQuizStorage purges all v1 study keys + migration stamp (T7b-07)

SEO (T7c):

  • noindex on /quiz while LockGate gates it (F-01)
  • LearnQuizWidget wired into /learn/[slug] (F-03)
  • /study/review and /study/settings routes created (F-06)
  • robots.txt duplicate Sitemap: directive removed (F-10)

Verification

  • npm test — 210/210 pass
  • npm run build — 912 pages, exit 0
  • npm run validate:questions — 50/50 valid, 0 unvetted, every rationale has references
  • npm run questions:coverage — 4 active categories ≥10 vetted items each
  • Bundle delta: study chunks ~21 KB gz total (under +25 KB cap)
  • T7a / T7b / T7c audit reports under audits/ for review

Test plan

  • Manual smoke on /quiz (auth-gated → setup → 5-question practice)
  • Open /study/review empty-state and post-answer state
  • Open /study/settings, toggle each setting, verify persistence
  • Export → wipe localStorage → import → all four keys restore
  • Open /learn/equipment/refractometer and verify pretest widget appears
  • Lighthouse on /quiz (Perf ≥85, A11y ≥95, BP ≥90, SEO ≥95)

Post-merge

Tag v1.0.0-study on main and let indexnow.yml ping /study/* URLs.

Bissbert added 24 commits May 5, 2026 11:09
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:
#	package-lock.json
#	vitest.config.ts
… 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.
@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

🚀 Preview deployed to: https://e1b83c9b.gemmology-dev.pages.dev

@Bissbert
Bissbert force-pushed the feature/study-v1 branch from 4fa26fa to b884e59 Compare May 5, 2026 05:33
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented May 5, 2026 •

Copy link
Copy Markdown

Deploying gemmology-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: b884e59
Status:🚫  Build failed.

View logs

@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

🚀 Preview deployed to: https://bc4f8042.gemmology-dev.pages.dev

@Bissbert
Bissbert merged commit 9362b81 into main May 5, 2026
3 of 4 checks passed
@Bissbert
Bissbert deleted the feature/study-v1 branch May 5, 2026 05:35
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

1 active deployment
gemmology-dev (Preview) — b884e594 Deployed May 5, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant