mirror of
https://github.com/prdlk/leetcode.git
synced 2026-09-16 23:16:26 +00:00
9.7 KiB
9.7 KiB
Repository Guidelines
Project Overview
LeetCode interview-prep campaign (8 weeks, 2026-08-17 → Oct 11) run as a repo: solution files under work/, a Blume docs site under apps/docs/, GitHub Actions that reconcile issues and publish docs, and a Cloudflare Worker (apps/api/) that runs the spaced-repetition system (SRS) — daily digest email, one-tap logging, weekly review issues, live charts. Issues model the curriculum: ~24 topic issues own ~161 problem sub-issues (set:core|optional|deferred, diff:*); milestones are phases; a user-level GitHub Project ("Interview Prep", #2) mirrors review state.
Architecture & Data Flow
Three owners, one direction of truth:
flowchart LR
work[work/ solutions] -->|bun run sync| docs[apps/docs/content/*.mdx]
work -->|close-solved.yml| PI[problem issues]
PI -->|close-topics.yml| TI[topic issues]
GH[GitHub issues = catalog] -->|nightly reconcile| D1[(D1 = SRS state)]
D1 -->|8 AM ET cron| Mail[digest email + one-tap links]
Mail -->|GET /log| D1
PI -->|/done webhook| D1
D1 -->|best-effort mirror| Proj[GitHub Project fields]
D1 -->|Sat midnight ET| Review[Review — Week N issue]
D1 --> Charts[SVG charts + /api/stats]
- Reconcile, don't react.
close-solved.ts,close-topics.ts, and the Worker's catalog sync recompute desired state from scratch each run: re-runs are no-ops, backfills need no special casing, closing is one-directional. - D1 owns SRS state; GitHub issues own the catalog;
apps/api/data/schedule.jsonowns the calendar. The catalog reconcile never invents rows and never overwrites SRS columns (stage,next_review). Project fields (Target Date,SRS Stage,First Attempt) are a best-effort mirror (apps/api/src/mirror.ts): failures are warnings, never lost D1 writes; topic rows'Target Dateis never written. - Determinism = idempotency. Drill/gate sampling uses a seeded PRNG (
rng()inapps/api/src/srs.ts, FNV-1a → mulberry32, seed = date / ISO week); the digest is keyed by ET date inemail_log; one-tap links are unique on (problem, date, kind). - DST-proof crons: each event has two UTC crons; code fires only on the computed ET hour (
etHour), unit-checked inapps/api/src/srs.test.ts. - Chained workflows:
GITHUB_TOKEN-driven issue closes fire noissuesevents, soclose-topics.ymlchains offworkflow_runof Close Solved instead. The Worker's webhook uses its ownGH_PAT, so its events flow normally.
Key Directories
| Path | Purpose |
|---|---|
work/<Difficulty>/<Category>/<num>.<slug>.{js,py} |
Solutions, e.g. work/Easy/Array/1.two-sum.py. Machine-parsed header comment (title / Difficulty: / URL / ─ rule / statement) — preserve its exact shape |
apps/cli/ |
Flat Bun TS workspace: automation entries (shebang + top-level await) and libraries (no shebang, side-effect-free on import). Root bun run scripts delegate here |
apps/docs/ |
Blume site (blume.config.ts, content/, islands/, public/). content/(<category>)/<num>-<slug>.mdx generated by sync |
apps/api/ |
Cloudflare Worker workspace: src/ modules, data/schedule.json (day → topic issue, human-edited, bundled at deploy), migrations/, scripts/import-srs.ts |
.github/workflows/ |
deploy (docs → Pages, Worker → Cloudflare, on every main push), close-solved, close-topics, sync-d1 (issue edits → /admin/reconcile) |
Development Commands
All from the repo root (a Bun workspace over apps/*):
bun run pick # scaffold a solution via leetcode-cli (fuzzy picker)
bun run test # run ONE solution against LeetCode's judge (not a test suite)
bun run sync # work/ → apps/docs/content/ pages
bun run dev|build # Blume docs site (runs in apps/docs/)
bun run close-solved -- --dry-run # issue reconcilers (also DRY_RUN=1)
bun run close-topics -- --dry-run
bun run api:dev # wrangler dev on :8787 (local D1); api:test = DST guard tests
bun run api:deploy # deploy the Worker by hand (CI also deploys on main pushes)
curl -X POST -H "Authorization: Bearer $LINK_KEY" \
"localhost:8787/admin/digest?dry=1&date=2026-08-30" # preview a digest, send nothing
Code Conventions & Common Patterns
- Scripts are either entries or libraries. Entries (
apps/cli/close-*.ts) use shebang + top-levelawait. Libraries (apps/cli/github.ts,apps/cli/report.ts, everything inapps/api/src/) are side-effect-free on import — nothing touches network/credentials until a factory (github(),projectMirror()) is called. - Shared plumbing:
github()givesrepo,api()(throwsMETHOD path -> status body),list()(paginating async generator),closeIssue()(comment first, then close — a failed PATCH still leaves a trace).report.tsgivesprintTable/markdownTable/writeStepSummary. - Narrow API payloads with guards, not inline casts:
if (!("number" in value) || typeof value.number !== "number") return;(seereadProblemIssueinclose-solved.ts). Named-const casts only with a reason. - Dry-run everything: reconcilers take
--dry-run/DRY_RUN=1; Worker admin routes take?dry=1&force=1&date=(theSRS_TODAYequivalent) andwrangler devruns against local D1 — exercise logic there before touching production state. - Dates: always ET calendar strings (
YYYY-MM-DD), arithmetic anchored at noon UTC (atNooninapps/api/src/srs.ts) to dodge DST. Nevernew Date()math directly. - Style: section-divider comments (
// ── name ───), file-top doc comments explaining the why and invariants, 2-space JSON with trailing newline,Mapfor dynamic keys /Recordfor static tables, no tiny one-expression wrapper functions. - Known intentional duplication:
close-solved.tsre-implements header stripping instead of importing fromsync.tsbecausesync.tsruns its pipeline on import. Don't "deduplicate" it.
Important Files
apps/api/src/srs.ts— SRS domain: ladder (new → +2 → +5 → +10 → retired; fail resets to+2; first-ever log enters at+2), ET date math, seeded sampling,logAttempt()(the ONE write path — email taps, webhook, gate scoring all converge here).apps/api/src/index.ts— router + cron dispatch;apps/api/wrangler.jsonc— bindings (DB,EMAIL), crons, vars; secretsGH_PAT/WEBHOOK_SECRET/LINK_KEYviawrangler secret put.apps/api/src/digest.ts/apps/api/src/email.tsx— the daily digest, split data/presentation.digest.tsreads D1 into aDigestData;email.tsxowns every colour and every sentence, and renders both the HTML and (via its ownplainDigest, not React Email'splainTextmode, which flattens the tables) the text alternative. The retrieval rules hold by construction:DigestRowhas no title and no issue field, so a review or drill line cannot leak the topic or a solution link. Subject is(Day N/56) LeetCode Daily Digest.apps/api/src/png.ts— hand-rolled PNG encoder (RGB8, one IDAT, zlib viaCompressionStream("deflate"), CRC32, 5×7 bitmap font). It exists because every major email client refuses remote SVG, so/chart/heatmap.pngrasters the heatmap for the digest while/chart/heatmap.svgkeeps serving the README byte-for-byte. Both come from oneheatmapCells()so the two pictures cannot drift.apps/cli/sync.ts— work→docs contract: only## Solutiononward is script-owned on existing pages; human prose is never touched; never hand-write solution pages or edit inside## Solution.README.mdlive charts are Worker endpoints (/chart/*.svg,/badge/gate.svg, 5-min Camo cache); the docs/progresspage fetches/api/statsclient-side (apps/docs/islands/ProgressDashboard.tsx).apps/docs/blume.config.ts— site base/leetcode;README.mdcampaign table doubles as topic-map input tosync.ts(readmeTopics(),TOPIC_ALIASES).- Old
.github/srs/JSON state and thesrs-*Actions are RETIRED — do not resurrect;apps/api/scripts/import-srs.tsdocuments the migration.
Runtime/Tooling Preferences
- Bun only: run scripts with
bun apps/cli/<name>.ts(or the rootbun runaliases), install withbun install --frozen-lockfile. One workspace (apps/*), one rootbun.lock. Node 22 appears only indeploy.ymlbecause Blume requires it. - No root tsconfig, no ESLint, no Prettier — match surrounding style by hand.
apps/api/has a stricttsconfig.json;Envtypes are generated (bunx wrangler types), never hand-written. - Near-zero runtime npm dependencies — SVG, PNG, HMAC (WebCrypto), GraphQL are hand-rolled; Actions scripts use Bun builtins +
fetchonly. The one exception is the digest email:apps/api/src/email.tsxrenders React Email (react,react-dom,@react-email/components,@react-email/render) inside the Worker, because hand-rolling table-based email HTML that survives Gmail and Outlook is not worth owning. It costs ~235 KB gzipped of a 3 MB budget, needs nonodejs_compat(it resolves torenderToReadableStream), and requires"jsx": "react-jsx"inapps/api/tsconfig.json. Do not extend this exception to any other module. - Local GitHub auth falls back to
gh auth token; scripts run fine outside Actions.
Testing & QA
- Repo-wide: no test framework —
bun run testsubmits one solution to LeetCode's judge. Exception:apps/api/src/srs.test.ts(bun:test) proves the DST cron guards with fixed dates; run viabun run api:test. - QA is dry-runs + local state: Worker logic against
wrangler dev+ local D1 with?dry=1&date=; reconcilers with--dry-run; idempotency check = run twice,cmpoutput. - Docs changes:
bunx blume build --isolated(fromapps/docs/) must pass with no new warnings (thenrm -rf apps/docs/.blume-verify); after sync, review only created pages (prose transforms are heuristic).