Math tracker (/trackers/math): Evan Chen's Napkin, chapter by chapter
Some checks failed
Deploy production / deploy (push) Failing after 9s

Schema math (parts, chapters, sections, activity), seeded with all 100 chapters
(Ch 0-94, appendix A-E), 647 sections, PDF pages, exercise counts and approximate
prereqs. Triggers stamp started_at/completed_at and log status/exercise/note/section
changes; math.apply_progress(jsonb) applies the JSON format bots send. Private by
default via MATH_PRIVATE in #lib/math (owner-only prefix + lock in every tracker list).
Overall + per-part progress, current chapter, prereq hints, quick status buttons,
exercises stepper, notes, optional section ticks, activity log, dependency chart.
Deploy preflight checks the math schema (4 tables, 100 chapters).
This commit is contained in:
2026-10-26 10:55:16 +00:00
parent 23a43049a0
commit d99245e9fc
16 changed files with 45741 additions and 4 deletions

39
src/lib/math.ts Normal file
View File

@@ -0,0 +1,39 @@
// Math tracker (/trackers/math): Evan Chen's "An Infinitely Large Napkin". Shared by the
// server (owner gate, loads) and the page.
/**
* The privacy switch. true: owner-only (404 for everyone else, lock icon in every tracker
* list). false: a public, read-only page for everyone else (notes stay owner-only, edits
* are always owner-only). Flip this one constant; owner.ts and projects.ts read it.
*/
export const MATH_PRIVATE = true;
export const MATH_STATUSES = ['not_started', 'reading', 'exercises', 'done', 'skipped'] as const;
export type MathStatus = (typeof MATH_STATUSES)[number];
export const STATUS_LABEL: Record<MathStatus, string> = {
not_started: 'not started',
reading: 'reading',
exercises: 'exercises',
done: 'done',
skipped: 'skipped'
};
/** The free PDF of the book; chapters link to it with #page=N (PDF page numbers). */
export const NAPKIN_PDF = 'https://venhance.github.io/napkin/Napkin.pdf';
export const pdfPage = (page: number) => `${NAPKIN_PDF}#page=${page}`;
/** "9,10,11,12,18" → "9–12, 18" (numeric keys only; letters are listed as-is). */
export function compactKeys(keys: readonly string[]): string {
const nums = keys.filter((k) => /^\d+$/.test(k)).map(Number);
const other = keys.filter((k) => !/^\d+$/.test(k));
nums.sort((a, b) => a - b);
const out: string[] = [];
for (let i = 0; i < nums.length;) {
let j = i;
while (j + 1 < nums.length && nums[j + 1] === nums[j] + 1) j++;
out.push(j - i >= 2 ? `${nums[i]}–${nums[j]}` : nums.slice(i, j + 1).join(', '));
i = j + 1;
}
return [...out, ...other].join(', ');
}