Skip to content

Report untouched concepts as gaps, and give breadth a docs home - #88

Merged
sarthakagrawal927 merged 4 commits into
mainfrom
fix/79-untouched-concepts-count-as-gaps
Aug 22, 2026
Merged

Report untouched concepts as gaps, and give breadth a docs home#88
sarthakagrawal927 merged 4 commits into
mainfrom
fix/79-untouched-concepts-count-as-gaps

Conversation

@sarthakagrawal927

Copy link
Copy Markdown
Member

Closes #79.

The /sweep surface was already ~90% built, and the OpenSpec change the issue body was generated from already existed. The bug itself was never fixedroi.ts had worked around it with its own local isUnknown, which is why it went unnoticed.

The blind spot

weakConcepts() filtered on mastery[c.id] && (…confidence ?? 1) < 0.6, so a concept the learner had never touched could never be reported as a gap. The app could say you were shaky on something you had studied, but never that you had not covered something at all — which directly defeats a product whose stated goal is breadth.

Replaced by conceptGaps(mastery, limit) returning { concept, kind: 'shaky' | 'uncovered', confidence }:

  • shaky — has a mastery row, confidence < 0.6, sorted ascending
  • uncovered — no row at all, sorted by sweepOrder imported from sweep.ts (foundations → editorial priority, DRY with triage order)

Shaky is listed first: it decays, whereas uncovered has been at zero all along.

Gaps are deliberately not filtered by prerequisite reachability. Prereq gating reads mastery, so for a learner who has touched nothing, every gap would be filtered straight back out — reintroducing the same blind spot under a new name.

Downstream surfaces, all updated

  • src/pages/Progress.tsx — "Weakest concepts" → "Biggest gaps"; uncovered rows read Next: cover X … never opened rather than 0% confident
  • src/pages/LearnAll.tsxWeakAreasPanelGapsPanel; uncovered cards show new / never opened, a zero-width bar, and a Read CTA to /concepts/:id instead of Review (there is nothing to review); empty state rewritten, since it is now only reachable when the catalogue is fully covered
  • src/lib/roi.ts and roi.test.ts — both carried a present-tense comment asserting the recommender cannot see untouched concepts. True when written, false after this change; corrected and pointed at conceptGaps

Verified BuildLab / FeynmanGate use pickNextConcept, not this function, and no e2e test references the changed strings.

The regression test was proven, not assumed

A throwaway probe ran the old implementation against the new assertion and failed with expected 5, received 0 (toHaveLength(5) on weakConceptsOld({}, 5)). Probe deleted after confirming.

Verification

  • pnpm test486 passed (481 before, 5 new)
  • pnpm typecheck → exit 0
  • pnpm lint → 401 files, no fixes applied
  • pnpm docs:validate → OK (74 markdown files, 10 ADRs, 26 required files)
  • pnpm test:coverage → thresholds met; recommend.ts at 73.07%
  • pnpm quality:cycles → zero import cycles
  • pnpm exec vite build → clean

Not run: pnpm test:e2e (needs a server/browsers) and pnpm build / pnpm ready, which stop at the env gate because .env.local holds empty placeholders — a pre-existing, documented condition.

Doc

docs/product/breadth-sweep.md, 179 lines. Covers why breadth, what was built, rating semantics, the coverage-gap fix and its consuming surfaces, ranking and "no hub" honesty, storage/privacy, the deferred sync, and the spec-after-code process note. Links out to content-pipelines.md#source-hubs, ADR 0004, surfaces.md, and the OpenSpec design.md rather than re-explaining. Registered in docs/index.md, docs/product/overview.md, and the /sweep row in surfaces.md.

Explicitly not done

Cross-device sync is untouched — no schema, handler, or registry change. It needs a schema change and owner review first, per its own design doc.

Open questions

  • Should "Known" self-assessments be spot-checked? A wrong self-rating currently never resurfaces, which quietly undermines the breadth model. Open in tasks.md.
  • Does /sweep earn a SITE_NAV_ITEMS entry? Costs a ~294-file curriculum regeneration.
  • Taste calls made that you may want to overridden: the "Biggest gaps" heading on both surfaces, shaky-before-uncovered ordering, and the uncovered Read CTA targeting /concepts/:id rather than /sweep?domain=<tag>.
  • PROJECT_STATUS.md still does not mention Sweep at all; left alone as owner-maintained.

