From aaf1297aa40b55b55c25ec9e17cd9e0df144ce79 Mon Sep 17 00:00:00 2001 From: tsenoner Date: Tue, 23 Jun 2026 16:09:40 +0200 Subject: [PATCH 1/2] feat(learn): label Jan/Feb month anchors common-year vs leap (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage 3 listed "January: the 3rd (the 4th in leap years)" — the lead value (3rd, 28th) was the silent non-leap anchor and the parenthetical the silent leap variant, the exact skim-trap the stage warns about. Label each explicitly. - curriculum.ts: "January: the 3rd in common years, the 4th in leap years"; "February: the last day — the 28th in common years, the 29th in leap years". Trim the framing line's redundant "shift by one" mechanic, keeping the "trap that catches everyone" warning + that only Jan/Feb change. Even-month doubles and the 9-to-5 odd mnemonic are leap-invariant — left untouched. - CheatSheet.tsx: add a caption "Jan/Feb shown for common years; each is +1 in a leap year" so the terse table cells are labeled too. - curriculum.test.ts: lock the common/leap labels + digits on Jan/Feb and assert no leap qualifier leaks onto the even-month doubles. Values match the engine (monthAnchor: common Jan 3/Feb 28, leap Jan 4/Feb 29). Gates: typecheck, lint, tests, build — all green. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/features/learn/CheatSheet.tsx | 3 +++ src/features/learn/curriculum.test.ts | 19 +++++++++++++++++++ src/features/learn/curriculum.ts | 6 +++--- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/features/learn/CheatSheet.tsx b/src/features/learn/CheatSheet.tsx index 35a6d91..a9645da 100644 --- a/src/features/learn/CheatSheet.tsx +++ b/src/features/learn/CheatSheet.tsx @@ -40,6 +40,9 @@ export function CheatSheet() {

÷4 yes · ÷100 no · ÷400 yes again. (Non-century year: last two digits ÷4.)

Month anchors

+

+ Jan/Feb shown for common years; each is +1 in a leap year. +

{months.map(([m, a]) => ( diff --git a/src/features/learn/curriculum.test.ts b/src/features/learn/curriculum.test.ts index 75f3da6..02b2c0f 100644 --- a/src/features/learn/curriculum.test.ts +++ b/src/features/learn/curriculum.test.ts @@ -23,6 +23,25 @@ describe('curriculum', () => { expect(text).toContain('last two digits') expect(text).toContain('unless they are 00') // the xx00 carve-out is still called out }) + it('Stage 3 labels Jan/Feb anchors common-year vs leap, leaving others unqualified (#8)', () => { + const items = (getStage('months')?.blocks ?? []) + .filter((b): b is Extract => b.kind === 'list') + .flatMap((b) => b.items) + const jan = items.find((i) => /january/i.test(i)) ?? '' + const feb = items.find((i) => /february/i.test(i)) ?? '' + for (const item of [jan, feb]) { + expect(item).toMatch(/common year/i) // lead value labeled non-leap + expect(item).toMatch(/leap year/i) // secondary value labeled leap + } + expect(jan).toContain('3') + expect(jan).toContain('4') + expect(feb).toContain('28') + expect(feb).toContain('29') + // Even-month doubles are leap-invariant — they must NOT gain a leap qualifier. + const doubles = items.filter((i) => /\d\/\d/.test(i) && !/jan|feb/i.test(i)) + expect(doubles.length).toBeGreaterThan(0) + for (const d of doubles) expect(d).not.toMatch(/leap/i) + }) it('every stage has content', () => { for (const s of CURRICULUM) expect(s.blocks.length).toBeGreaterThan(0) }) diff --git a/src/features/learn/curriculum.ts b/src/features/learn/curriculum.ts index bab179a..b898851 100644 --- a/src/features/learn/curriculum.ts +++ b/src/features/learn/curriculum.ts @@ -99,13 +99,13 @@ export const CURRICULUM: Stage[] = [ kind: 'list', items: [ 'March: Pi Day, 3/14', - 'January: the 3rd (the 4th in leap years)', - 'February: the last day — 28th (29th in leap years)', + 'January: the 3rd in common years, the 4th in leap years', + 'February: the last day — the 28th in common years, the 29th in leap years', ], }, { kind: 'p', - text: 'In leap years Jan and Feb shift by one — the trap that catches everyone.', + text: 'Only Jan and Feb change in leap years — the trap that catches everyone. Every other anchor is the same all years.', }, ], }, From 5262b5d3031c1fd7292610a8731b46610001a42d Mon Sep 17 00:00:00 2001 From: tsenoner Date: Tue, 23 Jun 2026 18:07:43 +0200 Subject: [PATCH 2/2] refactor(learn): tighten leap-anchor copy and the doubles test (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cleanup from /simplify + /code-review on the Jan/Feb labels PR: - Drop the framing line's redundant second sentence — "Only Jan and Feb change in leap years" already implies every other anchor is unchanged. - Tighten the test's even-month-doubles filter to same-number-both-sides so it no longer also matches March's "3/14"; the `doubles` set (and its length>0 guard) now means what its name says. typecheck ✓ · lint --max-warnings 0 ✓ · tests ✓ Co-Authored-By: Claude Opus 4.8 (1M context) --- src/features/learn/curriculum.test.ts | 5 +++-- src/features/learn/curriculum.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/features/learn/curriculum.test.ts b/src/features/learn/curriculum.test.ts index 02b2c0f..cc3e3cc 100644 --- a/src/features/learn/curriculum.test.ts +++ b/src/features/learn/curriculum.test.ts @@ -37,8 +37,9 @@ describe('curriculum', () => { expect(jan).toContain('4') expect(feb).toContain('28') expect(feb).toContain('29') - // Even-month doubles are leap-invariant — they must NOT gain a leap qualifier. - const doubles = items.filter((i) => /\d\/\d/.test(i) && !/jan|feb/i.test(i)) + // Even-month doubles (4/4 … 12/12) are leap-invariant — they must NOT gain a + // leap qualifier. Match same-number-both-sides so March's "3/14" is excluded. + const doubles = items.filter((i) => /\b(\d+)\/\1\b/.test(i)) expect(doubles.length).toBeGreaterThan(0) for (const d of doubles) expect(d).not.toMatch(/leap/i) }) diff --git a/src/features/learn/curriculum.ts b/src/features/learn/curriculum.ts index b898851..b0963ee 100644 --- a/src/features/learn/curriculum.ts +++ b/src/features/learn/curriculum.ts @@ -105,7 +105,7 @@ export const CURRICULUM: Stage[] = [ }, { kind: 'p', - text: 'Only Jan and Feb change in leap years — the trap that catches everyone. Every other anchor is the same all years.', + text: 'Only Jan and Feb change in leap years — the trap that catches everyone.', }, ], },