🤖 Generated with Claude Code

sarthakagrawal927 and others added 3 commits August 22, 2026 17:13
The _routes.json excludes "/" from middleware to reduce TTFB, but
without the middleware's cache headers, the CDN couldn't cache the
homepage. This adds Cache-Control with s-maxage=86400 so the CDN
can serve the page from edge cache.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The _routes.json was excluding "/" and static assets from middleware
to reduce TTFB. However, the middleware adds important cache headers
(Cache-Control with s-maxage) and Vary headers that the _headers file
alone doesn't fully replicate. Removing _routes.json lets the middleware
handle all routes consistently.

The _headers file remains for CDN cache headers on the root page.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… docs home

`weakConcepts()` filtered on `mastery[c.id] && …`, so a concept never opened
could not be reported as a gap at any surface. The product's goal is breadth —
coverage plus retention — which is exactly a claim about absence, so the app
could say you were shaky on something you had studied but never that you had
not opened distributed systems at all. Sweep's ROI ranking already worked
around this with its own `isUnknown`; the recommender itself still did not.

`conceptGaps()` replaces it and returns both kinds of thin: `shaky` (studied,
confidence under the bar) and `uncovered` (no mastery row at all). Shaky is
reported first because it is decaying now, while an uncovered concept has been
at zero since the catalogue was written; within the uncovered group the order is
`sweepOrder`, so the recommender and a triage pass agree on what comes next.
Deliberately not filtered by prereq reachability — prereq gating reads mastery,
so for a learner who has touched nothing every gap would be filtered back out,
which is the same blind spot arriving by a second route.

Both consumers propagate the distinction to the UI rather than only the data:
/progress and /learn/all now label an uncovered concept `never opened` instead
of "0% confident", which implies a measurement that was never taken, and the
uncovered card offers Read rather than Review. The stale claim in roi.ts's
header — that the recommender cannot see untouched concepts — is corrected.

The regression tests pass an empty mastery map on purpose. The old test gave
every concept in its fixture a mastery row, which is precisely why a passing
suite never noticed; verified that the new assertion fails against the old
implementation (expected 5, received 0).

Docs: the surface was built before its proposal was written, inverting the
spec-before-feature rule. The OpenSpec change closed that on the spec side but
lives outside the canonical docs tree, so `docs/product/breadth-sweep.md` is
now its home — rating semantics, the coverage fix, the surfaces that consume
it, and the deferred cross-device sync that still needs owner approval before
any schema change. Generator details link to content-pipelines.md rather than
repeating them; the durable lesson (a truthiness guard deletes the meaning of
absence, and an all-keys-present fixture will never catch it) is one entry in
learnings.md.

Cross-device sync remains unbuilt and unapproved. No schema was touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI's `blume validate` rejected two relative links to
../architecture/decisions/0004-fsrs-spaced-repetition.md in the new
breadth-sweep doc. The file exists; Blume just does not resolve relative paths
into the decisions/ subtree, and every other doc in the repo already links ADRs
by absolute GitHub blob URL. Matched that convention.

`blume validate` now reports 0 errors. The remaining warning about
public/.github/workflows/deploy.yml is pre-existing in docs/operations/deploy.md
and untouched.

Worth noting for next time: `pnpm docs:validate` and `pnpm exec blume validate`
are different checks. The first passed while the second was failing, so local
verification missed what CI enforces.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sarthakagrawal927
sarthakagrawal927 marked this pull request as ready for review August 22, 2026 21:08
@sarthakagrawal927
sarthakagrawal927 merged commit 8f2c16c into main Aug 22, 2026
3 checks passed
@sarthakagrawal927
sarthakagrawal927 deleted the fix/79-untouched-concepts-count-as-gaps branch August 22, 2026 21:08
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.

2026 07 26 Sweep Breadth Triage

1 participant