diff --git a/AGENTS.md b/AGENTS.md index 4258f3e..61bcef2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## Project Overview -LeetCode interview-prep campaign (2026-08-17 → Nov 3: eight weeks of topics, then the Review-issue phase; the end is the last date in `apps/api/data/schedule.json` — `CAMPAIGN_END`/`CAMPAIGN_DAYS` in `apps/api/src/srs.ts` are derived from it, never declared) 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 (`Topic NN — Name`) own ~161 `problem` sub-issues titled `LC · `, with difficulty and set carried **only** by labels (`set:core|optional|deferred`, `diff:*`) — never by the title; milestones are phases; a user-level GitHub Project ("Interview Prep", #2) mirrors review state. +LeetCode interview-prep campaign (2026-08-17 → Nov 3: eight weeks of topics, then the Review-issue phase; the end is the last date in `apps/api/data/schedule.json` — `CAMPAIGN_END`/`CAMPAIGN_DAYS` in `apps/api/src/srs.ts` are derived from it, never declared) run as a repo: solution files under `work/`, the docs content under `docs/` (MDX only — the Blume site that publishes it lives in [prdlk/cs](https://github.com/prdlk/cs) and reads `docs/` over HTTP), GitHub Actions that reconcile issues, 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 (`Topic NN — Name`) own ~161 `problem` sub-issues titled `LC · `, with difficulty and set carried **only** by labels (`set:core|optional|deferred`, `diff:*`) — never by the title; milestones are phases; a user-level GitHub Project ("Interview Prep", #2) mirrors review state. ## Architecture & Data Flow @@ -10,7 +10,8 @@ Three owners, one direction of truth: ```mermaid flowchart LR - work[work/ solutions] -->|bun run sync| docs[apps/docs/content/*.mdx] + work[work/ solutions] -->|bun run sync| docs[docs/*.mdx + docs/site.json] + docs -->|mdxRemote + site.json fetch| Site[prdlk/cs docs site] work -->|close-solved.yml| PI[problem issues] work -->|close-solved.yml POST /admin/solved| D1[(D1 = SRS state)] PI -->|close-topics.yml| TI[topic + Review N issues] @@ -38,7 +39,7 @@ flowchart LR - **Determinism = idempotency.** Drill/gate sampling uses a seeded PRNG (`rng()` in `apps/api/src/srs.ts`, FNV-1a → mulberry32, seed = date / ISO week); the digest is keyed by ET date in `email_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 in `apps/api/src/srs.test.ts`. - **Chained workflows:** `GITHUB_TOKEN`-driven issue closes fire no `issues` events, so `close-topics.yml` chains off `workflow_run` of Close Solved instead. The Worker's webhook uses its own `GH_PAT`, so its events flow normally. -- **One "finished topic" rule, two consumers.** `apps/cli/topics.ts` (`coreSet()`: every `set:core` sub-issue closed, zero core = never) is read by `close-topics.ts` (closes the topic issue) and `release-topics.ts` (the `release` job of `close-solved.yml`). The release reads the sub-issues, not the topic issue, so the push that finishes a topic releases it in the same run instead of waiting for Close Topics to chain after it. Version = [timever](https://gist.github.com/twolfson/de1b004dd22536b8e668) (`YYYYMMDD.HHMMSS.NNNNNNNNN`, UTC) of the instant the last core problem closed; the tag is annotated and backdated to that instant, on the last first-parent main commit before it; the body is a changelog snapshot at that instant (a one-sentence summary first, then the problems closed on the finish day / earlier, with solution links at the tag — problems still open at the finish are left out). A release is found by its `` body marker, never by tag name, so re-closing a core problem never mints a second one; an existing release keeps its tag and has its name (the topic title) and body rewritten whenever the render changes. The docs `/changelog` is ONE custom page, `apps/docs/pages/changelog.astro` (a NeonUI-style timeline: date left, a card per release right, no per-release subpages, so nothing of it enters the sidebar or prev/next), linked from the header by `navigation.actions` (not a tab). `apps/docs/lib/releases.ts` fetches the releases at build (`GITHUB_TOKEN` in `deploy.yml`; a failed fetch fails the build), dates each by its timever tag, not `published_at` (which the API cannot backdate), tags it `Topic` or `Review` from the release name, and lists the rows of its `### Closed …` tables — so the tag format and that table shape are the page's contract with `render()`. There is no changelog RSS feed. +- **One "finished topic" rule, two consumers.** `apps/cli/topics.ts` (`coreSet()`: every `set:core` sub-issue closed, zero core = never) is read by `close-topics.ts` (closes the topic issue) and `release-topics.ts` (the `release` job of `close-solved.yml`). The release reads the sub-issues, not the topic issue, so the push that finishes a topic releases it in the same run instead of waiting for Close Topics to chain after it. Version = [timever](https://gist.github.com/twolfson/de1b004dd22536b8e668) (`YYYYMMDD.HHMMSS.NNNNNNNNN`, UTC) of the instant the last core problem closed; the tag is annotated and backdated to that instant, on the last first-parent main commit before it; the body is a changelog snapshot at that instant (a one-sentence summary first, then the problems closed on the finish day / earlier, with solution links at the tag — problems still open at the finish are left out). A release is found by its `` body marker, never by tag name, so re-closing a core problem never mints a second one; an existing release keeps its tag and has its name (the topic title) and body rewritten whenever the render changes. The docs site's `/changelog` (prdlk/cs `pages/changelog.astro` + `lib/releases.ts`) fetches this repo's releases at its build, dates each by its timever tag, not `published_at` (which the API cannot backdate), tags it `Topic` or `Review` from the release name, and lists the rows of its `### Closed …` tables — so the tag format and that table shape are its cross-repo contract with `render()`. - **Review issues re-parent problems; topics still own them.** The transfer phase's 20 `Review — ` issues (label `review`, milestone `Review — Transfer & Mocks`; not the Worker's `Review — Week N` gate issues) hold open optional/deferred problems as sub-issues in solve order, moved there with `replace_parent`. `apps/api/src/reviews.ts` is the ONE recovery of ownership — `readReviews()` (one GraphQL request per 50 `review` issues, cheap enough for the Worker's cron subrequest budget) reads each moved problem's topic off its last `parent_issue_removed` event naming a topic, `movedFromTopics()` groups them — shared by the Worker's catalog reconcile (a moved problem's D1 row keeps syncing its labels and `topic_issue`) and `apps/cli/topics.ts` (`readProblems()` returns a topic's sub-issues plus its moved problems, so the finished rule and existing release bodies do not change when a problem moves). A Review issue finishes by `completion()` in `topics.ts` — every sub-issue closed, any set — which `close-topics.ts` closes it on and `release-topics.ts` releases it on, exactly like a topic: timever of the last close, same `` marker (issue numbers never collide), body fields from the review's `**Scheduled:**`/`**Phase:**`/`**Load:**` lines. - **Review days are plan days.** `schedule.json` books each Review issue on its own date — Mon/Tue/Thu/Fri only, Oct 1 → Nov 3 — as `review: { issue, title, problems }` (solve order). The digest renders it as a linked "Today's review" section with the issue's day rules (`REVIEW_RULES` in `email.tsx`, one sentence for HTML and text), its problems join `RESERVED` so drills and sampled gates never draw them early, and `gate: []` skips a Saturday's checkpoint (Oct 3). The issue's sub-issues stay the truth: the catalog reconcile compares every booked review's title and problem list with GitHub and returns any mismatch as `reviewDrift` (also logged), so a re-arranged Review issue shows up the next sync instead of silently disagreeing with the email. Move a review by editing its `schedule.json` date AND the issue's `**Scheduled:**` line (the release body reads it). @@ -48,10 +49,10 @@ flowchart LR |---|---| | `work/<1\|3\|7>///..{js,py}` | Solutions, e.g. `work/1/Easy/Array/1.two-sum.py`. The top directory is the spaced-repetition bucket: `1` = first solve, `3`/`7` = the blind re-solves of that problem 3 and 7 days later. 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/`, `pages/`, `components/` + `components.ts`, `islands/`, `public/`). `content/(0N-)//(problems)/_.mdx` plus one `meta.ts` per phase and per topic folder, all generated by sync (a phase's once, then hand-owned): the phase folder is a `(group)` (sidebar section, no route segment), the topic folder is the README campaign topic slug (`tree-dfs/`, `heap-priority-queue/`) and a real route segment, and `(problems)` is a second `(group)` inside it, so `tree-dfs/(problems)/226_invert-binary-tree.mdx` routes to `/tree-dfs/invert-binary-tree` (Blume sorts by the `_` prefix and strips it from the route). The `meta.ts` files carry the exact labels, phase/topic numbers and icons so the sidebar reads in curriculum order; every topic meta is `display: "page"`, so a topic is one sidebar row whose panel holds its landing page and a "Problems" section. A problem page's badge row (between the description and the body) is rendered, not written: the MDX `wrapper` (`components/ProblemBadges.astro`, registered as `mdx.wrapper` in `components.ts` — Blume's page template renders the title and description itself with no slot before the body, so the wrapper, which MDX hands no route, reads it off `Astro.url`) reads the page's `topics:` (LeetCode's topic tags, from sync) and `video:` (NeetCode YouTube id, from `bun run videos`) frontmatter — both declared via `frontmatter.extend` in `blume.config.ts`, hand-rolled Standard Schemas, no zod — and links a tag the campaign teaches to its topic landing page (filled), any other tag to its card on the Appendix glossary `/appendix#` (outlined; LeetCode's problem list if the tag has no term, gray and iconless), and the video as a red `Explanation` badge. Every badge in the row is a pill. A tag badge carries the icon its target shows (a landing's: the topic `meta.ts` icon read off `data.navigation`; an Appendix term's: its `icon` in `appendix.ts`) and the tag's own colour from `lib/tag-colors.ts` — unique per tag site-wide, assigned greedily over every tag with a landing or a term so tags that share problem pages sit furthest apart in OKLab, in a lightness band that keeps ≥ 3:1 contrast in both themes (Blume's Badge takes one hex for both). The reverse direction is the `PageFooter` slot (`components/BacklinksList.astro`): an Obsidian-style ` ` list under every topic landing page of the problems whose badges link there, plus a topic's own folder members (so a topic no tag names, `hybrid-structures`, still lists its problems), in a native `
` open on load whose top-right chevron collapses it (no JS); problem pages are recognised by their `. ` title and render no list. Which tags the campaign teaches is ONE map, `apps/docs/topic-landings.ts` (tag slug → landing routes), and where a badge points is ONE function beside it, `tagTarget()` — read by both components and the Appendix page (so badges, backlinks and term cards cannot disagree) and the map by sync. Every other tag a problem carries is ONE hand-written table, `apps/docs/appendix.ts` (`APPENDIX`: slug → `{ name, icon, category, definition }`), rendered by ONE custom page, `pages/appendix.astro` — a glossary with an A–Z letter index and a filter box (an `` custom element over static markup; no JS, the full list), one card per term (icon tinted the tag's colour, category, definition, LeetCode list link, and a `
` "Solved" list of the problem pages whose badge `tagTarget()` sends there, paged `PAGE_SIZE` (5) rows at a time by numbered buttons under it — a `` custom element; without JS every row shows and the buttons stay hidden), each card `id`'d by slug and highlighted as `:target`. It is no content folder, so it enters neither the sidebar tree nor prev/next: `navigation.featured` pins it above the tree. Sync writes nothing for it — it warns about a carried tag with no term, a term whose tag a topic now teaches (delete it), and a term `icon` that is a `TOPIC_ICONS` value or another term's. The former per-term pages `/appendix/` are exact redirects to `/appendix#` in `blume.config.ts`, generated from `APPENDIX`'s keys. A hand-written `(0N-)//index.mdx` (`hidden: true`) is that topic's landing page: Blume ≥ 1.7.3 links the sidebar group row to it (`sliding-window/index.mdx` → `/sliding-window`) and the hidden flag drops the duplicate row. Two kinds of custom MDX component, both import-free on every page: `islands/.tsx` is hydrated React (auto-discovered; the five `/progress` cards `SolvedRing`/`SubmissionHeatmap`/`SubmissionChart`/`TopicBubbles`/`RecentSolves`, and `DependencySpine` — the README topic map as data, zoom/pan, nodes link to whatever route Blume's `/api/docs/navigation.json` reports for the topic folder, inert until a topic has pages), `components/.astro` registered in `components.ts` is static markup plus a custom element for behaviour, the way Blume's own `Frame` is built (`Carousel`/`CarouselImage`: `src` takes a URL, `/file.png`, or the page-relative `../../public/file.png`) | +| `docs/` | The docs content, and nothing of the site: no `blume.config.ts`, no `meta.ts`, no components. prdlk/cs publishes it with Blume's `mdxRemote()` source, which fetches `.md`/`.mdx` files only. `docs/(0N-)//(problems)/_.mdx` is generated by sync; `(0N-)//index.mdx` (`hidden: true`) is a topic's hand-written landing page, and `index.mdx`/`keywords.mdx`/`progress.mdx` sit at the root. The phase folder is a `(group)` (sidebar section, no route segment), the topic folder is the README campaign topic slug (`tree-dfs/`, `heap-priority-queue/`) and a real route segment, and `(problems)` is a second `(group)` inside it, so `tree-dfs/(problems)/226_invert-binary-tree.mdx` routes to `/tree-dfs/invert-binary-tree` (Blume sorts by the `_` prefix and strips it from the route). `docs/site.json` is sync's generated manifest and the site's ONE other input: `folders` (path under `docs/` → `{ title, order, icon?, display? }`, exactly what a `meta.ts` in that folder would say — every topic `display: "page"`, so it is one sidebar row whose panel holds its landing page and a "Problems" section) and `redirects` (the `/` short links). Images are root-absolute (`![](/topo_intro_1.png)`, ``) and served from the site's `public/` — a page-relative path cannot resolve once the MDX is fetched over HTTP. Frontmatter keys beyond Blume's (`topics:`, `video:`, `issue:`) and the MDX components pages use (``, ``/``, the islands) are the site's to declare — add a new one there before a page uses it | | `apps/api/` | Cloudflare Worker workspace: `src/` modules, `data/schedule.json` (the plan: `date → { topics | problems | review | gate | sweep }`, human-edited, bundled at deploy), `migrations/` (append-only; `0002` is the +3/+7 ladder rebuild) | -| `.github/workflows/` | deploy (docs → Pages with `GITHUB_TOKEN` for the releases-backed changelog, Worker → Cloudflare with the `OPENROUTER_API_KEY` Actions secret put into it first, on every main push), close-solved (issues + `/admin/solved` + Project `Set`/`Difficulty`, needs `SRS_ADMIN_KEY` and `PROJECT_PAT`; then the `release` job: timever tag + release per finished topic, `contents: write`), close-topics, sync-d1 (issue edits → `/admin/reconcile`), spaced-repetition (daily 06:00 ET cron → today's review issue; `GITHUB_TOKEN` only, no secrets) | -| `.githooks/` | Tracked git hooks; `bun install` points `core.hooksPath` here (root `prepare` script, no husky). `pre-commit` runs `bun run sync` and stages only what sync changed (pages, `apps/docs/redirects.ts`), so a solution, its page and its `/` short link land in one commit; it refuses to run while `work/1` has anything unstaged (sync reads the tree, so the page would not match the commit), and leaves pre-existing dirt under `apps/docs/content` (in-progress `## Approach` prose) unstaged. `git commit --no-verify` for a deliberate partial commit | +| `.github/workflows/` | deploy (Worker → Cloudflare with the `OPENROUTER_API_KEY` Actions secret put into it first, on every main push; the docs site deploys from prdlk/cs), close-solved (issues + `/admin/solved` + Project `Set`/`Difficulty`, needs `SRS_ADMIN_KEY` and `PROJECT_PAT`; then the `release` job: timever tag + release per finished topic, `contents: write`), close-topics, sync-d1 (issue edits → `/admin/reconcile`), spaced-repetition (daily 06:00 ET cron → today's review issue; `GITHUB_TOKEN` only, no secrets) | +| `.githooks/` | `bun install` points `core.hooksPath` here (root `prepare` script, no husky). Its `pre-commit` hook (run `bun run sync`, stage only what sync changed — pages and `docs/site.json`) was removed in `e63d517`, so nothing runs on commit today: run `bun run sync` yourself before committing a solution, or its page and short link ship a commit later | ## Development Commands @@ -71,12 +72,11 @@ bun run test # run ONE solution against LeetCode's judge (not a test # most recently tested first, stamped in the db on exit 0) bun run submit # submit ONE solution to LeetCode (newest scaffold first, nothing # hidden; last_submitted stamped in the db on exit 0) -bun run sync # work/1 → apps/docs/content/ pages + apps/docs/redirects.ts (/ short +bun run sync # work/1 → docs/ pages + docs/site.json (folder meta + / short # links; re-solves are never published) bun run videos # docs pages with no `video:` frontmatter → NeetCode video per page # (channel search scraped, no API key; --write adds the # `video:` key; "no NeetCode match" pages stay untouched) -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 release-topics -- --dry-run # prints every would-be topic release body @@ -113,21 +113,20 @@ curl -X POST -H "Authorization: Bearer $LINK_KEY" \ - `apps/api/src/ask.ts` — `POST /ask`, the docs assistant's backend. Blume's own assistant route is server-rendered and cannot ship in the static Pages build (`BLUME_SERVER_FEATURE_REQUIRED`), so `blume.config.ts` sets `ai.assistant.endpoint` to this route instead of a `provider`. It speaks the Blume island's wire contract (`{ messages, page: { path } }` in, a plain-text stream out, a 503 "The assistant is not configured…" when the key is missing), validates like Blume's generated route (1–40 user/assistant messages, 24 000 chars, 64 KB — no caller system prompt), grounds on the published `llms-full.txt` (current page first, then keyword-ranked excerpts in Blume's 6 × 2 000 / 10 000-char budget, cached 5 min), and streams OpenRouter's chat completions over plain `fetch` — no AI SDK — for the `ASK_MODEL` var. `:free` models share an upstream pool and often answer 429, which the island shows as its rate-limit notice. - `apps/api/src/digest.ts` / `apps/api/src/email.tsx` — the daily digest, split data/presentation. `digest.ts` reads D1 into a `DigestData`; `email.tsx` owns every colour and every sentence, and renders both the HTML and (via its own `plainDigest`, not React Email's `plainText` mode, which flattens the tables) the text alternative. The retrieval rules hold by construction: `DigestRow` has no title and no issue field, so a review or drill line *cannot* leak the topic or a solution link. Subject is `(Day N/) LeetCode Daily Digest` — 79 with the plan ending Nov 3; booking a later day in `schedule.json` lengthens it, and the heatmaps and `/api/stats` `heat` with it. - `apps/api/src/png.ts` — hand-rolled PNG encoder (RGB8, one IDAT, zlib via `CompressionStream("deflate")`, CRC32, 5×7 bitmap font). It exists because every major email client refuses remote SVG, so `/chart/heatmap.png` rasters the heatmap for the digest while `/chart/heatmap.svg` keeps serving the README byte-for-byte. Both come from one `heatmapCells()` so the two pictures cannot drift. -- `apps/cli/sync.ts` — work→docs contract: on existing pages only the frontmatter `icon:` and the `## Solution` section are regenerated, and `topics:` (LeetCode's `topicTags` as `{ name, slug }`, fetched by GraphQL) and `issue:` (the URL of the problem's `problem`-labelled GitHub issue, `LC · …`, listed via `github()` — rendered as the neutral `Issue #N` badge after `Explanation`) are filled once — on create, or on any run that finds them missing — never refreshed (delete a key to refetch; offline or without `gh` auth, the page is written without it and the next sync retries; a sync with nothing missing makes no request). Only a problem the README plan lists gets `issue:` — an unplanned warm-up has no catalog issue, so it is never looked up. `## Solution` is the latter bounded by `solutionEnd()` at the next unfenced top-level heading; human prose before it and any sections after it are never touched. Never hand-write solution pages or edit inside `## Solution`. Two classifications, two homes: the sidebar group is the README topic (`readmeTopics()`, first listing wins for a problem under two topics; a warm-up outside the plan groups under its category at `order: 0`, ahead of topic 1) and the `work/1///` category, which only places an unplanned problem — the page's topic badges are LeetCode's tags, not either classification. Topics nest under their README phase (`## Phase N — Name` headings, shortened via `PHASE_ALIASES`): `content/(0N-)//`, with every problem page in the topic's `(problems)/` group as `_.mdx` (a page is found by its frontmatter title, wherever it sits, so a layout change is one sync of `rename`s). The topic `meta.ts` is script-owned and rewritten every run (label, curriculum order, `TOPIC_ICONS` icon, `display: "page"`); the phase `meta.ts` is written once when missing (`Phase N: Name`, `order: N`) and hand-owned after — rename its title freely. Never rewriting it loses nothing: its order and name are also the folder name, so a README change to either lands in a new folder with a fresh meta. The phase folder is a Blume `(group)` — no route segment, so `/tree-dfs` is unchanged and a landing page there is one level deep, never at `/` — and the topic folder is the real segment; never put a topic `index.mdx` directly inside a `(group)`. A page is `rename`d when its topic or phase changes; a folder every page has left is deleted (climbing through an emptied `(problems)` and topic into an emptied phase folder) unless it still holds a hand-written `index.mdx` (sync never writes, moves, or deletes one — so page-relative asset paths in a moved landing page are yours to fix). Slugs come from `slugify()`, which glues a leading number to the next token (`1d-dynamic-programming`) because Blume strips `-` prefixes from routes. The hand-owned parts have their own owners: `## Approach` (a Mermaid flowchart tracing the page's code, placed *before* `## Solution` so a re-sync keeps it) is written per the `problem-approach` skill (`/problem-approach` fills every page missing one); the frontmatter `video:` (the NeetCode walkthrough, rendered as the red `Explanation` badge) is added by `bun run videos --write` (`apps/cli/videos.ts`). +- `apps/cli/sync.ts` — work→docs contract: on existing pages only the frontmatter `icon:` and the `## Solution` section are regenerated, and `topics:` (LeetCode's `topicTags` as `{ name, slug }`, fetched by GraphQL) and `issue:` (the URL of the problem's `problem`-labelled GitHub issue, `LC · …`, listed via `github()` — rendered as the neutral `Issue #N` badge after `Explanation`) are filled once — on create, or on any run that finds them missing — never refreshed (delete a key to refetch; offline or without `gh` auth, the page is written without it and the next sync retries; a sync with nothing missing makes no request). Only a problem the README plan lists gets `issue:` — an unplanned warm-up has no catalog issue, so it is never looked up. `## Solution` is the latter bounded by `solutionEnd()` at the next unfenced top-level heading; human prose before it and any sections after it are never touched. Never hand-write solution pages or edit inside `## Solution`. Two classifications, two homes: the sidebar group is the README topic (`readmeTopics()`, first listing wins for a problem under two topics; a warm-up outside the plan groups under its category at `order: 0`, ahead of topic 1) and the `work/1///` category, which only places an unplanned problem — the page's topic badges are LeetCode's tags, not either classification. Topics nest under their README phase (`## Phase N — Name` headings, shortened via `PHASE_ALIASES`): `docs/(0N-)//`, with every problem page in the topic's `(problems)/` group as `_.mdx` (a page is found by its frontmatter title, wherever it sits, so a layout change is one sync of `rename`s). `docs/site.json` is wholly script-owned and rewritten whenever it differs, because `mdxRemote()` reads no `meta.ts`: its `folders` are what a `meta.ts` per phase and topic folder would carry — a phase `N. Name` at `order: N`, a topic its exact label, curriculum order, `TOPIC_ICONS` icon and `display: "page"` — all derived from the README, so a rename there is a rename on the site; its `redirects` are the `/` → `//` short links. Which tags the site teaches as topics (`topic-landings.ts`) and its Appendix glossary (`appendix.ts`) are the site's, in prdlk/cs: sync no longer checks them. The phase folder is a Blume `(group)` — no route segment, so `/tree-dfs` is unchanged and a landing page there is one level deep, never at `/` — and the topic folder is the real segment; never put a topic `index.mdx` directly inside a `(group)`. A page is `rename`d when its topic or phase changes; a folder every page has left is deleted (climbing through an emptied `(problems)` and topic into an emptied phase folder) unless it still holds a hand-written `index.mdx` (sync never writes, moves, or deletes one). Slugs come from `slugify()`, which glues a leading number to the next token (`1d-dynamic-programming`) because Blume strips `-` prefixes from routes. The hand-owned parts have their own owners: `## Approach` (a Mermaid flowchart tracing the page's code, placed *before* `## Solution` so a re-sync keeps it) is written per the `problem-approach` skill (`/problem-approach` fills every page missing one); the frontmatter `video:` (the NeetCode walkthrough, rendered as the red `Explanation` badge) is added by `bun run videos --write` (`apps/cli/videos.ts`). - `apps/cli/work.ts` — the `work/` layout: bucket paths, `isSolution()`/`solvedInBucket()` (the stub test, shared with `close-solved.ts`), `firstSolved()` (one `git log`, ET dates, keyed by LC number so past layout moves don't matter), and `reviewQueues()` (what each window still owes). Everything that needs to know where a solution file lives, or when it was solved, goes through here. -- `apps/api/src/stats.ts` / `apps/cli/stats.ts` / `apps/cli/tui.ts` / `apps/cli/api.ts` / `apps/docs/lib/stats.ts` — the read side. `buildStats()` is the ONE aggregation: phases, ladder, difficulty, `attempting` (open misses, via `MISS_WHERE` in `srs.ts` — the sweep day's definition), per-topic temperature, gates, the 14-day due queue, `heat` (every campaign day, zeros and future days included — the TUI windows its tail rather than asking a second question), `recent` (the ten problems most recently solved, dated by each one's first pass) and `year` (the 365 days ending today — always the calendar year to date — each split by difficulty into attempts and first solves; the docs cards window and bucket it themselves). Two readers consume it, the docs `/progress` islands and `bun run stats`, so its shape is their contract: change the Worker and both readers together (additive fields a reader ignores are safe). `tui.ts` is hand-rolled ANSI (bars, sparklines, heat cells, alternate screen) on charts.ts's palette, and every chart differs by GLYPH as well as by colour so a piped or `NO_COLOR` dashboard still reads. The TUI recomputes nothing: the only local fact it shows is the `work/` solution count, which is inventory, not schedule. -- `README.md` live charts are Worker endpoints (`/chart/*.svg`, `/badge/gate.svg`, 5-min Camo cache). The docs `/progress` page is five LeetCode-style islands — `SolvedRing` (difficulty gauge + "Attempting"), `SubmissionHeatmap` and `SubmissionChart` sharing one row (the heatmap: the last six months in month blocks, the README's colour ramp as theme.css `--heat-0..4`, its attempts / active days / max streak counted from the cells it draws; the chart: this calendar year as stacked Easy/Med./Hard bars per month or week, first solves or every attempt, via two shadcn `ToggleGroup`s), `TopicBubbles` (the campaign topics circle-packed by `d3-hierarchy`, filled by solved share), `RecentSolves` (the ten most recent, linked through `redirects.ts`) — composed from shadcn/ui (`components/ui/{card,chart,skeleton,toggle,toggle-group}.tsx`, Recharts under `chart`, Radix under the toggles) on Blume's tokens: `apps/docs/theme.css` maps shadcn's names onto `--blume-*` (never `accent`, which is Blume's brand colour — vendored components that use it, like `toggle.tsx`, are retokened to `background`/`muted`) and gives shadcn's `data-slot` parts their border colour. They share `lib/stats.ts` (`useStats()`), so the page makes ONE `/api/stats` request however many islands hydrate. shadcn sources live outside `islands/` (every `.tsx` there becomes an island) and import `lib/utils` relatively; `components.json` + `tsconfig.json` exist for the shadcn CLI and `tsc`, so after `shadcn add` rewrite `@/…` imports to relative ones (house style) — and check `package.json`: the CLI has twice installed an unrelated `cn` package for `lib/utils` and skipped real deps (`class-variance-authority`). Blume turns every tsconfig `paths` target into a Vite alias AND a dev dep-scan glob (`/**/*.astro`…), so `paths` maps only `@/components`, `@/lib`, `@/hooks` — each bare and `/*`, since `components.json` names the bare forms — never `@/*` → `./*`: that glob reaches through `node_modules/blume`, where Vite's scanner reads a ` diff --git a/apps/docs/components/CarouselImage.astro b/apps/docs/components/CarouselImage.astro deleted file mode 100644 index 883d946..0000000 --- a/apps/docs/components/CarouselImage.astro +++ /dev/null @@ -1,45 +0,0 @@ ---- -/** - * One slide of a : Blume's own around an , so a slide - * is pixel-identical to a framed image elsewhere on the site — `alt` doubles - * as the frame's hint line and `caption` is Markdown, exactly as in Frame. - * - * `src` accepts what MDX authors already write for images: a URL, a - * root-relative `/file.png` (served from public/, prefixed with the deploy - * base), or the page-relative `../../public/file.png` form. A relative path - * that does not pass through public/ has nothing to serve it, so it is a - * build error rather than a silent 404. - */ -import Frame from "blume/components/content/Frame.astro"; -import { withBase } from "blume/components/islands/base-path.ts"; - -interface Props { - src: string; - alt?: string; - caption?: string; -} - -const { src, alt, caption } = Astro.props; - -const resolveSrc = (value: string): string => { - if (URL.canParse(value)) return value; - if (value.startsWith("/")) return withBase(value); - const at = value.indexOf("public/"); - if (at === -1) { - throw new Error( - `: a relative src must point into public/ (or use /file.png)` - ); - } - return withBase(`/${value.slice(at + "public/".length)}`); -}; ---- - - - - {alt - - diff --git a/apps/docs/components/ProblemBadges.astro b/apps/docs/components/ProblemBadges.astro deleted file mode 100644 index 0790f7d..0000000 --- a/apps/docs/components/ProblemBadges.astro +++ /dev/null @@ -1,134 +0,0 @@ ---- -/** - * The MDX `wrapper`: the badge row between a page's description and its body. - * Blume's page template renders the title and description itself and then - * the MDX content, with no slot in between, so wrapping the content is the - * only way to land right after the description. MDX hands a wrapper no - * route, so it is read off the URL, and the page's frontmatter is then - * looked up through the route manifest — the same `getEntry` call Blume's own - * page template makes. Pages with none of the keys below (landing pages, the - * changelog) render only their content. - * - * - `topics:` (problem pages, written by `apps/cli/sync.ts`) is LeetCode's own - * topic tags. A tag the campaign teaches (`topic-landings.ts`) links to that - * topic's landing page and is filled; any other tag links to its card on the - * Appendix glossary, `/appendix#` (`appendix.ts`), and is outlined. - * `tagTarget()` picks the route (and between two landings, e.g. DFS); the - * `PageFooter` backlinks list and the Appendix page read the same answer. - * Each tag badge is a pill carrying the icon its target shows (a topic's - * `meta.ts` icon in the sidebar, a term's `icon` on its card) and the tag's - * own colour (`lib/tag-colors.ts`), assigned over every tag the site has a - * page or card for — no two tags share one, and tags that share problem - * pages are spread furthest apart. A tag with neither (the LeetCode-list - * fallback) stays gray and iconless. - * - `video:` (problem pages, written by `apps/cli/videos.ts`) is the NeetCode - * walkthrough's YouTube id, the red "Explanation" badge. - * - `issue:` (problem pages, written by sync) is the problem's GitHub issue - * URL, a neutral "Issue #N" badge with the GitHub mark, right after - * "Explanation" at the end of the row. - */ -import { getEntry } from "astro:content"; -import data from "blume:data"; -import Badge from "blume/components/content/Badge.astro"; -import { stripBase, withBase } from "blume/components/islands/base-path.ts"; -import { APPENDIX } from "../appendix.ts"; -import { tagColors } from "../lib/tag-colors.ts"; -import { TOPIC_LANDINGS, tagTarget, type TopicTag } from "../topic-landings.ts"; - -// `/sub/tree-dfs/invert-binary-tree/` (or `.../index.html` in a static build) -// -> `/tree-dfs/invert-binary-tree`, the base-less shape of `data.routes`. -const route = - stripBase(import.meta.env.BASE_URL ?? "/", Astro.url.pathname) - .replace(/(\/index)?\.html$/u, "") - .replace(/(.)\/+$/u, "$1"); -const at = data.routes.find((r: { path: string }) => r.path === route); -const entry = at ? await getEntry(at.collection, at.entryId) : undefined; -const tags: TopicTag[] = entry?.data.topics ?? []; -const video: string | undefined = entry?.data.video; -const issue: string | undefined = entry?.data.issue; - -const served = new Set(data.routes.map((r: { path: string }) => r.path)); - -// route → the icon its sidebar row shows: a page's own, or a topic group's -// (from its `meta.ts`) for the landing page the group row links to. -interface NavNode { - route?: string; - icon?: string; - children?: NavNode[]; -} -const icons = new Map(); -const walk = (nodes: NavNode[]) => { - for (const node of nodes) { - if (node.route && node.icon && !icons.has(node.route)) icons.set(node.route, node.icon); - if (node.children) walk(node.children); - } -}; -walk(data.navigation.sidebar); - -// Every tag with a landing or an Appendix card, coloured against how problem -// pages (`. ` titles, the shape sync writes) combine them. Only a -// page with tags needs the answer. -const colors = tags.length - ? tagColors( - [...Object.keys(TOPIC_LANDINGS), ...Object.keys(APPENDIX)], - await Promise.all( - data.routes - .filter((r: { title: string }) => /^\d+\.\s/u.test(r.title)) - .map(async (r: { collection: string; entryId: string }) => { - const page = await getEntry(r.collection, r.entryId); - return (page?.data.topics ?? []).map((t: TopicTag) => t.slug); - }), - ), - ) - : new Map(); - -const badges = tags.map((tag) => { - const target = tagTarget(tag, { route, tags }, served); - const external = target.kind === "external"; - return { - name: tag.name, - href: external ? target.href : withBase(target.href), - external, - icon: target.kind === "appendix" ? APPENDIX[tag.slug]?.icon : external ? undefined : icons.get(target.href), - color: external ? undefined : colors.get(tag.slug), - stroke: target.kind !== "landing", - }; -}); - -const linkClass = "no-underline transition-opacity hover:opacity-80"; ---- - -{ - (badges.length > 0 || video || issue) && ( -
- {badges.map((b) => ( - - - {b.name} - - - ))} - {video && ( - - - Explanation - - - )} - {issue && ( - - - Issue #{issue.slice(issue.lastIndexOf("/") + 1)} - - - )} -
- ) -} - - diff --git a/apps/docs/components/progress/shared.tsx b/apps/docs/components/progress/shared.tsx deleted file mode 100644 index b412d2b..0000000 --- a/apps/docs/components/progress/shared.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/** - * What the four progress islands share besides the data: the not-ready body - * (a skeleton while /api/stats loads, one muted line if it fails) and the - * hover tooltip for the two charts drawn as plain SVG (the heatmap calendar - * and the topic bubbles — Recharts has neither layout). The tooltip wears the - * exact classes of shadcn's ChartTooltipContent, so all four cards' tooltips - * look like one component. - */ -import { type PointerEvent, type ReactNode, useRef, useState } from "react"; -import { cn } from "../../lib/utils"; -import type { Load } from "../../lib/stats"; -import { Skeleton } from "../ui/skeleton"; - -export function StatsPending({ load, className }: { load: Exclude; className: string }) { - if (load.phase === "loading") return ; - return ( -

- Stats unavailable — they are served live from the SRS Worker, and it did not respond. -

- ); -} - -interface Tip { - x: number; - y: number; - content: ReactNode; -} - -/** - * A pointer-following tooltip for SVG marks. Put `ref` on a `relative` - * wrapper, call `show` from a mark's pointer events, render `tip` inside the - * wrapper. Positions are relative to the wrapper, so page scroll never skews it. - */ -export function useHoverTip() { - const ref = useRef(null); - const [tip, setTip] = useState(null); - const show = (event: PointerEvent, content: ReactNode) => { - const box = ref.current?.getBoundingClientRect(); - if (box) setTip({ x: event.clientX - box.left, y: event.clientY - box.top, content }); - }; - const hide = () => setTip(null); - const node = tip && ( -
- {tip.content} -
- ); - return { ref, show, hide, node }; -} - -/** One tooltip row, the shape ChartTooltipContent draws: colour dot, label, mono value. */ -export function TipRow({ color, label, value }: { color: string; label: ReactNode; value: ReactNode }) { - return ( -
-
-
- {label} - {value} -
-
- ); -} diff --git a/apps/docs/components/ui/card.tsx b/apps/docs/components/ui/card.tsx deleted file mode 100644 index c2909e0..0000000 --- a/apps/docs/components/ui/card.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import * as React from "react" -import { cn } from "../../lib/utils" - -function Card({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardHeader({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardTitle({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardDescription({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardAction({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardContent({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardFooter({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -export { - Card, - CardHeader, - CardFooter, - CardTitle, - CardAction, - CardDescription, - CardContent, -} diff --git a/apps/docs/components/ui/chart.tsx b/apps/docs/components/ui/chart.tsx deleted file mode 100644 index e834ffb..0000000 --- a/apps/docs/components/ui/chart.tsx +++ /dev/null @@ -1,372 +0,0 @@ -import * as React from "react" -import { cn } from "../../lib/utils" -import * as RechartsPrimitive from "recharts" -import type { TooltipValueType } from "recharts" - -// Format: { THEME_NAME: CSS_SELECTOR }. Blume switches dark mode with -// [data-theme="dark"] on , not shadcn's .dark class. -const THEMES = { light: "", dark: ':root[data-theme="dark"]' } as const - -const INITIAL_DIMENSION = { width: 320, height: 200 } as const -type TooltipNameType = number | string - -export type ChartConfig = Record< - string, - { - label?: React.ReactNode - icon?: React.ComponentType - } & ( - | { color?: string; theme?: never } - | { color?: never; theme: Record } - ) -> - -type ChartContextProps = { - config: ChartConfig -} - -const ChartContext = React.createContext(null) - -function useChart() { - const context = React.useContext(ChartContext) - - if (!context) { - throw new Error("useChart must be used within a ") - } - - return context -} - -function ChartContainer({ - id, - className, - children, - config, - initialDimension = INITIAL_DIMENSION, - ...props -}: React.ComponentProps<"div"> & { - config: ChartConfig - children: React.ComponentProps< - typeof RechartsPrimitive.ResponsiveContainer - >["children"] - initialDimension?: { - width: number - height: number - } -}) { - const uniqueId = React.useId() - const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}` - - return ( - -
- - - {children} - -
-
- ) -} - -const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { - const colorConfig = Object.entries(config).filter( - ([, config]) => config.theme ?? config.color - ) - - if (!colorConfig.length) { - return null - } - - return ( - - - ); -} diff --git a/apps/docs/islands/RecentSolves.tsx b/apps/docs/islands/RecentSolves.tsx deleted file mode 100644 index c449cf9..0000000 --- a/apps/docs/islands/RecentSolves.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/** - * The ten most recently solved problems — LeetCode's "Recent AC" list. Each - * row is dated by the problem's first pass (an ET calendar date: the Worker - * logs days, not times), read relative to today in ET with the full date on - * hover. A row links to the problem's docs page when one exists: the - * sync-generated short links (`redirects.ts`) are exactly the pages built. - */ -import { StatsPending } from "../components/progress/shared"; -import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/card"; -import { useStats, utcDate } from "../lib/stats"; -import redirects from "../redirects"; - -export const client = "load"; - -const PAGES = new Map(redirects.map((r) => [r.from, r.to])); - -const ET_TODAY = new Intl.DateTimeFormat("en-CA", { timeZone: "America/New_York" }); -const RELATIVE = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); -const FULL = new Intl.DateTimeFormat("en-US", { dateStyle: "medium", timeZone: "UTC" }); -const DAY_MS = 86_400_000; - -export default function RecentSolves() { - const load = useStats(); - - if (load.phase !== "ready") { - return ( - - - Recently solved - - - - - - ); - } - - const today = utcDate(ET_TODAY.format(new Date())).getTime(); - - return ( - - - Recently solved - - - {load.stats.recent.length === 0 ? ( -

Nothing solved yet.

- ) : ( -
    - {load.stats.recent.map((p) => { - const date = utcDate(p.date); - const page = PAGES.get(`/${p.lc}`); - const name = ( - <> - {p.lc}. {p.title} - - ); - return ( -
  1. - {page ? ( - - {name} - - ) : ( - {name} - )} - -
  2. - ); - })} -
- )} -
-
- ); -} diff --git a/apps/docs/islands/SolvedRing.tsx b/apps/docs/islands/SolvedRing.tsx deleted file mode 100644 index e04000b..0000000 --- a/apps/docs/islands/SolvedRing.tsx +++ /dev/null @@ -1,145 +0,0 @@ -/** - * Solved-by-difficulty ring — LeetCode's profile gauge. A 270° arc split into - * one segment per difficulty, each sized by that difficulty's share of the - * catalog; the faint track is the whole set, the solid arc the solved part. - * The centre reads the total, or one difficulty while its segment (or its - * stat box) is hovered. "Attempting" is the open-miss count: problems whose - * latest attempt failed. - */ -import { useState } from "react"; -import { Pie, PieChart } from "recharts"; -import { StatsPending } from "../components/progress/shared"; -import { Card, CardContent } from "../components/ui/card"; -import { type ChartConfig, ChartContainer } from "../components/ui/chart"; -import { DIFFICULTIES, type Difficulty, useStats } from "../lib/stats"; -import { cn } from "../lib/utils"; - -export const client = "load"; - -const chartConfig = { - easy: { label: "Easy", color: "var(--chart-1)" }, - medium: { label: "Med.", color: "var(--chart-2)" }, - hard: { label: "Hard", color: "var(--chart-3)" }, -} satisfies ChartConfig; - -// Static class names, so Tailwind's scanner sees every one (the chart's -// --color-* variables exist only inside ChartContainer). -const DIFFICULTY_TEXT: Record = { - easy: "text-chart-1", - medium: "text-chart-2", - hard: "text-chart-3", -}; - -// The gauge opens at the bottom: from 225° (lower left) clockwise to -45° -// (lower right), in Recharts' counter-clockwise-from-3-o'clock degrees. -const START = 225; -const SWEEP = 270; -const GAP = 8; - -export default function SolvedRing() { - const load = useStats(); - const [focus, setFocus] = useState(null); - - if (load.phase !== "ready") { - return ( - - - - - - ); - } - - const rows = DIFFICULTIES.map( - (difficulty) => load.stats.difficulty.find((d) => d.difficulty === difficulty) ?? { difficulty, total: 0, done: 0 }, - ); - const total = rows.reduce((n, r) => n + r.total, 0); - const done = rows.reduce((n, r) => n + r.done, 0); - const span = SWEEP - GAP * (rows.length - 1); - - const segments: ((typeof rows)[number] & { from: number; to: number; solvedTo: number })[] = []; - let cursor = START; - for (const r of rows) { - const size = total > 0 ? (span * r.total) / total : 0; - const solved = r.total > 0 ? size * (r.done / r.total) : 0; - segments.push({ ...r, from: cursor, to: cursor - size, solvedTo: cursor - solved }); - cursor -= size + GAP; - } - - const focused = focus ? rows.find((r) => r.difficulty === focus) : undefined; - const arc = { innerRadius: "80%", outerRadius: "88%", cornerRadius: 99, stroke: "none", dataKey: "value" } as const; - - return ( - - -
- - - {segments.map((s) => ( - setFocus(s.difficulty)} - onMouseLeave={() => setFocus(null)} - /> - ))} - {segments - .filter((s) => s.done > 0) - .map((s) => ( - setFocus(s.difficulty)} - onMouseLeave={() => setFocus(null)} - /> - ))} - - -
-
- {focused ? focused.done : done} - /{focused ? focused.total : total} -
- - {focused ? chartConfig[focused.difficulty].label : "Solved"} - -
-
- {load.stats.attempting} Attempting -
-
-
- {rows.map((r) => ( -
setFocus(r.difficulty)} - onPointerLeave={() => setFocus(null)} - > - - {chartConfig[r.difficulty].label} - - - {r.done}/{r.total} - -
- ))} -
-
-
- ); -} diff --git a/apps/docs/islands/SubmissionChart.tsx b/apps/docs/islands/SubmissionChart.tsx deleted file mode 100644 index d247ed0..0000000 --- a/apps/docs/islands/SubmissionChart.tsx +++ /dev/null @@ -1,222 +0,0 @@ -/** - * This calendar year by difficulty — LeetCode's solved/submissions bar chart, - * sharing its row with the heatmap. Stacked Easy / Med. / Hard bars per month - * or per week (Monday-start, like the heatmap rows); "Solved" counts each - * problem once, on its first-pass date (the date `recent` uses), while - * "Submissions" counts every logged attempt. The totals under the chart are - * the year's, in the selected mode. Buckets are cut from `year`, which always - * covers January 1 to today. - */ -import { useState } from "react"; -import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"; -import { StatsPending } from "../components/progress/shared"; -import { Card, CardAction, CardContent, CardFooter, CardHeader } from "../components/ui/card"; -import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "../components/ui/chart"; -import { ToggleGroup, ToggleGroupItem } from "../components/ui/toggle-group"; -import { DIFFICULTIES, type Day, type Difficulty, useStats, utcDate } from "../lib/stats"; - -export const client = "load"; - -const chartConfig = { - easy: { label: "Easy", color: "var(--chart-1)" }, - medium: { label: "Med.", color: "var(--chart-2)" }, - hard: { label: "Hard", color: "var(--chart-3)" }, -} satisfies ChartConfig; - -// Static class names, so Tailwind's scanner sees every one. -const DIFFICULTY_TEXT: Record = { - easy: "text-chart-1", - medium: "text-chart-2", - hard: "text-chart-3", -}; - -type Mode = "solved" | "attempts"; -type Grain = "month" | "week"; - -const DAY_MS = 86_400_000; -const MONTH = new Intl.DateTimeFormat("en-US", { month: "short", timeZone: "UTC" }); -const MONTH_YEAR = new Intl.DateTimeFormat("en-US", { month: "short", year: "numeric", timeZone: "UTC" }); -const SHORT = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", timeZone: "UTC" }); - -interface Bucket extends Record { - index: number; - /** Axis tick: the month the bucket starts in. */ - tick: string; - /** Tooltip heading. */ - label: string; -} - -/** The year's buckets, every one present (future ones empty), filled from `days`. */ -function buckets(days: Day[], year: number, grain: Grain, mode: Mode): Bucket[] { - const jan1 = Date.UTC(year, 0, 1); - const lead = (new Date(jan1).getUTCDay() + 6) % 7; // Jan 1's Monday-based weekday - const length = Math.round((Date.UTC(year + 1, 0, 1) - jan1) / DAY_MS); - const out: Bucket[] = []; - if (grain === "month") { - for (let m = 0; m < 12; m++) { - const start = new Date(Date.UTC(year, m, 1)); - out.push({ index: m, tick: MONTH.format(start), label: MONTH_YEAR.format(start), easy: 0, medium: 0, hard: 0 }); - } - } else { - const weeks = Math.floor((length - 1 + lead) / 7) + 1; - for (let w = 0; w < weeks; w++) { - const start = new Date(Math.max(jan1, jan1 + (w * 7 - lead) * DAY_MS)); - const end = new Date(Math.min(jan1 + (length - 1) * DAY_MS, jan1 + (w * 7 - lead + 6) * DAY_MS)); - out.push({ - index: w, - tick: MONTH.format(start), - label: `${SHORT.format(start)} – ${SHORT.format(end)}`, - easy: 0, - medium: 0, - hard: 0, - }); - } - } - for (const day of days) { - const date = utcDate(day.date); - if (date.getUTCFullYear() !== year) continue; - const i = grain === "month" ? date.getUTCMonth() : Math.floor(((date.getTime() - jan1) / DAY_MS + lead) / 7); - const bucket = out[i]; - if (!bucket) continue; - for (const d of DIFFICULTIES) bucket[d] += day[mode][d]; - } - return out; -} - -export default function SubmissionChart() { - const load = useStats(); - const [mode, setMode] = useState("solved"); - const [grain, setGrain] = useState("month"); - - if (load.phase !== "ready") { - return ( - - - - - - ); - } - - const year = utcDate(load.stats.year.at(-1)?.date ?? "1970-01-01").getUTCFullYear(); - const data = buckets(load.stats.year, year, grain, mode); - const totals = Object.fromEntries( - DIFFICULTIES.map((d) => [d, data.reduce((n, b) => n + b[d], 0)]), - ); - const peak = Math.max(...data.map((b) => b.easy + b.medium + b.hard)); - // A round ceiling for the one labelled gridline, as LeetCode draws it. - const top = peak <= 5 ? 5 : Math.ceil(peak / 10) * 10; - - return ( - - - value && setMode(value as Mode)} - className="rounded-full bg-muted p-0.5" - aria-label="Count" - > - - Solved - - - Submissions - - -
- value && setGrain(value as Grain)} - className="rounded-full bg-muted p-0.5" - aria-label="Bucket size" - > - - M - - - W - - -
-
- - - - - ( - - {data[Number(payload.value)]?.tick} - - )} - /> - - ( - items[0]?.payload?.label} - indicator="dot" - /> - )} - /> - {(["hard", "medium", "easy"] as const).map((d) => ( - - ))} - - - - - {DIFFICULTIES.map((d) => ( - - {chartConfig[d].label} - {totals[d]} - - ))} - {year} - -
- ); -} diff --git a/apps/docs/islands/SubmissionHeatmap.tsx b/apps/docs/islands/SubmissionHeatmap.tsx deleted file mode 100644 index 2619e81..0000000 --- a/apps/docs/islands/SubmissionHeatmap.tsx +++ /dev/null @@ -1,165 +0,0 @@ -/** - * The last six months of attempts — LeetCode's calendar heatmap, halved to - * share its row with the submissions chart: one cell per day, weeks as - * columns, each month its own block, and the window's attempt count, active - * days and longest streak above it. The figures are counted from exactly the - * cells drawn, so they cannot disagree with the grid. The colour ramp is the - * README/email heatmap's (theme.css `--heat-0..4`, level = attempts capped at - * 4) and rows start on Monday like it, so the three heatmaps read the same. - * Drawn as SVG in a viewBox, so it scales to its column instead of scrolling. - */ -import { StatsPending, TipRow, useHoverTip } from "../components/progress/shared"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card"; -import { type Day, useStats, utcDate } from "../lib/stats"; - -export const client = "load"; - -const MONTHS = 6; -const CELL = 10; -const STEP = 13; // cell + gap -const MONTH_GAP = 8; -const LABEL_H = 16; -const HEAT = ["var(--heat-0)", "var(--heat-1)", "var(--heat-2)", "var(--heat-3)", "var(--heat-4)"]; - -const MONTH = new Intl.DateTimeFormat("en-US", { month: "short", timeZone: "UTC" }); -const DAY = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" }); - -interface Cell { - x: number; - y: number; - date: Date; - attempts: number; - passes: number; -} - -interface Block { - label: string; - x: number; - width: number; - cells: Cell[]; -} - -/** - * Month blocks laid left to right over the last MONTHS calendar months. Within - * a block a day sits at row = weekday (Mon = 0) and column = its week counted - * from the 1st, so a month starting on a Thursday opens with a short column. - */ -function layout(year: Day[]): { blocks: Block[]; width: number } { - const last = utcDate(year.at(-1)?.date ?? "1970-01-01"); - const from = Date.UTC(last.getUTCFullYear(), last.getUTCMonth() - (MONTHS - 1), 1); - const blocks: Block[] = []; - let x = 0; - for (const day of year) { - const date = utcDate(day.date); - if (date.getTime() < from) continue; - let block = blocks.at(-1); - if (date.getUTCDate() === 1 || !block) { - if (block) x += block.width + MONTH_GAP; - block = { label: MONTH.format(date), x, width: 0, cells: [] }; - blocks.push(block); - } - const first = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1)); - const index = (first.getUTCDay() + 6) % 7 + date.getUTCDate() - 1; - const col = Math.floor(index / 7); - block.cells.push({ - x: x + col * STEP, - y: (index % 7) * STEP, - date, - attempts: day.attempts.easy + day.attempts.medium + day.attempts.hard, - passes: day.passes, - }); - block.width = col * STEP + CELL; - } - const end = blocks.at(-1); - return { blocks, width: end ? end.x + end.width : 0 }; -} - -export default function SubmissionHeatmap() { - const load = useStats(); - const { ref, show, hide, node } = useHoverTip(); - - if (load.phase !== "ready") { - return ( - - - - - - ); - } - - const { blocks, width } = layout(load.stats.year); - const cells = blocks.flatMap((b) => b.cells); - let run = 0; - let streak = 0; - for (const c of cells) { - run = c.attempts > 0 ? run + 1 : 0; - streak = Math.max(streak, run); - } - const height = 7 * STEP - (STEP - CELL) + LABEL_H; - - return ( - - - - {cells.reduce((n, c) => n + c.attempts, 0)} - attempts in the past {MONTHS} months - - - - Active days:{" "} - {cells.filter((c) => c.attempts > 0).length} - - - Max streak: {streak} - - - - -
- - {blocks.map((block) => ( - - {block.cells.map((cell) => ( - - show( - event, - <> -
{DAY.format(cell.date)}
- - - , - ) - } - /> - ))} - - {block.label} - - - ))} - - {node} -
-
-
- ); -} diff --git a/apps/docs/islands/TopicBubbles.tsx b/apps/docs/islands/TopicBubbles.tsx deleted file mode 100644 index a75f754..0000000 --- a/apps/docs/islands/TopicBubbles.tsx +++ /dev/null @@ -1,139 +0,0 @@ -/** - * The campaign's topics as packed bubbles — LeetCode's "All Topics" view, - * over our curriculum instead of LeetCode's tags. A bubble's area is the - * topic's problem count; it fills from the bottom like a glass as problems - * are solved, and turns solid once every one is. A topic the schedule has not - * reached yet is dimmed. Packing is d3-hierarchy's front-chain (packSiblings, - * largest first, so the layout is deterministic), drawn as SVG in a viewBox: - * Recharts has no packed layout. - */ -import { packEnclose, packSiblings } from "d3-hierarchy"; -import { useId } from "react"; -import { StatsPending, TipRow, useHoverTip } from "../components/progress/shared"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card"; -import { type Stats, useStats } from "../lib/stats"; -import { cn } from "../lib/utils"; - -export const client = "load"; - -const PAD = 2; // gap between bubbles, in layout units -const UNIT = 12; // radius of a one-problem bubble - -type Topic = Stats["topics"][number]; -type Bubble = Topic & { x: number; y: number; r: number }; - -/** Split a name across lines of at most ~10 characters, never breaking a word. */ -function lines(name: string): string[] { - const out: string[] = []; - for (const word of name.split(" ")) { - const last = out.at(-1); - if (last !== undefined && `${last} ${word}`.length <= 10) out[out.length - 1] = `${last} ${word}`; - else out.push(word); - } - return out; -} - -export default function TopicBubbles() { - const load = useStats(); - const { ref, show, hide, node } = useHoverTip(); - const clip = useId().replace(/:/g, ""); - - if (load.phase !== "ready") { - return ( - - - Topics - - - - - - ); - } - - const topics = load.stats.topics.filter((t) => t.total > 0); - const circles: Bubble[] = topics - .slice() - .sort((a, b) => b.total - a.total || a.issue - b.issue) - .map((t) => ({ ...t, x: 0, y: 0, r: UNIT * Math.sqrt(t.total) + PAD })); - packSiblings(circles); - const bound = packEnclose(circles) ?? { x: 0, y: 0, r: 1 }; - const solved = topics.reduce((n, t) => n + t.done, 0); - const total = topics.reduce((n, t) => n + t.total, 0); - - return ( - - - Topics - - {solved} of {total} problems across {topics.length} topics · size is problem count, fill is solved - - - -
- - {circles.map((c) => { - const r = c.r - PAD; - const ratio = c.total > 0 ? c.done / c.total : 0; - const complete = c.done >= c.total; - const words = lines(c.name); - const size = Math.min(r / 3.2, (1.7 * r) / Math.max(...words.map((w) => w.length))); - const id = `${clip}-${c.issue}`; - return ( - - show( - event, - <> -
{c.name}
- - {!c.taught &&
Not reached yet
} - , - ) - } - > - - - - - - - {words.map((w, i) => ( - - {w} - - ))} - - - ); - })} - - {node} -
-
-
- ); -} diff --git a/apps/docs/lib/env.d.ts b/apps/docs/lib/env.d.ts deleted file mode 100644 index 1943ad1..0000000 --- a/apps/docs/lib/env.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Islands and the Blume modules they import (blume/components/islands/ -// base-path.ts) are bundled by Vite, which provides import.meta.env. Vite is -// Blume's dependency, not this workspace's, so its client types are not -// resolvable from here; this mirrors the slice of them (ImportMetaEnv) tsc needs. -interface ImportMeta { - readonly env: { - readonly [key: string]: unknown; - readonly BASE_URL: string; - readonly MODE: string; - readonly DEV: boolean; - readonly PROD: boolean; - readonly SSR: boolean; - }; -} diff --git a/apps/docs/lib/releases.ts b/apps/docs/lib/releases.ts deleted file mode 100644 index 636c2f0..0000000 --- a/apps/docs/lib/releases.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * The `/changelog` page's data: every timever release apps/cli/release-topics.ts - * published — one per finished topic and per finished `Review ` issue — - * read from the GitHub API at build time (pages/changelog.astro awaits it). - * - * One page, no per-release subpages: each release becomes one timeline card, - * so the fields come from the release itself rather than a staged content - * entry. The date is the timever tag (`YYYYMMDD.HHMMSS.NNNNNNNNN`, UTC — the - * instant the last required problem closed), never `published_at`, which the - * API cannot backdate and which reads the backfill day for every old release. - * The problems are the rows of the body's `### Closed …` tables, the shape - * release-topics.ts's render() writes: `| #issue | LC n · Name | set | diff | - * closed | [py](…) · [docs](…) |`. Only `### Closed …` sections are read, so - * a release published before open problems were dropped from the body still - * shows only what finished. The row's own `[docs]` link is not used: it is - * frozen at release time (a problem solved before the docs existed has - * none), while the page links each problem by its LC number to its page now. - * - * Auth: GITHUB_TOKEN when set (deploy.yml passes it), else unauthenticated — - * one request per 100 releases fits the anonymous limit. A failed request - * fails the build: a changelog silently missing its releases is worse. - */ - -const REPO = "prdlk/leetcode"; -const MARKER = //u; -const TIMEVER = /^(\d{4})(\d{2})(\d{2})\.(\d{2})(\d{2})(\d{2})\.\d{9}$/u; -const KIND = /^(Topic|Review) \d+ — /u; - -export interface ReleaseProblem { - issue: number; - /** The LeetCode number, the key of the docs `/` short links. */ - lc: number; - /** `LC 57 · Insert Interval`. */ - title: string; - set: string; - /** `easy` / `medium` / `hard`, the problem's `diff:*` label. */ - difficulty: string; -} - -export interface Release { - /** Topic or Review issue the release records. */ - issue: number; - name: string; - kind: "Topic" | "Review"; - /** UTC instant the last required problem closed, from the timever tag. */ - finishedAt: Date; - url: string; - problems: ReleaseProblem[]; -} - -/** Rows of every `### Closed …` table in a release body. */ -function closedProblems(body: string): ReleaseProblem[] { - const problems: ReleaseProblem[] = []; - let closed = false; - for (const line of body.split("\n")) { - if (line.startsWith("### ")) closed = line.startsWith("### Closed"); - if (!closed || !line.startsWith("| #")) continue; - const [issue, title, set, difficulty] = line.slice(1, -1).split("|").map((cell) => cell.trim()); - const lc = title?.match(/^LC (\d+) · /u)?.[1]; - if (!issue || !title || !lc || !set || !difficulty) continue; - problems.push({ issue: Number(issue.slice(1)), lc: Number(lc), title, set, difficulty }); - } - return problems; -} - -export async function readReleases(): Promise { - const headers: Record = { accept: "application/vnd.github+json" }; - if (process.env.GITHUB_TOKEN) headers.authorization = `Bearer ${process.env.GITHUB_TOKEN}`; - const releases: Release[] = []; - for (let page = 1; ; page++) { - const res = await fetch(`https://api.github.com/repos/${REPO}/releases?per_page=100&page=${page}`, { headers }); - if (!res.ok) throw new Error(`GET ${REPO} releases -> ${res.status} ${await res.text()}`); - const batch = (await res.json()) as { tag_name: string; name: string | null; body: string | null; html_url: string }[]; - for (const r of batch) { - const issue = r.body?.match(MARKER)?.[1]; - const t = r.tag_name.match(TIMEVER); - const kind = r.name?.match(KIND)?.[1]; - if (!issue || !t || !kind || !r.name || !r.body) continue; - releases.push({ - issue: Number(issue), - name: r.name, - kind: kind as Release["kind"], - finishedAt: new Date(`${t[1]}-${t[2]}-${t[3]}T${t[4]}:${t[5]}:${t[6]}Z`), - url: r.html_url, - problems: closedProblems(r.body), - }); - } - if (batch.length < 100) break; - } - return releases.sort((a, b) => b.finishedAt.getTime() - a.finishedAt.getTime()); -} diff --git a/apps/docs/lib/stats.ts b/apps/docs/lib/stats.ts deleted file mode 100644 index 025a9b0..0000000 --- a/apps/docs/lib/stats.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * The progress page's data: GET /api/stats from the SRS Worker, fetched in the - * browser so `blume build` never depends on the API being up. API_BASE is the - * deployed Worker — do not inline it anywhere else. - * - * Four islands read this (SolvedRing, SubmissionHeatmap, RecentSolves, - * TopicBubbles). Each hydrates on its own, but they share this module, so the - * one in-flight request is shared too: the page asks the Worker once. - * - * Invariants: a malformed body is an error state, never a throw; the first - * frame is always the loading state, so server and client render identically. - */ -import { useEffect, useState } from "react"; - -const API_BASE = "https://srs-api.prdlk.workers.dev"; - -// ── /api/stats response shape (the fields these islands read) ──── - -export const DIFFICULTIES = ["easy", "medium", "hard"] as const; - -export type Difficulty = (typeof DIFFICULTIES)[number]; - -export interface Stats { - generated: string; - /** Per difficulty, catalog size and how many entered the ladder. */ - difficulty: { difficulty: Difficulty; total: number; done: number }[]; - /** Problems whose most recent attempt failed — the sweep day's miss log. */ - attempting: number; - topics: { issue: number; name: string; total: number; done: number; taught: boolean }[]; - /** The ten most recently solved, newest first, dated by first pass (ET). */ - recent: { lc: number; title: string; difficulty: Difficulty; date: string }[]; - /** - * The 365 days ending today (ET), zeros included — always the current - * calendar year to date. Per difficulty: every logged attempt, and every - * first solve (the first-pass date `recent` uses). - */ - year: Day[]; -} - -export interface Day { - date: string; - passes: number; - attempts: Record; - solved: Record; -} - -function isStats(value: unknown): value is Stats { - if (typeof value !== "object" || value === null) return false; - const v = value as Record; - return ( - Array.isArray(v.difficulty) && - typeof v.attempting === "number" && - Array.isArray(v.topics) && - Array.isArray(v.recent) && - Array.isArray(v.year) - ); -} - -// ── the shared request ─────────────────────────────────────────── - -export type Load = { phase: "loading" } | { phase: "error" } | { phase: "ready"; stats: Stats }; - -let request: Promise | undefined; - -/** The page's stats, fetched once however many islands ask. */ -export function useStats(): Load { - const [load, setLoad] = useState({ phase: "loading" }); - useEffect(() => { - if (!request) { - request = fetch(`${API_BASE}/api/stats`) - .then((res) => (res.ok ? res.json() : null)) - .then((body: unknown) => (isStats(body) ? body : null)) - .catch(() => null); - } - let live = true; - request.then((stats) => { - if (live) setLoad(stats ? { phase: "ready", stats } : { phase: "error" }); - }); - return () => { - live = false; - }; - }, []); - return load; -} - -/** "YYYY-MM-DD" → a UTC-midnight Date, so calendar math never drifts with the reader's timezone. */ -export function utcDate(iso: string): Date { - const [y, m, d] = iso.split("-").map(Number); - return new Date(Date.UTC(y!, m! - 1, d!)); -} diff --git a/apps/docs/lib/tag-colors.ts b/apps/docs/lib/tag-colors.ts deleted file mode 100644 index 11d8fc0..0000000 --- a/apps/docs/lib/tag-colors.ts +++ /dev/null @@ -1,129 +0,0 @@ -/** - * One colour per topic tag, unique across the site: the problem-page badge - * row (`components/ProblemBadges.astro`) paints each tag with it, so a tag - * reads the same on every page and no two tags share a colour. - * - * Fifty-odd hues cannot all be told apart, so uniqueness alone is not the - * goal: tags that share a problem page must be told apart, since that is - * where they are seen together. The palette is a fixed grid in OKLCH, a few - * lightness rows by `HUE_STEP` hues, and tags are coloured greedily, most-used - * first. Each tag takes the free palette colour farthest (OKLab distance) - * from every colour its co-tags already hold, ties broken by distance from - * every colour taken at all. The answer is a pure function of the pages' - * tags, so it is identical on every page of one build; a newly solved problem - * can shift colours in the next. - * - * Blume's Badge takes a custom colour only as hex and uses that one value in - * both themes, as text over a 12% tint of itself, so the rows sit in the - * narrow lightness band where that text holds ≥ 3:1 contrast on the light and - * the dark background alike; wider rows spread tags further but fail one theme. - */ - -const LIGHTNESS = [0.55, 0.6]; -const CHROMA = 0.15; -const HUE_STEP = 5; - -interface Swatch { - hex: string; - lab: [number, number, number]; -} - -/** - * slug → `#rrggbb`, pairwise distinct, for every slug in `slugs`; `pages` is - * each problem page's tag slugs, the co-occurrence the spread is judged by. - */ -export function tagColors(slugs: Iterable, pages: readonly (readonly string[])[]): Map { - const universe = new Set(slugs); - const uses = new Map(); - const cotags = new Map>(); - for (const page of pages) { - const on = page.filter((slug) => universe.has(slug)); - for (const slug of on) { - uses.set(slug, (uses.get(slug) ?? 0) + 1); - const set = cotags.get(slug) ?? new Set(); - for (const other of on) if (other !== slug) set.add(other); - cotags.set(slug, set); - } - } - const order = [...universe].sort((a, b) => (uses.get(b) ?? 0) - (uses.get(a) ?? 0) || a.localeCompare(b)); - - const palette = swatches(order.length); - const free = new Set(palette.keys()); - const chosen = new Map(); - for (const slug of order) { - const near = [...(cotags.get(slug) ?? [])].flatMap((other) => chosen.get(other) ?? []); - const all = [...chosen.values()]; - let best = -1; - let bestScore: [number, number] = [-1, -1]; - for (const i of free) { - const score: [number, number] = [nearest(palette[i]!, near), nearest(palette[i]!, all)]; - if (score[0] > bestScore[0] || (score[0] === bestScore[0] && score[1] > bestScore[1])) { - best = i; - bestScore = score; - } - } - free.delete(best); - chosen.set(slug, palette[best]!); - } - return new Map([...chosen].map(([slug, swatch]) => [slug, swatch.hex])); -} - -/** Distance from `swatch` to the closest of `others`; Infinity when there are none. */ -function nearest(swatch: Swatch, others: readonly Swatch[]): number { - let min = Number.POSITIVE_INFINITY; - for (const other of others) { - const d = Math.hypot(swatch.lab[0] - other.lab[0], swatch.lab[1] - other.lab[1], swatch.lab[2] - other.lab[2]); - if (d < min) min = d; - } - return min; -} - -/** - * The palette: every `HUE_STEP` degrees in each `LIGHTNESS` row, the step - * shrunk when there are more tags than swatches. Duplicate hexes (possible - * only at a tiny step) are dropped, which keeps every assignment unique. - */ -function swatches(count: number): Swatch[] { - const step = Math.min(HUE_STEP, Math.max(1, Math.floor((360 * LIGHTNESS.length) / Math.max(count, 1)))); - const seen = new Set(); - const out: Swatch[] = []; - for (const lightness of LIGHTNESS) { - for (let hue = 0; hue < 360; hue += step) { - const swatch = oklch(lightness, hue); - if (seen.has(swatch.hex)) continue; - seen.add(swatch.hex); - out.push(swatch); - } - } - return out; -} - -/** - * `oklch(lightness CHROMA hue)` as sRGB hex plus its OKLab coordinates. Hues - * sRGB cannot reach at full chroma (cyans) give up chroma until they fit, - * rather than being clipped per channel, which would shift the hue. - */ -function oklch(lightness: number, hue: number): Swatch { - const rad = (hue * Math.PI) / 180; - for (let chroma = CHROMA; ; chroma -= 0.005) { - const a = chroma * Math.cos(rad); - const b = chroma * Math.sin(rad); - const l = (lightness + 0.3963377774 * a + 0.2158037573 * b) ** 3; - const m = (lightness - 0.1055613458 * a - 0.0638541728 * b) ** 3; - const s = (lightness - 0.0894841775 * a - 1.291485548 * b) ** 3; - const rgb = [ - 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s, - -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s, - -0.0041960863 * l - 0.7034186147 * m + 1.707614681 * s, - ]; - if (chroma > 0 && rgb.some((c) => c < 0 || c > 1)) continue; - const hex = rgb - .map((c) => { - const v = Math.min(1, Math.max(0, c)); - const srgb = v <= 0.0031308 ? 12.92 * v : 1.055 * v ** (1 / 2.4) - 0.055; - return Math.round(srgb * 255).toString(16).padStart(2, "0"); - }) - .join(""); - return { hex: `#${hex}`, lab: [lightness, a, b] }; - } -} diff --git a/apps/docs/lib/utils.ts b/apps/docs/lib/utils.ts deleted file mode 100644 index 2aafffa..0000000 --- a/apps/docs/lib/utils.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { type ClassValue, clsx } from "clsx"; -import { twMerge } from "tailwind-merge"; - -/** shadcn's class combiner: conditional classes, later Tailwind utilities win. */ -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} diff --git a/apps/docs/package.json b/apps/docs/package.json deleted file mode 100644 index 03aae8c..0000000 --- a/apps/docs/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "docs", - "private": true, - "type": "module", - "scripts": { - "dev": "blume dev", - "build": "blume build", - "doctor": "blume doctor" - }, - "dependencies": { - "blume": "^2.1.2", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "d3-hierarchy": "^3.1.2", - "radix-ui": "^1.6.7", - "react": "^19.2.8", - "react-dom": "^19.2.8", - "recharts": "3.8.0", - "tailwind-merge": "^3.7.0" - }, - "devDependencies": { - "@fontsource-variable/inter": "^5.3.0", - "@fontsource/ibm-plex-mono": "^5.3.0", - "@types/d3-hierarchy": "^3.1.7", - "@types/react": "^19.3.0", - "@types/react-dom": "^19.3.0" - } -} diff --git a/apps/docs/pages/appendix.astro b/apps/docs/pages/appendix.astro deleted file mode 100644 index 1727380..0000000 --- a/apps/docs/pages/appendix.astro +++ /dev/null @@ -1,293 +0,0 @@ ---- -/** - * `/appendix`: the glossary of LeetCode topic tags the campaign meets without - * teaching them as a topic (`appendix.ts`), one card per term, filed A–Z under - * a letter index. A card is the target of that tag's badge on every problem - * page (`/appendix#`, from `tagTarget()`), so it carries what the badge - * shows (the term's icon in the tag's colour from `lib/tag-colors.ts`, over - * the same tag universe `ProblemBadges.astro` colours) and lists the problem - * pages whose badge links here: the backlinks a per-term page used to have. - * The filter is a custom element over static markup, the way the Carousel - * is built; with no JS the page is the full glossary. - */ -import { getEntry } from "astro:content"; -import data from "blume:data"; -import Icon from "blume/components/Icon.astro"; -import { withBase } from "blume/components/islands/base-path.ts"; -import PageLayout from "blume/components/layout/PageLayout.astro"; -import { APPENDIX } from "../appendix.ts"; -import { tagColors } from "../lib/tag-colors.ts"; -import { TOPIC_LANDINGS, tagTarget, type TopicTag } from "../topic-landings.ts"; - -const { config } = data; -const served = new Set(data.routes.map((r: { path: string }) => r.path)); - -// Problem pages (`. ` titles, the shape sync writes) with their tags. -const problems = await Promise.all( - data.routes - .filter((r: { title: string }) => /^\d+\.\s/u.test(r.title)) - .map(async (r: { path: string; title: string; collection: string; entryId: string }) => { - const entry = await getEntry(r.collection, r.entryId); - const [, num, name] = r.title.match(/^(\d+)\.\s+(.+)$/u)!; - return { - route: r.path, - num: Number(num), - name: name!, - difficulty: entry?.data.sidebar?.badge as string | undefined, - tags: (entry?.data.topics ?? []) as TopicTag[], - }; - }), -); - -const colors = tagColors( - [...Object.keys(TOPIC_LANDINGS), ...Object.keys(APPENDIX)], - problems.map((p) => p.tags.map((t) => t.slug)), -); - -const terms = Object.entries(APPENDIX) - .map(([slug, term]) => ({ - slug, - ...term, - color: colors.get(slug), - problems: problems - .filter((p) => p.tags.some((tag) => tagTarget(tag, p, served).href === `/appendix#${slug}`)) - .sort((a, b) => a.num - b.num), - })) - .sort((a, b) => a.name.localeCompare(b.name)); - -const sections = new Map(); -for (const term of terms) { - const letter = term.name[0]!.toUpperCase(); - sections.set(letter, [...(sections.get(letter) ?? []), term]); -} -const LETTERS = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZ"]; -// A card's Solved list shows this many rows at a time; a longer one (Array has -// dozens) pages through them with numbered buttons under the list. -const PAGE_SIZE = 5; - -// The /progress cards' difficulty palette (--chart-1..3 in theme.css), as on -// the changelog, so a Hard reads the same colour everywhere. -const DIFFICULTY: Record = { - Easy: { label: "Easy", class: "border-chart-1/40 bg-chart-1/10 text-chart-1" }, - Medium: { label: "Med.", class: "border-chart-2/40 bg-chart-2/10 text-chart-2" }, - Hard: { label: "Hard", class: "border-chart-3/40 bg-chart-3/10 text-chart-3" }, -}; -const description = - "The LeetCode topics the campaign meets without teaching them as a topic: what each one means, and the solved problems that use it."; ---- - - -
-

Appendix

-

{description}

- - -
- - -
- - { - [...sections].map(([letter, group]) => ( -
-

{letter}

-
- {group.map((term) => ( -
-
- - - -
-

- - {term.name} - -

-

- {term.category} -

-
- - LeetCode ↗ - -
-

{term.definition}

- {term.problems.length > 0 && ( -
- - - Solved - {term.problems.length} - - - - - - {term.problems.length > PAGE_SIZE && ( - - )} - -
- )} -
- ))} -
-
- )) - } - -
-
-
- - diff --git a/apps/docs/pages/changelog.astro b/apps/docs/pages/changelog.astro deleted file mode 100644 index d6c8a3c..0000000 --- a/apps/docs/pages/changelog.astro +++ /dev/null @@ -1,120 +0,0 @@ ---- -/** - * `/changelog`: every topic and review release on one page, newest first — a - * vertical timeline, the date on the left and one card per release on the - * right, with no per-release subpages. Replaces Blume's generated changelog - * (a custom page wins its route); the header links here from - * `navigation.actions` in blume.config.ts. Data: lib/releases.ts. - */ -import PageLayout from "blume/components/layout/PageLayout.astro"; -import data from "blume:data"; -import { withBase } from "blume/components/islands/base-path.ts"; -import redirects from "../redirects"; -import { readReleases } from "../lib/releases.ts"; - -const { config } = data; -const releases = await readReleases(); -const DATE = new Intl.DateTimeFormat("en-US", { - timeZone: "America/New_York", - month: "short", - day: "numeric", - year: "numeric", -}); -const issueUrl = (n: number) => `https://github.com/prdlk/leetcode/issues/${n}`; -// LC number -> its docs page route, off sync's `/` short links — the -// page itself, not the redirect, so a click is one hop. A problem with no page -// (none yet) links to its issue instead. -const pages = new Map(redirects.map((r) => [Number(r.from.slice(1)), r.to])); -const problemHref = (lc: number, issue: number) => { - const route = pages.get(lc); - return route ? withBase(route) : issueUrl(issue); -}; -// The /progress cards' difficulty palette (--chart-1..3 in theme.css), so a -// Hard reads the same colour here as in the SolvedRing gauge. -const DIFFICULTY: Record = { - easy: { label: "Easy", class: "border-chart-1/40 bg-chart-1/10 text-chart-1" }, - medium: { label: "Med.", class: "border-chart-2/40 bg-chart-2/10 text-chart-2" }, - hard: { label: "Hard", class: "border-chart-3/40 bg-chart-3/10 text-chart-3" }, -}; -const description = "Every topic and review the campaign finished, newest first."; ---- - - -
-

Changelog

-

{description}

- -
    - { - releases.map((r, i) => ( -
  1. - {/* The rail: a line down the left edge, a ring at each release. */} - {i < releases.length - 1 && ( -
  2. - )) - } -
-
-
diff --git a/apps/docs/public/binary_search.001.png b/apps/docs/public/binary_search.001.png deleted file mode 100644 index 8871ad1..0000000 Binary files a/apps/docs/public/binary_search.001.png and /dev/null differ diff --git a/apps/docs/public/binary_search.002.png b/apps/docs/public/binary_search.002.png deleted file mode 100644 index 93ec3d3..0000000 Binary files a/apps/docs/public/binary_search.002.png and /dev/null differ diff --git a/apps/docs/public/binary_search.003.png b/apps/docs/public/binary_search.003.png deleted file mode 100644 index c3d027e..0000000 Binary files a/apps/docs/public/binary_search.003.png and /dev/null differ diff --git a/apps/docs/public/binary_search.004.png b/apps/docs/public/binary_search.004.png deleted file mode 100644 index 2678eec..0000000 Binary files a/apps/docs/public/binary_search.004.png and /dev/null differ diff --git a/apps/docs/public/binary_search.005.png b/apps/docs/public/binary_search.005.png deleted file mode 100644 index 40ead9d..0000000 Binary files a/apps/docs/public/binary_search.005.png and /dev/null differ diff --git a/apps/docs/public/binary_search.006.png b/apps/docs/public/binary_search.006.png deleted file mode 100644 index 3163ff1..0000000 Binary files a/apps/docs/public/binary_search.006.png and /dev/null differ diff --git a/apps/docs/public/binary_search.007.png b/apps/docs/public/binary_search.007.png deleted file mode 100644 index d509fbf..0000000 Binary files a/apps/docs/public/binary_search.007.png and /dev/null differ diff --git a/apps/docs/public/binary_search.008.png b/apps/docs/public/binary_search.008.png deleted file mode 100644 index e5a7ac6..0000000 Binary files a/apps/docs/public/binary_search.008.png and /dev/null differ diff --git a/apps/docs/public/hash-table-operations.svg b/apps/docs/public/hash-table-operations.svg deleted file mode 100644 index 3823419..0000000 --- a/apps/docs/public/hash-table-operations.svg +++ /dev/null @@ -1,48 +0,0 @@ - - - - - Hash Table: Three Core Operations (All O(1)) - - - - INSERT - - map["Alice"] = - "555-1234" - - 1. Hash "Alice" - 2. Compute index - 3. Store at index - - O(1) - - - - LOOKUP - - value = - map["Alice"] - - 1. Hash "Alice" - 2. Compute index - 3. Read from index - - O(1) - - - - DELETE - - delete - map["Alice"] - - 1. Hash "Alice" - 2. Compute index - 3. Remove from index - - O(1) - - - All operations complete in constant time (on average) - diff --git a/apps/docs/public/icon.png b/apps/docs/public/icon.png deleted file mode 100644 index b04c939..0000000 Binary files a/apps/docs/public/icon.png and /dev/null differ diff --git a/apps/docs/public/intervals_merge-formula.svg b/apps/docs/public/intervals_merge-formula.svg deleted file mode 100644 index a541aba..0000000 --- a/apps/docs/public/intervals_merge-formula.svg +++ /dev/null @@ -1,29 +0,0 @@ -AAAWgmp1bWIAAAAeanVtZGMycGEAEQAQgAAAqgA4m3EDYzJwYQAAABZcanVtYgAAAEdqdW1kYzJtYQARABCAAACqADibcQN1cm46YzJwYTplNTQ2ZmQ3OS1hNTQwLTQwYzctYjMxZC1mOWYyODMxZWUwN2YAAAADl2p1bWIAAAApanVtZGMyYXMAEQAQgAAAqgA4m3EDYzJwYS5hc3NlcnRpb25zAAAAALxqdW1iAAAARGp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuaW5ncmVkaWVudC52MwAAAAAYYzJzaPzccOzcNqqGwCVEue2cwv0AAABwY2JvcqNpZGM6Zm9ybWF0bWltYWdlL3N2Zyt4bWxqaW5zdGFuY2VJRHgseG1wOmlpZDo3OGU1MmE0MS1iMWZjLTQ5MTItOGFkNC1kMjQzODM2MjAyZTRscmVsYXRpb25zaGlwaHBhcmVudE9mAAAB4mp1bWIAAABBanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5hY3Rpb25zLnYyAAAAABhjMnNocPSh2E7eebWNUztuxZwhMAAAAZljYm9yomdhY3Rpb25zgqJmYWN0aW9ua2MycGEub3BlbmVkanBhcmFtZXRlcnOha2luZ3JlZGllbnRzgaJjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFggfICJhx/gC3AGlwvd6lmmDILnZfr/OwUArljCD0UDNCWkZmFjdGlvbngdY29tLmFudGhyb3BpYy5jbGF1ZGUucHJvdmlkZWRqcGFyYW1ldGVyc6F4H2NvbS5hbnRocm9waWMub3JpZ2luLWNvbmZpZGVuY2VndW5rbm93bmtkZXNjcmlwdGlvbnhmQ2xhdWRlIHByb3ZpZGVkIHRoaXMgZmlsZSBhdCB0aGUgcmVxdWVzdCBvZiBhIHVzZXIgYW5kIG1heSBoYXZlIGNyZWF0ZWQgb3IgbW9kaWZpZWQgdGhlIGZpbGUgY29udGVudHMubXNvZnR3YXJlQWdlbnShZG5hbWVmQ2xhdWRlcmFsbEFjdGlvbnNJbmNsdWRlZPUAAADIanVtYgAAAEBqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmhhc2guZGF0YQAAAAAYYzJzaBCXwkaI77miLLMhWyIze/IAAACAY2JvcqVjYWxnZnNoYTI1NmNwYWRNAAAAAAAAAAAAAAAAAGRoYXNoWCB1LL7eMTTGwGK0wDUm4AhFQ7VuJ1M+odScr8pTYladZWRuYW1lbmp1bWJmIG1hbmlmZXN0amV4Y2x1c2lvbnOBomVzdGFydBjKZmxlbmd0aBkeBAAAAj5qdW1iAAAAJ2p1bWRjMmNsABEAEIAAAKoAOJtxA2MycGEuY2xhaW0udjIAAAACD2Nib3KlY2FsZ2ZzaGEyNTZpc2lnbmF0dXJleE1zZWxmI2p1bWJmPS9jMnBhL3VybjpjMnBhOmU1NDZmZDc5LWE1NDAtNDBjNy1iMzFkLWY5ZjI4MzFlZTA3Zi9jMnBhLnNpZ25hdHVyZWppbnN0YW5jZUlEeCx4bXA6aWlkOmVjYTBhMGI3LTM2YmEtNGU2NC05ZjY5LWVhYTY1YmI4MzEyOHJjcmVhdGVkX2Fzc2VydGlvbnODomN1cmx4LXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaW5ncmVkaWVudC52M2RoYXNoWCB8gImHH+ALcAaXC93qWaYMgudl+v87BQCuWMIPRQM0JaJjdXJseCpzZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmFjdGlvbnMudjJkaGFzaFggSiOomuuA+mAkOE2eUUrEUWQVHEzCQnln5mDcE/fV6vSiY3VybHgpc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5oYXNoLmRhdGFkaGFzaFggzqJMOqEsC3jDPpPwzhLmPHdw+3qStk06lig2fLA7eKB0Y2xhaW1fZ2VuZXJhdG9yX2luZm+jZG5hbWVvQW50aHJvcGljIEZpbGVzZ3ZlcnNpb25lMS4wLjBrc3BlY1ZlcnNpb25lMi40LjAAABA4anVtYgAAAChqdW1kYzJjcwARABCAAACqADibcQNjMnBhLnNpZ25hdHVyZQAAABAIY2JvctKEWQISogEmGCFZAgowggIGMIIBjaADAgECAhRA5aAK7sI50L64g/oGQgU9Z1UTADAKBggqhkjOPQQDAzBJMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEuMCwGA1UEAxMlQW50aHJvcGljIENvbnRlbnQgQ3JlZGVudGlhbHMgUm9vdCBDQTAeFw0yNjA4MDcxODQzNTZaFw0yODA4MDYxOTQzNTZaMEQxFzAVBgNVBAoTDkFudGhyb3BpYywgUEJDMSkwJwYDVQQDEyBBbnRocm9waWMgQ2xhdWRlIENvbnRlbnQgU2lnbmluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJh6CmvLUBgFFNU0vUKlOVtE6djd17L5SuwX0LemFisBM3dkd/3cyjxFA3Qo5S46fX0/ihY0VZ7mfb9KF703t5OjWDBWMA4GA1UdDwEB/wQEAwIHgDAVBgNVHSUEDjAMBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUzlHiBIFOZFsj+OPEz5o+nMHXXMIwCgYIKoZIzj0EAwMDZwAwZAIwMXMdFJ4BetLLVY7ORuE9noqbbAZOZn/aArXyTwFAZfKrPzxF2vPoJNf1+UCdg1XGAjBwX1zd9WGqYkqmL5SFqw1QySjr1zJfpJM9+1rdDwSPLMOPOjKuiXjoU/pUUeG9RwmhY3BhZFkNngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPZYQIaJokP0Hf2HFMBeaGK6DyJkDDQQ4XQObQu3dSbfF2CKf2lxM4/ZntW2Hq+PesGaHfq2LFy55GOjWL0an2tLY3M= - - - - Merge = [min(starts), max(ends)] - - - - - - - 2 - 8 - x - - - - y - - 5 - 11 - - - - merge = [2, 11] - - - min(2, 5) = 2 and max(8, 11) = 11 - diff --git a/apps/docs/public/intervals_overlap-condition.svg b/apps/docs/public/intervals_overlap-condition.svg deleted file mode 100644 index 6299e9f..0000000 --- a/apps/docs/public/intervals_overlap-condition.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - When do intervals overlap? - - - No overlap: a ends before b starts - a - - b - - a.end < b.start - - - Overlap: their times cross - a - - b - - - - - - - shared = [max start, min end] - diff --git a/apps/docs/public/intervals_overlap-formula.svg b/apps/docs/public/intervals_overlap-formula.svg deleted file mode 100644 index 3ba4fa6..0000000 --- a/apps/docs/public/intervals_overlap-formula.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Overlap = [max(starts), min(ends)] - - - 2 - 8 - x - - - - y - - 5 - 11 - - - - - - overlap = [5, 8] - - - max(2, 5) = 5 and min(8, 11) = 8 - diff --git a/apps/docs/public/logo.svg b/apps/docs/public/logo.svg deleted file mode 100644 index 2f4041a..0000000 --- a/apps/docs/public/logo.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - \ No newline at end of file diff --git a/apps/docs/public/prefix-sum_1.png b/apps/docs/public/prefix-sum_1.png deleted file mode 100644 index 93318cd..0000000 Binary files a/apps/docs/public/prefix-sum_1.png and /dev/null differ diff --git a/apps/docs/public/sliding-window-fixed-size.svg b/apps/docs/public/sliding-window-fixed-size.svg deleted file mode 100644 index b497628..0000000 --- a/apps/docs/public/sliding-window-fixed-size.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - Sliding Window: Fixed Size - Window of size K=3 moving through the array. - - - - 1. Current window (Sum = 10) - - - - - 2 - - - 3 - - - 5 - - - 1 - - - 8 - - - - - - - - - - Slide window: -2, +1 - - - - - 2. Next window (Sum = 9) - - - - - 2 - - - 3 - - - 5 - - - 1 - - - 8 - - - - - - diff --git a/apps/docs/public/sliding-window-flexible.svg b/apps/docs/public/sliding-window-flexible.svg deleted file mode 100644 index 048b407..0000000 --- a/apps/docs/public/sliding-window-flexible.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - Sliding Window: Flexible Size - Window expands until invalid, then shrinks until valid again. - - - - 1. Expansion breaks invariant - - - - - a - - L - - - b - - - c - - - a - - R - - Duplicate 'a'! - - - - - - - Shrink from left - - - - - 2. Invariant restored - - - - - a - - - b - - L - - - c - - - a - - R - - Valid again! - - - diff --git a/apps/docs/public/sliding-window-frequency.svg b/apps/docs/public/sliding-window-frequency.svg deleted file mode 100644 index 0e8d5cf..0000000 --- a/apps/docs/public/sliding-window-frequency.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - Sliding Window: Frequency Tracking - Maintaining a character frequency map for the current window. - - - - Current Window - - - - - a - - L - - - b - - - a - - R - - - c - - - - - - Frequency Map - - - - - - - 'a' : 2 - 'b' : 1 - 'c' : 0 - - - - - INVARIANT: SYNCED STATE - - - - - diff --git a/apps/docs/public/topo_intro_1.png b/apps/docs/public/topo_intro_1.png deleted file mode 100644 index 3e246ab..0000000 Binary files a/apps/docs/public/topo_intro_1.png and /dev/null differ diff --git a/apps/docs/public/topo_intro_2.png b/apps/docs/public/topo_intro_2.png deleted file mode 100644 index 20662b1..0000000 Binary files a/apps/docs/public/topo_intro_2.png and /dev/null differ diff --git a/apps/docs/public/topo_kahn.001.png b/apps/docs/public/topo_kahn.001.png deleted file mode 100644 index 7c103cd..0000000 Binary files a/apps/docs/public/topo_kahn.001.png and /dev/null differ diff --git a/apps/docs/public/topo_kahn.002.png b/apps/docs/public/topo_kahn.002.png deleted file mode 100644 index 65604a6..0000000 Binary files a/apps/docs/public/topo_kahn.002.png and /dev/null differ diff --git a/apps/docs/public/topo_kahn.003.png b/apps/docs/public/topo_kahn.003.png deleted file mode 100644 index 9c86ba1..0000000 Binary files a/apps/docs/public/topo_kahn.003.png and /dev/null differ diff --git a/apps/docs/public/topo_kahn.004.png b/apps/docs/public/topo_kahn.004.png deleted file mode 100644 index 8d85d29..0000000 Binary files a/apps/docs/public/topo_kahn.004.png and /dev/null differ diff --git a/apps/docs/public/topo_kahn.005.png b/apps/docs/public/topo_kahn.005.png deleted file mode 100644 index 1ef4863..0000000 Binary files a/apps/docs/public/topo_kahn.005.png and /dev/null differ diff --git a/apps/docs/public/topo_kahn.006.png b/apps/docs/public/topo_kahn.006.png deleted file mode 100644 index f9f4b4d..0000000 Binary files a/apps/docs/public/topo_kahn.006.png and /dev/null differ diff --git a/apps/docs/public/topo_kahn.007.png b/apps/docs/public/topo_kahn.007.png deleted file mode 100644 index 88b5dbc..0000000 Binary files a/apps/docs/public/topo_kahn.007.png and /dev/null differ diff --git a/apps/docs/public/topo_kahn.009.png b/apps/docs/public/topo_kahn.009.png deleted file mode 100644 index ef2320d..0000000 Binary files a/apps/docs/public/topo_kahn.009.png and /dev/null differ diff --git a/apps/docs/public/topo_kahn.010.png b/apps/docs/public/topo_kahn.010.png deleted file mode 100644 index da9f88b..0000000 Binary files a/apps/docs/public/topo_kahn.010.png and /dev/null differ diff --git a/apps/docs/redirects.ts b/apps/docs/redirects.ts deleted file mode 100644 index d47316a..0000000 --- a/apps/docs/redirects.ts +++ /dev/null @@ -1,95 +0,0 @@ -// Generated by `bun run sync` (apps/cli/sync.ts) — do not edit. -// `/leetcode/` → that problem's page; exact paths, base added by Blume. -export default [ - { from: "/1", to: "/hash-table/two-sum" }, - { from: "/2", to: "/hybrid-structures/add-two-numbers" }, - { from: "/3", to: "/sliding-window/longest-substring-without-repeating-characters" }, - { from: "/11", to: "/two-pointers/container-with-most-water" }, - { from: "/15", to: "/two-pointers/3sum" }, - { from: "/20", to: "/stack/valid-parentheses" }, - { from: "/21", to: "/linked-list/merge-two-sorted-lists" }, - { from: "/23", to: "/heap-priority-queue/merge-k-sorted-lists" }, - { from: "/33", to: "/binary-search/search-in-rotated-sorted-array" }, - { from: "/34", to: "/binary-search/find-first-and-last-position-of-element-in-sorted-array" }, - { from: "/35", to: "/binary-search/search-insert-position" }, - { from: "/39", to: "/backtracking/combination-sum" }, - { from: "/42", to: "/two-pointers/trapping-rain-water" }, - { from: "/46", to: "/backtracking/permutations" }, - { from: "/48", to: "/matrix-index-math/rotate-image" }, - { from: "/49", to: "/hash-table/group-anagrams" }, - { from: "/53", to: "/greedy/maximum-subarray" }, - { from: "/54", to: "/matrix-index-math/spiral-matrix" }, - { from: "/55", to: "/greedy/jump-game" }, - { from: "/56", to: "/intervals/merge-intervals" }, - { from: "/57", to: "/intervals/insert-interval" }, - { from: "/62", to: "/multi-d-grid-dp/unique-paths" }, - { from: "/64", to: "/multi-d-grid-dp/minimum-path-sum" }, - { from: "/70", to: "/1d-dynamic-programming/climbing-stairs" }, - { from: "/73", to: "/matrix-index-math/set-matrix-zeroes" }, - { from: "/74", to: "/binary-search/search-a-2d-matrix" }, - { from: "/78", to: "/backtracking/subsets" }, - { from: "/98", to: "/binary-search-tree/validate-binary-search-tree" }, - { from: "/102", to: "/tree-bfs/binary-tree-level-order-traversal" }, - { from: "/104", to: "/tree-dfs/maximum-depth-of-binary-tree" }, - { from: "/121", to: "/sliding-window/best-time-to-buy-and-sell-stock" }, - { from: "/125", to: "/two-pointers/valid-palindrome" }, - { from: "/127", to: "/graph-bfs/word-ladder" }, - { from: "/128", to: "/hash-table/longest-consecutive-sequence" }, - { from: "/133", to: "/graph-dfs/clone-graph" }, - { from: "/134", to: "/greedy/gas-station" }, - { from: "/141", to: "/linked-list/linked-list-cycle" }, - { from: "/146", to: "/hybrid-structures/lru-cache" }, - { from: "/150", to: "/stack/evaluate-reverse-polish-notation" }, - { from: "/153", to: "/binary-search/find-minimum-in-rotated-sorted-array" }, - { from: "/155", to: "/stack/min-stack" }, - { from: "/162", to: "/binary-search/find-peak-element" }, - { from: "/167", to: "/two-pointers/two-sum-ii-input-array-is-sorted" }, - { from: "/189", to: "/two-pointers/rotate-array" }, - { from: "/198", to: "/1d-dynamic-programming/house-robber" }, - { from: "/199", to: "/tree-bfs/binary-tree-right-side-view" }, - { from: "/200", to: "/graph-dfs/number-of-islands" }, - { from: "/205", to: "/hash-table/isomorphic-strings" }, - { from: "/206", to: "/linked-list/reverse-linked-list" }, - { from: "/207", to: "/topological-sort/course-schedule" }, - { from: "/208", to: "/trie/implement-trie-prefix-tree" }, - { from: "/209", to: "/sliding-window/minimum-size-subarray-sum" }, - { from: "/210", to: "/topological-sort/course-schedule-ii" }, - { from: "/211", to: "/trie/design-add-and-search-words-data-structure" }, - { from: "/215", to: "/heap-priority-queue/kth-largest-element-in-an-array" }, - { from: "/217", to: "/hash-table/contains-duplicate" }, - { from: "/226", to: "/tree-dfs/invert-binary-tree" }, - { from: "/230", to: "/binary-search-tree/kth-smallest-element-in-a-bst" }, - { from: "/235", to: "/binary-search-tree/lowest-common-ancestor-of-a-binary-search-tree" }, - { from: "/236", to: "/tree-dfs/lowest-common-ancestor-of-a-binary-tree" }, - { from: "/238", to: "/prefix-sum/product-of-array-except-self" }, - { from: "/242", to: "/hash-table/valid-anagram" }, - { from: "/290", to: "/hash-table/word-pattern" }, - { from: "/303", to: "/prefix-sum/range-sum-query-immutable" }, - { from: "/322", to: "/1d-dynamic-programming/coin-change" }, - { from: "/323", to: "/union-find/number-of-connected-components-in-an-undirected-graph" }, - { from: "/344", to: "/two-pointers/reverse-string" }, - { from: "/347", to: "/hash-table/top-k-frequent-elements" }, - { from: "/387", to: "/hash-table/first-unique-character-in-a-string" }, - { from: "/392", to: "/two-pointers/is-subsequence" }, - { from: "/417", to: "/graph-dfs/pacific-atlantic-water-flow" }, - { from: "/424", to: "/sliding-window/longest-repeating-character-replacement" }, - { from: "/435", to: "/intervals/non-overlapping-intervals" }, - { from: "/451", to: "/hash-table/sort-characters-by-frequency" }, - { from: "/525", to: "/prefix-sum/contiguous-array" }, - { from: "/560", to: "/prefix-sum/subarray-sum-equals-k" }, - { from: "/567", to: "/sliding-window/permutation-in-string" }, - { from: "/643", to: "/sliding-window/maximum-average-subarray-i" }, - { from: "/684", to: "/union-find/redundant-connection" }, - { from: "/704", to: "/binary-search/binary-search" }, - { from: "/739", to: "/monotonic-stack/daily-temperatures" }, - { from: "/853", to: "/monotonic-stack/car-fleet" }, - { from: "/875", to: "/binary-search/koko-eating-bananas" }, - { from: "/973", to: "/heap-priority-queue/k-closest-points-to-origin" }, - { from: "/974", to: "/prefix-sum/subarray-sums-divisible-by-k" }, - { from: "/977", to: "/two-pointers/squares-of-a-sorted-array" }, - { from: "/994", to: "/graph-bfs/rotting-oranges" }, - { from: "/1004", to: "/sliding-window/max-consecutive-ones-iii" }, - { from: "/1143", to: "/multi-d-grid-dp/longest-common-subsequence" }, - { from: "/1448", to: "/tree-bfs/count-good-nodes-in-binary-tree" }, - { from: "/1832", to: "/hash-table/check-if-the-sentence-is-pangram" }, -]; diff --git a/apps/docs/theme.css b/apps/docs/theme.css deleted file mode 100644 index 6cb39f1..0000000 --- a/apps/docs/theme.css +++ /dev/null @@ -1,71 +0,0 @@ -/* - * shadcn/ui tokens on top of Blume's theme. Blume (Tailwind v4) already maps - * background/foreground/muted/border onto its --blume-* tokens; this adds the - * names shadcn components expect, pointed at the same tokens so the progress - * cards follow the site in both colour schemes. Blume inlines this file into - * its Tailwind entry and switches dark mode with [data-theme="dark"], not - * shadcn's .dark class. - * - * Deliberately NOT remapped: `accent`. In Blume it is the brand colour - * (bg-accent), in shadcn a subtle hover surface; the vendored components here - * never use it. - */ - -:root { - /* Difficulty hues, LeetCode's: easy teal, medium amber, hard red. */ - --chart-1: oklch(0.72 0.12 195); - --chart-2: oklch(0.8 0.16 80); - --chart-3: oklch(0.63 0.22 25); - /* Solved fill for the topic bubbles. */ - --chart-4: oklch(0.72 0.17 150); - --chart-5: oklch(0.6 0.12 250); - /* Heatmap ramp — the GREENS of apps/api/src/charts.ts (README + email), with - level 0 on the theme's muted surface so an empty day reads in light mode. */ - --heat-0: var(--blume-muted); - --heat-1: #9be9a8; - --heat-2: #40c463; - --heat-3: #30a14e; - --heat-4: #216e39; -} - -:root[data-theme="dark"] { - --chart-1: oklch(0.75 0.12 195); - --chart-2: oklch(0.83 0.16 80); - --chart-3: oklch(0.68 0.21 25); - --chart-4: oklch(0.76 0.17 150); - --chart-5: oklch(0.7 0.12 250); - --heat-0: var(--blume-muted); - --heat-1: #0e4429; - --heat-2: #006d32; - --heat-3: #26a641; - --heat-4: #39d353; -} - -/* shadcn components draw `border` with no colour and rely on a global - `* { border-color: var(--border) }` base rule. Blume has none (its own - components always name border-border), so give shadcn's parts — every one - carries data-slot — that default, scoped, without touching Blume's. */ -@layer base { - [data-slot], - [data-slot] * { - border-color: var(--blume-border); - } -} - -@theme inline { - --color-card: var(--blume-background); - --color-card-foreground: var(--blume-foreground); - --color-popover: var(--blume-background); - --color-popover-foreground: var(--blume-foreground); - --color-primary: var(--blume-foreground); - --color-primary-foreground: var(--blume-background); - --color-secondary: var(--blume-muted); - --color-secondary-foreground: var(--blume-foreground); - --color-input: var(--blume-border); - --color-ring: var(--blume-muted-foreground); - --color-chart-1: var(--chart-1); - --color-chart-2: var(--chart-2); - --color-chart-3: var(--chart-3); - --color-chart-4: var(--chart-4); - --color-chart-5: var(--chart-5); -} diff --git a/apps/docs/topic-landings.ts b/apps/docs/topic-landings.ts deleted file mode 100644 index c6e69cc..0000000 --- a/apps/docs/topic-landings.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * LeetCode topic-tag slug → the campaign landing routes that teach it, in - * default preference order. The one answer to "does the campaign cover this - * tag", read by the sides that must agree: - * - * - `components/ProblemBadges.astro` links a covered tag's badge to its - * landing page, and every other tag's badge to its Appendix card, - * `/appendix#` (`appendix.ts`); - * - `components/BacklinksList.astro` lists, under a landing page, every - * problem whose badge links there — both via `tagTarget()`; - * - `apps/cli/sync.ts` warns about a tag a problem page carries that is - * neither a key here nor an Appendix term. - * - * Add a key when a campaign topic starts teaching a tag; its Appendix term is - * then dead weight, delete it. A tag naming two topics (DFS/BFS: tree and - * graph; DP: 1-D and grid) lists both; the badge picks per page. - */ -import { APPENDIX } from "./appendix.ts"; - -export const TOPIC_LANDINGS: Record = { - "hash-table": ["/hash-table"], - "prefix-sum": ["/prefix-sum"], - "two-pointers": ["/two-pointers"], - "sliding-window": ["/sliding-window"], - "binary-search": ["/binary-search"], - stack: ["/stack"], - "monotonic-stack": ["/monotonic-stack"], - "linked-list": ["/linked-list"], - matrix: ["/matrix-index-math"], - "depth-first-search": ["/tree-dfs", "/graph-dfs"], - "breadth-first-search": ["/tree-bfs", "/graph-bfs"], - "binary-search-tree": ["/binary-search-tree"], - "heap-priority-queue": ["/heap-priority-queue"], - trie: ["/trie"], - "topological-sort": ["/topological-sort"], - "union-find": ["/union-find"], - backtracking: ["/backtracking"], - "dynamic-programming": ["/1d-dynamic-programming", "/multi-d-grid-dp"], - greedy: ["/greedy"], -}; - -/** One of LeetCode's topic tags, as a problem page's `topics:` frontmatter carries it. */ -export interface TopicTag { - name: string; - slug: string; -} - -/** - * Where a problem page's badge for `tag` points: a root-relative landing - * route or Appendix anchor (the caller adds the base), or LeetCode's problem - * list when the site has neither, so a badge never links to a 404. - * - * A tag naming two landings links to the page's own topic — its route's first - * segment — when that is one of them; otherwise DFS/BFS prefer the tree topic - * for a problem LeetCode also tags `Tree` and the graph topic for the rest. - */ -export function tagTarget( - tag: TopicTag, - page: { route: string; tags: readonly TopicTag[] }, - served: ReadonlySet, -): { href: string; kind: "landing" | "appendix" | "external" } { - const landings = (TOPIC_LANDINGS[tag.slug] ?? []).filter((path) => served.has(path)); - const own = `/${page.route.split("/")[1]}`; - const prefer = page.tags.some((t) => t.slug === "tree") ? "/tree-" : "/graph-"; - const landing = landings.includes(own) ? own : (landings.find((p) => p.startsWith(prefer)) ?? landings[0]); - if (landing) return { href: landing, kind: "landing" }; - if (APPENDIX[tag.slug]) return { href: `/appendix#${tag.slug}`, kind: "appendix" }; - return { href: `https://leetcode.com/problem-list/${tag.slug}/`, kind: "external" }; -} diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json deleted file mode 100644 index a466480..0000000 --- a/apps/docs/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "Bundler", - "jsx": "react-jsx", - "strict": true, - "noEmit": true, - "skipLibCheck": true, - "allowImportingTsExtensions": true, - "paths": { - "@/components": ["./components"], - "@/components/*": ["./components/*"], - "@/lib": ["./lib"], - "@/lib/*": ["./lib/*"], - "@/hooks": ["./hooks"], - "@/hooks/*": ["./hooks/*"] - } - }, - "include": ["islands", "components/ui", "lib", "topic-landings.ts", "redirects.ts"] -} diff --git a/apps/docs/content/(01-linear-structures)/binary-search/(problems)/153_find-minimum-in-rotated-sorted-array.mdx b/docs/(01-linear-structures)/binary-search/(problems)/153_find-minimum-in-rotated-sorted-array.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/binary-search/(problems)/153_find-minimum-in-rotated-sorted-array.mdx rename to docs/(01-linear-structures)/binary-search/(problems)/153_find-minimum-in-rotated-sorted-array.mdx diff --git a/apps/docs/content/(01-linear-structures)/binary-search/(problems)/162_find-peak-element.mdx b/docs/(01-linear-structures)/binary-search/(problems)/162_find-peak-element.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/binary-search/(problems)/162_find-peak-element.mdx rename to docs/(01-linear-structures)/binary-search/(problems)/162_find-peak-element.mdx diff --git a/apps/docs/content/(01-linear-structures)/binary-search/(problems)/33_search-in-rotated-sorted-array.mdx b/docs/(01-linear-structures)/binary-search/(problems)/33_search-in-rotated-sorted-array.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/binary-search/(problems)/33_search-in-rotated-sorted-array.mdx rename to docs/(01-linear-structures)/binary-search/(problems)/33_search-in-rotated-sorted-array.mdx diff --git a/apps/docs/content/(01-linear-structures)/binary-search/(problems)/34_find-first-and-last-position-of-element-in-sorted-array.mdx b/docs/(01-linear-structures)/binary-search/(problems)/34_find-first-and-last-position-of-element-in-sorted-array.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/binary-search/(problems)/34_find-first-and-last-position-of-element-in-sorted-array.mdx rename to docs/(01-linear-structures)/binary-search/(problems)/34_find-first-and-last-position-of-element-in-sorted-array.mdx diff --git a/apps/docs/content/(01-linear-structures)/binary-search/(problems)/35_search-insert-position.mdx b/docs/(01-linear-structures)/binary-search/(problems)/35_search-insert-position.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/binary-search/(problems)/35_search-insert-position.mdx rename to docs/(01-linear-structures)/binary-search/(problems)/35_search-insert-position.mdx diff --git a/apps/docs/content/(01-linear-structures)/binary-search/(problems)/704_binary-search.mdx b/docs/(01-linear-structures)/binary-search/(problems)/704_binary-search.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/binary-search/(problems)/704_binary-search.mdx rename to docs/(01-linear-structures)/binary-search/(problems)/704_binary-search.mdx diff --git a/apps/docs/content/(01-linear-structures)/binary-search/(problems)/74_search-a-2d-matrix.mdx b/docs/(01-linear-structures)/binary-search/(problems)/74_search-a-2d-matrix.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/binary-search/(problems)/74_search-a-2d-matrix.mdx rename to docs/(01-linear-structures)/binary-search/(problems)/74_search-a-2d-matrix.mdx diff --git a/apps/docs/content/(01-linear-structures)/binary-search/(problems)/875_koko-eating-bananas.mdx b/docs/(01-linear-structures)/binary-search/(problems)/875_koko-eating-bananas.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/binary-search/(problems)/875_koko-eating-bananas.mdx rename to docs/(01-linear-structures)/binary-search/(problems)/875_koko-eating-bananas.mdx diff --git a/apps/docs/content/(01-linear-structures)/binary-search/index.mdx b/docs/(01-linear-structures)/binary-search/index.mdx similarity index 90% rename from apps/docs/content/(01-linear-structures)/binary-search/index.mdx rename to docs/(01-linear-structures)/binary-search/index.mdx index 8fafb1e..7667191 100644 --- a/apps/docs/content/(01-linear-structures)/binary-search/index.mdx +++ b/docs/(01-linear-structures)/binary-search/index.mdx @@ -26,14 +26,14 @@ Time Complexity: `O(log(n))` - - - - - - - - + + + + + + + + ```python diff --git a/apps/docs/content/(01-linear-structures)/hash-table/(problems)/128_longest-consecutive-sequence.mdx b/docs/(01-linear-structures)/hash-table/(problems)/128_longest-consecutive-sequence.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/hash-table/(problems)/128_longest-consecutive-sequence.mdx rename to docs/(01-linear-structures)/hash-table/(problems)/128_longest-consecutive-sequence.mdx diff --git a/apps/docs/content/(01-linear-structures)/hash-table/(problems)/1832_check-if-the-sentence-is-pangram.mdx b/docs/(01-linear-structures)/hash-table/(problems)/1832_check-if-the-sentence-is-pangram.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/hash-table/(problems)/1832_check-if-the-sentence-is-pangram.mdx rename to docs/(01-linear-structures)/hash-table/(problems)/1832_check-if-the-sentence-is-pangram.mdx diff --git a/apps/docs/content/(01-linear-structures)/hash-table/(problems)/1_two-sum.mdx b/docs/(01-linear-structures)/hash-table/(problems)/1_two-sum.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/hash-table/(problems)/1_two-sum.mdx rename to docs/(01-linear-structures)/hash-table/(problems)/1_two-sum.mdx diff --git a/apps/docs/content/(01-linear-structures)/hash-table/(problems)/205_isomorphic-strings.mdx b/docs/(01-linear-structures)/hash-table/(problems)/205_isomorphic-strings.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/hash-table/(problems)/205_isomorphic-strings.mdx rename to docs/(01-linear-structures)/hash-table/(problems)/205_isomorphic-strings.mdx diff --git a/apps/docs/content/(01-linear-structures)/hash-table/(problems)/217_contains-duplicate.mdx b/docs/(01-linear-structures)/hash-table/(problems)/217_contains-duplicate.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/hash-table/(problems)/217_contains-duplicate.mdx rename to docs/(01-linear-structures)/hash-table/(problems)/217_contains-duplicate.mdx diff --git a/apps/docs/content/(01-linear-structures)/hash-table/(problems)/242_valid-anagram.mdx b/docs/(01-linear-structures)/hash-table/(problems)/242_valid-anagram.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/hash-table/(problems)/242_valid-anagram.mdx rename to docs/(01-linear-structures)/hash-table/(problems)/242_valid-anagram.mdx diff --git a/apps/docs/content/(01-linear-structures)/hash-table/(problems)/290_word-pattern.mdx b/docs/(01-linear-structures)/hash-table/(problems)/290_word-pattern.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/hash-table/(problems)/290_word-pattern.mdx rename to docs/(01-linear-structures)/hash-table/(problems)/290_word-pattern.mdx diff --git a/apps/docs/content/(01-linear-structures)/hash-table/(problems)/347_top-k-frequent-elements.mdx b/docs/(01-linear-structures)/hash-table/(problems)/347_top-k-frequent-elements.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/hash-table/(problems)/347_top-k-frequent-elements.mdx rename to docs/(01-linear-structures)/hash-table/(problems)/347_top-k-frequent-elements.mdx diff --git a/apps/docs/content/(01-linear-structures)/hash-table/(problems)/387_first-unique-character-in-a-string.mdx b/docs/(01-linear-structures)/hash-table/(problems)/387_first-unique-character-in-a-string.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/hash-table/(problems)/387_first-unique-character-in-a-string.mdx rename to docs/(01-linear-structures)/hash-table/(problems)/387_first-unique-character-in-a-string.mdx diff --git a/apps/docs/content/(01-linear-structures)/hash-table/(problems)/451_sort-characters-by-frequency.mdx b/docs/(01-linear-structures)/hash-table/(problems)/451_sort-characters-by-frequency.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/hash-table/(problems)/451_sort-characters-by-frequency.mdx rename to docs/(01-linear-structures)/hash-table/(problems)/451_sort-characters-by-frequency.mdx diff --git a/apps/docs/content/(01-linear-structures)/hash-table/(problems)/49_group-anagrams.mdx b/docs/(01-linear-structures)/hash-table/(problems)/49_group-anagrams.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/hash-table/(problems)/49_group-anagrams.mdx rename to docs/(01-linear-structures)/hash-table/(problems)/49_group-anagrams.mdx diff --git a/apps/docs/content/(01-linear-structures)/hash-table/index.mdx b/docs/(01-linear-structures)/hash-table/index.mdx similarity index 97% rename from apps/docs/content/(01-linear-structures)/hash-table/index.mdx rename to docs/(01-linear-structures)/hash-table/index.mdx index 2b720f4..de2adcd 100644 --- a/apps/docs/content/(01-linear-structures)/hash-table/index.mdx +++ b/docs/(01-linear-structures)/hash-table/index.mdx @@ -13,7 +13,7 @@ Data is stored in a fix-sized array, where each data value has an unique index. -![](../../../public/hash-table-operations.svg) +![](/hash-table-operations.svg) diff --git a/apps/docs/content/(01-linear-structures)/monotonic-stack/(problems)/739_daily-temperatures.mdx b/docs/(01-linear-structures)/monotonic-stack/(problems)/739_daily-temperatures.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/monotonic-stack/(problems)/739_daily-temperatures.mdx rename to docs/(01-linear-structures)/monotonic-stack/(problems)/739_daily-temperatures.mdx diff --git a/apps/docs/content/(01-linear-structures)/monotonic-stack/(problems)/853_car-fleet.mdx b/docs/(01-linear-structures)/monotonic-stack/(problems)/853_car-fleet.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/monotonic-stack/(problems)/853_car-fleet.mdx rename to docs/(01-linear-structures)/monotonic-stack/(problems)/853_car-fleet.mdx diff --git a/apps/docs/content/(01-linear-structures)/monotonic-stack/index.mdx b/docs/(01-linear-structures)/monotonic-stack/index.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/monotonic-stack/index.mdx rename to docs/(01-linear-structures)/monotonic-stack/index.mdx diff --git a/apps/docs/content/(01-linear-structures)/prefix-sum/(problems)/238_product-of-array-except-self.mdx b/docs/(01-linear-structures)/prefix-sum/(problems)/238_product-of-array-except-self.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/prefix-sum/(problems)/238_product-of-array-except-self.mdx rename to docs/(01-linear-structures)/prefix-sum/(problems)/238_product-of-array-except-self.mdx diff --git a/apps/docs/content/(01-linear-structures)/prefix-sum/(problems)/303_range-sum-query-immutable.mdx b/docs/(01-linear-structures)/prefix-sum/(problems)/303_range-sum-query-immutable.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/prefix-sum/(problems)/303_range-sum-query-immutable.mdx rename to docs/(01-linear-structures)/prefix-sum/(problems)/303_range-sum-query-immutable.mdx diff --git a/apps/docs/content/(01-linear-structures)/prefix-sum/(problems)/525_contiguous-array.mdx b/docs/(01-linear-structures)/prefix-sum/(problems)/525_contiguous-array.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/prefix-sum/(problems)/525_contiguous-array.mdx rename to docs/(01-linear-structures)/prefix-sum/(problems)/525_contiguous-array.mdx diff --git a/apps/docs/content/(01-linear-structures)/prefix-sum/(problems)/560_subarray-sum-equals-k.mdx b/docs/(01-linear-structures)/prefix-sum/(problems)/560_subarray-sum-equals-k.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/prefix-sum/(problems)/560_subarray-sum-equals-k.mdx rename to docs/(01-linear-structures)/prefix-sum/(problems)/560_subarray-sum-equals-k.mdx diff --git a/apps/docs/content/(01-linear-structures)/prefix-sum/(problems)/974_subarray-sums-divisible-by-k.mdx b/docs/(01-linear-structures)/prefix-sum/(problems)/974_subarray-sums-divisible-by-k.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/prefix-sum/(problems)/974_subarray-sums-divisible-by-k.mdx rename to docs/(01-linear-structures)/prefix-sum/(problems)/974_subarray-sums-divisible-by-k.mdx diff --git a/apps/docs/content/(01-linear-structures)/prefix-sum/index.mdx b/docs/(01-linear-structures)/prefix-sum/index.mdx similarity index 98% rename from apps/docs/content/(01-linear-structures)/prefix-sum/index.mdx rename to docs/(01-linear-structures)/prefix-sum/index.mdx index 12974c0..e5940f3 100644 --- a/apps/docs/content/(01-linear-structures)/prefix-sum/index.mdx +++ b/docs/(01-linear-structures)/prefix-sum/index.mdx @@ -27,7 +27,7 @@ The leading `prefix[0] = 0` is the sum of zero elements. That extra slot is what -![](../../../public/prefix-sum_1.png) +![](/prefix-sum_1.png) diff --git a/apps/docs/content/(01-linear-structures)/sliding-window/(problems)/1004_max-consecutive-ones-iii.mdx b/docs/(01-linear-structures)/sliding-window/(problems)/1004_max-consecutive-ones-iii.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/sliding-window/(problems)/1004_max-consecutive-ones-iii.mdx rename to docs/(01-linear-structures)/sliding-window/(problems)/1004_max-consecutive-ones-iii.mdx diff --git a/apps/docs/content/(01-linear-structures)/sliding-window/(problems)/121_best-time-to-buy-and-sell-stock.mdx b/docs/(01-linear-structures)/sliding-window/(problems)/121_best-time-to-buy-and-sell-stock.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/sliding-window/(problems)/121_best-time-to-buy-and-sell-stock.mdx rename to docs/(01-linear-structures)/sliding-window/(problems)/121_best-time-to-buy-and-sell-stock.mdx diff --git a/apps/docs/content/(01-linear-structures)/sliding-window/(problems)/209_minimum-size-subarray-sum.mdx b/docs/(01-linear-structures)/sliding-window/(problems)/209_minimum-size-subarray-sum.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/sliding-window/(problems)/209_minimum-size-subarray-sum.mdx rename to docs/(01-linear-structures)/sliding-window/(problems)/209_minimum-size-subarray-sum.mdx diff --git a/apps/docs/content/(01-linear-structures)/sliding-window/(problems)/3_longest-substring-without-repeating-characters.mdx b/docs/(01-linear-structures)/sliding-window/(problems)/3_longest-substring-without-repeating-characters.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/sliding-window/(problems)/3_longest-substring-without-repeating-characters.mdx rename to docs/(01-linear-structures)/sliding-window/(problems)/3_longest-substring-without-repeating-characters.mdx diff --git a/apps/docs/content/(01-linear-structures)/sliding-window/(problems)/424_longest-repeating-character-replacement.mdx b/docs/(01-linear-structures)/sliding-window/(problems)/424_longest-repeating-character-replacement.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/sliding-window/(problems)/424_longest-repeating-character-replacement.mdx rename to docs/(01-linear-structures)/sliding-window/(problems)/424_longest-repeating-character-replacement.mdx diff --git a/apps/docs/content/(01-linear-structures)/sliding-window/(problems)/567_permutation-in-string.mdx b/docs/(01-linear-structures)/sliding-window/(problems)/567_permutation-in-string.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/sliding-window/(problems)/567_permutation-in-string.mdx rename to docs/(01-linear-structures)/sliding-window/(problems)/567_permutation-in-string.mdx diff --git a/apps/docs/content/(01-linear-structures)/sliding-window/(problems)/643_maximum-average-subarray-i.mdx b/docs/(01-linear-structures)/sliding-window/(problems)/643_maximum-average-subarray-i.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/sliding-window/(problems)/643_maximum-average-subarray-i.mdx rename to docs/(01-linear-structures)/sliding-window/(problems)/643_maximum-average-subarray-i.mdx diff --git a/apps/docs/content/(01-linear-structures)/sliding-window/index.mdx b/docs/(01-linear-structures)/sliding-window/index.mdx similarity index 97% rename from apps/docs/content/(01-linear-structures)/sliding-window/index.mdx rename to docs/(01-linear-structures)/sliding-window/index.mdx index 1124a53..074c52a 100644 --- a/apps/docs/content/(01-linear-structures)/sliding-window/index.mdx +++ b/docs/(01-linear-structures)/sliding-window/index.mdx @@ -42,7 +42,7 @@ The window size never changes. The main task is updating the window state effici -![](../../../public/sliding-window-fixed-size.svg) +![](/sliding-window-fixed-size.svg) @@ -79,7 +79,7 @@ Here the window expands until invalid, then shrinks until valid again. -![](../../../public/sliding-window-flexible.svg) +![](/sliding-window-flexible.svg) @@ -143,7 +143,7 @@ The difficulty comes from keeping the bookkeeping correct as characters enter an -![](../../../public/sliding-window-frequency.svg) +![](/sliding-window-frequency.svg) diff --git a/apps/docs/content/(01-linear-structures)/stack/(problems)/150_evaluate-reverse-polish-notation.mdx b/docs/(01-linear-structures)/stack/(problems)/150_evaluate-reverse-polish-notation.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/stack/(problems)/150_evaluate-reverse-polish-notation.mdx rename to docs/(01-linear-structures)/stack/(problems)/150_evaluate-reverse-polish-notation.mdx diff --git a/apps/docs/content/(01-linear-structures)/stack/(problems)/155_min-stack.mdx b/docs/(01-linear-structures)/stack/(problems)/155_min-stack.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/stack/(problems)/155_min-stack.mdx rename to docs/(01-linear-structures)/stack/(problems)/155_min-stack.mdx diff --git a/apps/docs/content/(01-linear-structures)/stack/(problems)/20_valid-parentheses.mdx b/docs/(01-linear-structures)/stack/(problems)/20_valid-parentheses.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/stack/(problems)/20_valid-parentheses.mdx rename to docs/(01-linear-structures)/stack/(problems)/20_valid-parentheses.mdx diff --git a/docs/(01-linear-structures)/stack/(problems)/71_simplify-path.mdx b/docs/(01-linear-structures)/stack/(problems)/71_simplify-path.mdx new file mode 100644 index 0000000..185a0b1 --- /dev/null +++ b/docs/(01-linear-structures)/stack/(problems)/71_simplify-path.mdx @@ -0,0 +1,62 @@ +--- +title: '71. Simplify Path' +description: You are given an absolute path for a Unix-style file system, which always begins with a slash '/'. Your task is to transform this absolute path into its simplified canonical path +icon: dot +topics: + - { name: "String", slug: "string" } + - { name: "Stack", slug: "stack" } +issue: "https://github.com/prdlk/leetcode/issues/70" +sidebar: + label: 'Simplify Path' + badge: 'Medium' +--- + +### Example 1: +- Input: `path = "/home/"` +- Output: `"/home"` +- Explanation: The trailing slash should be removed. + +### Example 2: +- Input: `path = "/home//foo/"` +- Output: `"/home/foo"` +- Explanation: Multiple consecutive slashes are replaced by a single one. + +### Example 3: +- Input: `path = "/home/user/Documents/../Pictures"` +- Output: `"/home/user/Pictures"` +- Explanation: A double period ".." refers to the directory up a level (the parent directory). + +### Example 4: +- Input: `path = "/../"` +- Output: `"/"` +- Explanation: Going one level up from the root directory is not possible. + +### Example 5: +- Input: `path = "/.../a/../b/c/../d/./"` +- Output: `"/.../b/d"` +- Explanation: "..." is a valid name for a directory in this problem. + +### Constraints: + +- `1 <= path.length <= 3000` +- `path` consists of English letters, digits, period '.', slash '/' or '_'. +- `path` is a valid absolute Unix path. + +## Solution + +```py +class Solution: + def simplifyPath(self, path: str) -> str: + stack = [] + segments = [item for item in path.split("/") if item] + + for p in segments: + if stack and p == "..": + stack.pop() + elif not stack and p == ".." or p == ".": + continue + else: + stack.append(p) + + return "/" + "/".join(stack) +``` diff --git a/apps/docs/content/(01-linear-structures)/stack/index.mdx b/docs/(01-linear-structures)/stack/index.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/stack/index.mdx rename to docs/(01-linear-structures)/stack/index.mdx diff --git a/apps/docs/content/(01-linear-structures)/two-pointers/(problems)/11_container-with-most-water.mdx b/docs/(01-linear-structures)/two-pointers/(problems)/11_container-with-most-water.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/two-pointers/(problems)/11_container-with-most-water.mdx rename to docs/(01-linear-structures)/two-pointers/(problems)/11_container-with-most-water.mdx diff --git a/apps/docs/content/(01-linear-structures)/two-pointers/(problems)/125_valid-palindrome.mdx b/docs/(01-linear-structures)/two-pointers/(problems)/125_valid-palindrome.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/two-pointers/(problems)/125_valid-palindrome.mdx rename to docs/(01-linear-structures)/two-pointers/(problems)/125_valid-palindrome.mdx diff --git a/apps/docs/content/(01-linear-structures)/two-pointers/(problems)/15_3sum.mdx b/docs/(01-linear-structures)/two-pointers/(problems)/15_3sum.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/two-pointers/(problems)/15_3sum.mdx rename to docs/(01-linear-structures)/two-pointers/(problems)/15_3sum.mdx diff --git a/apps/docs/content/(01-linear-structures)/two-pointers/(problems)/167_two-sum-ii-input-array-is-sorted.mdx b/docs/(01-linear-structures)/two-pointers/(problems)/167_two-sum-ii-input-array-is-sorted.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/two-pointers/(problems)/167_two-sum-ii-input-array-is-sorted.mdx rename to docs/(01-linear-structures)/two-pointers/(problems)/167_two-sum-ii-input-array-is-sorted.mdx diff --git a/apps/docs/content/(01-linear-structures)/two-pointers/(problems)/189_rotate-array.mdx b/docs/(01-linear-structures)/two-pointers/(problems)/189_rotate-array.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/two-pointers/(problems)/189_rotate-array.mdx rename to docs/(01-linear-structures)/two-pointers/(problems)/189_rotate-array.mdx diff --git a/apps/docs/content/(01-linear-structures)/two-pointers/(problems)/344_reverse-string.mdx b/docs/(01-linear-structures)/two-pointers/(problems)/344_reverse-string.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/two-pointers/(problems)/344_reverse-string.mdx rename to docs/(01-linear-structures)/two-pointers/(problems)/344_reverse-string.mdx diff --git a/apps/docs/content/(01-linear-structures)/two-pointers/(problems)/392_is-subsequence.mdx b/docs/(01-linear-structures)/two-pointers/(problems)/392_is-subsequence.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/two-pointers/(problems)/392_is-subsequence.mdx rename to docs/(01-linear-structures)/two-pointers/(problems)/392_is-subsequence.mdx diff --git a/apps/docs/content/(01-linear-structures)/two-pointers/(problems)/42_trapping-rain-water.mdx b/docs/(01-linear-structures)/two-pointers/(problems)/42_trapping-rain-water.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/two-pointers/(problems)/42_trapping-rain-water.mdx rename to docs/(01-linear-structures)/two-pointers/(problems)/42_trapping-rain-water.mdx diff --git a/apps/docs/content/(01-linear-structures)/two-pointers/(problems)/977_squares-of-a-sorted-array.mdx b/docs/(01-linear-structures)/two-pointers/(problems)/977_squares-of-a-sorted-array.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/two-pointers/(problems)/977_squares-of-a-sorted-array.mdx rename to docs/(01-linear-structures)/two-pointers/(problems)/977_squares-of-a-sorted-array.mdx diff --git a/apps/docs/content/(01-linear-structures)/two-pointers/index.mdx b/docs/(01-linear-structures)/two-pointers/index.mdx similarity index 100% rename from apps/docs/content/(01-linear-structures)/two-pointers/index.mdx rename to docs/(01-linear-structures)/two-pointers/index.mdx diff --git a/apps/docs/content/(02-nodal-grid)/hybrid-structures/(problems)/146_lru-cache.mdx b/docs/(02-nodal-grid)/hybrid-structures/(problems)/146_lru-cache.mdx similarity index 100% rename from apps/docs/content/(02-nodal-grid)/hybrid-structures/(problems)/146_lru-cache.mdx rename to docs/(02-nodal-grid)/hybrid-structures/(problems)/146_lru-cache.mdx diff --git a/apps/docs/content/(02-nodal-grid)/hybrid-structures/(problems)/2_add-two-numbers.mdx b/docs/(02-nodal-grid)/hybrid-structures/(problems)/2_add-two-numbers.mdx similarity index 100% rename from apps/docs/content/(02-nodal-grid)/hybrid-structures/(problems)/2_add-two-numbers.mdx rename to docs/(02-nodal-grid)/hybrid-structures/(problems)/2_add-two-numbers.mdx diff --git a/apps/docs/content/(02-nodal-grid)/hybrid-structures/index.mdx b/docs/(02-nodal-grid)/hybrid-structures/index.mdx similarity index 100% rename from apps/docs/content/(02-nodal-grid)/hybrid-structures/index.mdx rename to docs/(02-nodal-grid)/hybrid-structures/index.mdx diff --git a/apps/docs/content/(02-nodal-grid)/linked-list/(problems)/141_linked-list-cycle.mdx b/docs/(02-nodal-grid)/linked-list/(problems)/141_linked-list-cycle.mdx similarity index 100% rename from apps/docs/content/(02-nodal-grid)/linked-list/(problems)/141_linked-list-cycle.mdx rename to docs/(02-nodal-grid)/linked-list/(problems)/141_linked-list-cycle.mdx diff --git a/apps/docs/content/(02-nodal-grid)/linked-list/(problems)/206_reverse-linked-list.mdx b/docs/(02-nodal-grid)/linked-list/(problems)/206_reverse-linked-list.mdx similarity index 100% rename from apps/docs/content/(02-nodal-grid)/linked-list/(problems)/206_reverse-linked-list.mdx rename to docs/(02-nodal-grid)/linked-list/(problems)/206_reverse-linked-list.mdx diff --git a/apps/docs/content/(02-nodal-grid)/linked-list/(problems)/21_merge-two-sorted-lists.mdx b/docs/(02-nodal-grid)/linked-list/(problems)/21_merge-two-sorted-lists.mdx similarity index 100% rename from apps/docs/content/(02-nodal-grid)/linked-list/(problems)/21_merge-two-sorted-lists.mdx rename to docs/(02-nodal-grid)/linked-list/(problems)/21_merge-two-sorted-lists.mdx diff --git a/apps/docs/content/(02-nodal-grid)/linked-list/index.mdx b/docs/(02-nodal-grid)/linked-list/index.mdx similarity index 100% rename from apps/docs/content/(02-nodal-grid)/linked-list/index.mdx rename to docs/(02-nodal-grid)/linked-list/index.mdx diff --git a/apps/docs/content/(02-nodal-grid)/matrix-index-math/(problems)/48_rotate-image.mdx b/docs/(02-nodal-grid)/matrix-index-math/(problems)/48_rotate-image.mdx similarity index 100% rename from apps/docs/content/(02-nodal-grid)/matrix-index-math/(problems)/48_rotate-image.mdx rename to docs/(02-nodal-grid)/matrix-index-math/(problems)/48_rotate-image.mdx diff --git a/apps/docs/content/(02-nodal-grid)/matrix-index-math/(problems)/54_spiral-matrix.mdx b/docs/(02-nodal-grid)/matrix-index-math/(problems)/54_spiral-matrix.mdx similarity index 100% rename from apps/docs/content/(02-nodal-grid)/matrix-index-math/(problems)/54_spiral-matrix.mdx rename to docs/(02-nodal-grid)/matrix-index-math/(problems)/54_spiral-matrix.mdx diff --git a/apps/docs/content/(02-nodal-grid)/matrix-index-math/(problems)/73_set-matrix-zeroes.mdx b/docs/(02-nodal-grid)/matrix-index-math/(problems)/73_set-matrix-zeroes.mdx similarity index 100% rename from apps/docs/content/(02-nodal-grid)/matrix-index-math/(problems)/73_set-matrix-zeroes.mdx rename to docs/(02-nodal-grid)/matrix-index-math/(problems)/73_set-matrix-zeroes.mdx diff --git a/apps/docs/content/(02-nodal-grid)/matrix-index-math/index.mdx b/docs/(02-nodal-grid)/matrix-index-math/index.mdx similarity index 100% rename from apps/docs/content/(02-nodal-grid)/matrix-index-math/index.mdx rename to docs/(02-nodal-grid)/matrix-index-math/index.mdx diff --git a/apps/docs/content/(03-hierarchical)/binary-search-tree/(problems)/230_kth-smallest-element-in-a-bst.mdx b/docs/(03-hierarchical)/binary-search-tree/(problems)/230_kth-smallest-element-in-a-bst.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/binary-search-tree/(problems)/230_kth-smallest-element-in-a-bst.mdx rename to docs/(03-hierarchical)/binary-search-tree/(problems)/230_kth-smallest-element-in-a-bst.mdx diff --git a/apps/docs/content/(03-hierarchical)/binary-search-tree/(problems)/235_lowest-common-ancestor-of-a-binary-search-tree.mdx b/docs/(03-hierarchical)/binary-search-tree/(problems)/235_lowest-common-ancestor-of-a-binary-search-tree.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/binary-search-tree/(problems)/235_lowest-common-ancestor-of-a-binary-search-tree.mdx rename to docs/(03-hierarchical)/binary-search-tree/(problems)/235_lowest-common-ancestor-of-a-binary-search-tree.mdx diff --git a/apps/docs/content/(03-hierarchical)/binary-search-tree/(problems)/98_validate-binary-search-tree.mdx b/docs/(03-hierarchical)/binary-search-tree/(problems)/98_validate-binary-search-tree.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/binary-search-tree/(problems)/98_validate-binary-search-tree.mdx rename to docs/(03-hierarchical)/binary-search-tree/(problems)/98_validate-binary-search-tree.mdx diff --git a/apps/docs/content/(03-hierarchical)/binary-search-tree/index.mdx b/docs/(03-hierarchical)/binary-search-tree/index.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/binary-search-tree/index.mdx rename to docs/(03-hierarchical)/binary-search-tree/index.mdx diff --git a/apps/docs/content/(03-hierarchical)/heap-priority-queue/(problems)/215_kth-largest-element-in-an-array.mdx b/docs/(03-hierarchical)/heap-priority-queue/(problems)/215_kth-largest-element-in-an-array.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/heap-priority-queue/(problems)/215_kth-largest-element-in-an-array.mdx rename to docs/(03-hierarchical)/heap-priority-queue/(problems)/215_kth-largest-element-in-an-array.mdx diff --git a/apps/docs/content/(03-hierarchical)/heap-priority-queue/(problems)/23_merge-k-sorted-lists.mdx b/docs/(03-hierarchical)/heap-priority-queue/(problems)/23_merge-k-sorted-lists.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/heap-priority-queue/(problems)/23_merge-k-sorted-lists.mdx rename to docs/(03-hierarchical)/heap-priority-queue/(problems)/23_merge-k-sorted-lists.mdx diff --git a/apps/docs/content/(03-hierarchical)/heap-priority-queue/(problems)/973_k-closest-points-to-origin.mdx b/docs/(03-hierarchical)/heap-priority-queue/(problems)/973_k-closest-points-to-origin.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/heap-priority-queue/(problems)/973_k-closest-points-to-origin.mdx rename to docs/(03-hierarchical)/heap-priority-queue/(problems)/973_k-closest-points-to-origin.mdx diff --git a/apps/docs/content/(03-hierarchical)/heap-priority-queue/index.mdx b/docs/(03-hierarchical)/heap-priority-queue/index.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/heap-priority-queue/index.mdx rename to docs/(03-hierarchical)/heap-priority-queue/index.mdx diff --git a/apps/docs/content/(03-hierarchical)/tree-bfs/(problems)/102_binary-tree-level-order-traversal.mdx b/docs/(03-hierarchical)/tree-bfs/(problems)/102_binary-tree-level-order-traversal.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/tree-bfs/(problems)/102_binary-tree-level-order-traversal.mdx rename to docs/(03-hierarchical)/tree-bfs/(problems)/102_binary-tree-level-order-traversal.mdx diff --git a/apps/docs/content/(03-hierarchical)/tree-bfs/(problems)/1448_count-good-nodes-in-binary-tree.mdx b/docs/(03-hierarchical)/tree-bfs/(problems)/1448_count-good-nodes-in-binary-tree.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/tree-bfs/(problems)/1448_count-good-nodes-in-binary-tree.mdx rename to docs/(03-hierarchical)/tree-bfs/(problems)/1448_count-good-nodes-in-binary-tree.mdx diff --git a/apps/docs/content/(03-hierarchical)/tree-bfs/(problems)/199_binary-tree-right-side-view.mdx b/docs/(03-hierarchical)/tree-bfs/(problems)/199_binary-tree-right-side-view.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/tree-bfs/(problems)/199_binary-tree-right-side-view.mdx rename to docs/(03-hierarchical)/tree-bfs/(problems)/199_binary-tree-right-side-view.mdx diff --git a/apps/docs/content/(03-hierarchical)/tree-bfs/index.mdx b/docs/(03-hierarchical)/tree-bfs/index.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/tree-bfs/index.mdx rename to docs/(03-hierarchical)/tree-bfs/index.mdx diff --git a/apps/docs/content/(03-hierarchical)/tree-dfs/(problems)/104_maximum-depth-of-binary-tree.mdx b/docs/(03-hierarchical)/tree-dfs/(problems)/104_maximum-depth-of-binary-tree.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/tree-dfs/(problems)/104_maximum-depth-of-binary-tree.mdx rename to docs/(03-hierarchical)/tree-dfs/(problems)/104_maximum-depth-of-binary-tree.mdx diff --git a/apps/docs/content/(03-hierarchical)/tree-dfs/(problems)/226_invert-binary-tree.mdx b/docs/(03-hierarchical)/tree-dfs/(problems)/226_invert-binary-tree.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/tree-dfs/(problems)/226_invert-binary-tree.mdx rename to docs/(03-hierarchical)/tree-dfs/(problems)/226_invert-binary-tree.mdx diff --git a/apps/docs/content/(03-hierarchical)/tree-dfs/(problems)/236_lowest-common-ancestor-of-a-binary-tree.mdx b/docs/(03-hierarchical)/tree-dfs/(problems)/236_lowest-common-ancestor-of-a-binary-tree.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/tree-dfs/(problems)/236_lowest-common-ancestor-of-a-binary-tree.mdx rename to docs/(03-hierarchical)/tree-dfs/(problems)/236_lowest-common-ancestor-of-a-binary-tree.mdx diff --git a/apps/docs/content/(03-hierarchical)/tree-dfs/index.mdx b/docs/(03-hierarchical)/tree-dfs/index.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/tree-dfs/index.mdx rename to docs/(03-hierarchical)/tree-dfs/index.mdx diff --git a/apps/docs/content/(03-hierarchical)/trie/(problems)/208_implement-trie-prefix-tree.mdx b/docs/(03-hierarchical)/trie/(problems)/208_implement-trie-prefix-tree.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/trie/(problems)/208_implement-trie-prefix-tree.mdx rename to docs/(03-hierarchical)/trie/(problems)/208_implement-trie-prefix-tree.mdx diff --git a/apps/docs/content/(03-hierarchical)/trie/(problems)/211_design-add-and-search-words-data-structure.mdx b/docs/(03-hierarchical)/trie/(problems)/211_design-add-and-search-words-data-structure.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/trie/(problems)/211_design-add-and-search-words-data-structure.mdx rename to docs/(03-hierarchical)/trie/(problems)/211_design-add-and-search-words-data-structure.mdx diff --git a/apps/docs/content/(03-hierarchical)/trie/index.mdx b/docs/(03-hierarchical)/trie/index.mdx similarity index 100% rename from apps/docs/content/(03-hierarchical)/trie/index.mdx rename to docs/(03-hierarchical)/trie/index.mdx diff --git a/apps/docs/content/(04-relational)/graph-bfs/(problems)/127_word-ladder.mdx b/docs/(04-relational)/graph-bfs/(problems)/127_word-ladder.mdx similarity index 100% rename from apps/docs/content/(04-relational)/graph-bfs/(problems)/127_word-ladder.mdx rename to docs/(04-relational)/graph-bfs/(problems)/127_word-ladder.mdx diff --git a/apps/docs/content/(04-relational)/graph-bfs/(problems)/994_rotting-oranges.mdx b/docs/(04-relational)/graph-bfs/(problems)/994_rotting-oranges.mdx similarity index 100% rename from apps/docs/content/(04-relational)/graph-bfs/(problems)/994_rotting-oranges.mdx rename to docs/(04-relational)/graph-bfs/(problems)/994_rotting-oranges.mdx diff --git a/apps/docs/content/(04-relational)/graph-bfs/index.mdx b/docs/(04-relational)/graph-bfs/index.mdx similarity index 100% rename from apps/docs/content/(04-relational)/graph-bfs/index.mdx rename to docs/(04-relational)/graph-bfs/index.mdx diff --git a/apps/docs/content/(04-relational)/graph-dfs/(problems)/133_clone-graph.mdx b/docs/(04-relational)/graph-dfs/(problems)/133_clone-graph.mdx similarity index 100% rename from apps/docs/content/(04-relational)/graph-dfs/(problems)/133_clone-graph.mdx rename to docs/(04-relational)/graph-dfs/(problems)/133_clone-graph.mdx diff --git a/apps/docs/content/(04-relational)/graph-dfs/(problems)/200_number-of-islands.mdx b/docs/(04-relational)/graph-dfs/(problems)/200_number-of-islands.mdx similarity index 100% rename from apps/docs/content/(04-relational)/graph-dfs/(problems)/200_number-of-islands.mdx rename to docs/(04-relational)/graph-dfs/(problems)/200_number-of-islands.mdx diff --git a/apps/docs/content/(04-relational)/graph-dfs/(problems)/417_pacific-atlantic-water-flow.mdx b/docs/(04-relational)/graph-dfs/(problems)/417_pacific-atlantic-water-flow.mdx similarity index 100% rename from apps/docs/content/(04-relational)/graph-dfs/(problems)/417_pacific-atlantic-water-flow.mdx rename to docs/(04-relational)/graph-dfs/(problems)/417_pacific-atlantic-water-flow.mdx diff --git a/apps/docs/content/(04-relational)/graph-dfs/index.mdx b/docs/(04-relational)/graph-dfs/index.mdx similarity index 100% rename from apps/docs/content/(04-relational)/graph-dfs/index.mdx rename to docs/(04-relational)/graph-dfs/index.mdx diff --git a/apps/docs/content/(04-relational)/topological-sort/(problems)/207_course-schedule.mdx b/docs/(04-relational)/topological-sort/(problems)/207_course-schedule.mdx similarity index 100% rename from apps/docs/content/(04-relational)/topological-sort/(problems)/207_course-schedule.mdx rename to docs/(04-relational)/topological-sort/(problems)/207_course-schedule.mdx diff --git a/apps/docs/content/(04-relational)/topological-sort/(problems)/210_course-schedule-ii.mdx b/docs/(04-relational)/topological-sort/(problems)/210_course-schedule-ii.mdx similarity index 100% rename from apps/docs/content/(04-relational)/topological-sort/(problems)/210_course-schedule-ii.mdx rename to docs/(04-relational)/topological-sort/(problems)/210_course-schedule-ii.mdx diff --git a/apps/docs/content/(04-relational)/topological-sort/index.mdx b/docs/(04-relational)/topological-sort/index.mdx similarity index 86% rename from apps/docs/content/(04-relational)/topological-sort/index.mdx rename to docs/(04-relational)/topological-sort/index.mdx index 57160dd..3bac838 100644 --- a/apps/docs/content/(04-relational)/topological-sort/index.mdx +++ b/docs/(04-relational)/topological-sort/index.mdx @@ -18,11 +18,11 @@ Before we get started on topological order, let's talk about directed graphs fir Topological sort is not unique. For example, for a graph like this: -![Topological sort example](../../../public/topo_intro_1.png) +![Topological sort example](/topo_intro_1.png) Both `[4, 5, 2]` and `[5, 4, 2]` are valid topological orders. -![Topological sort example](../../../public/topo_intro_2.png) +![Topological sort example](/topo_intro_2.png) Task 3 is completely independent of task 2 and 4, and it can be anywhere in the order as long as it is before task 1 which depends on it. All of the following ordering are valid topological orderings. @@ -46,15 +46,15 @@ For this algorithm, we systematically remove one node at a time, each time remov Here is a graphic to demonstrate the idea. - - - - - - - - - + + + + + + + + + ## Example Code diff --git a/apps/docs/content/(04-relational)/union-find/(problems)/323_number-of-connected-components-in-an-undirected-graph.mdx b/docs/(04-relational)/union-find/(problems)/323_number-of-connected-components-in-an-undirected-graph.mdx similarity index 100% rename from apps/docs/content/(04-relational)/union-find/(problems)/323_number-of-connected-components-in-an-undirected-graph.mdx rename to docs/(04-relational)/union-find/(problems)/323_number-of-connected-components-in-an-undirected-graph.mdx diff --git a/apps/docs/content/(04-relational)/union-find/(problems)/684_redundant-connection.mdx b/docs/(04-relational)/union-find/(problems)/684_redundant-connection.mdx similarity index 100% rename from apps/docs/content/(04-relational)/union-find/(problems)/684_redundant-connection.mdx rename to docs/(04-relational)/union-find/(problems)/684_redundant-connection.mdx diff --git a/apps/docs/content/(04-relational)/union-find/index.mdx b/docs/(04-relational)/union-find/index.mdx similarity index 100% rename from apps/docs/content/(04-relational)/union-find/index.mdx rename to docs/(04-relational)/union-find/index.mdx diff --git a/apps/docs/content/(05-decision-space)/1d-dynamic-programming/(problems)/198_house-robber.mdx b/docs/(05-decision-space)/1d-dynamic-programming/(problems)/198_house-robber.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/1d-dynamic-programming/(problems)/198_house-robber.mdx rename to docs/(05-decision-space)/1d-dynamic-programming/(problems)/198_house-robber.mdx diff --git a/apps/docs/content/(05-decision-space)/1d-dynamic-programming/(problems)/322_coin-change.mdx b/docs/(05-decision-space)/1d-dynamic-programming/(problems)/322_coin-change.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/1d-dynamic-programming/(problems)/322_coin-change.mdx rename to docs/(05-decision-space)/1d-dynamic-programming/(problems)/322_coin-change.mdx diff --git a/apps/docs/content/(05-decision-space)/1d-dynamic-programming/(problems)/70_climbing-stairs.mdx b/docs/(05-decision-space)/1d-dynamic-programming/(problems)/70_climbing-stairs.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/1d-dynamic-programming/(problems)/70_climbing-stairs.mdx rename to docs/(05-decision-space)/1d-dynamic-programming/(problems)/70_climbing-stairs.mdx diff --git a/apps/docs/content/(05-decision-space)/1d-dynamic-programming/index.mdx b/docs/(05-decision-space)/1d-dynamic-programming/index.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/1d-dynamic-programming/index.mdx rename to docs/(05-decision-space)/1d-dynamic-programming/index.mdx diff --git a/apps/docs/content/(05-decision-space)/backtracking/(problems)/39_combination-sum.mdx b/docs/(05-decision-space)/backtracking/(problems)/39_combination-sum.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/backtracking/(problems)/39_combination-sum.mdx rename to docs/(05-decision-space)/backtracking/(problems)/39_combination-sum.mdx diff --git a/apps/docs/content/(05-decision-space)/backtracking/(problems)/46_permutations.mdx b/docs/(05-decision-space)/backtracking/(problems)/46_permutations.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/backtracking/(problems)/46_permutations.mdx rename to docs/(05-decision-space)/backtracking/(problems)/46_permutations.mdx diff --git a/apps/docs/content/(05-decision-space)/backtracking/(problems)/78_subsets.mdx b/docs/(05-decision-space)/backtracking/(problems)/78_subsets.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/backtracking/(problems)/78_subsets.mdx rename to docs/(05-decision-space)/backtracking/(problems)/78_subsets.mdx diff --git a/apps/docs/content/(05-decision-space)/backtracking/index.mdx b/docs/(05-decision-space)/backtracking/index.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/backtracking/index.mdx rename to docs/(05-decision-space)/backtracking/index.mdx diff --git a/apps/docs/content/(05-decision-space)/greedy/(problems)/134_gas-station.mdx b/docs/(05-decision-space)/greedy/(problems)/134_gas-station.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/greedy/(problems)/134_gas-station.mdx rename to docs/(05-decision-space)/greedy/(problems)/134_gas-station.mdx diff --git a/apps/docs/content/(05-decision-space)/greedy/(problems)/53_maximum-subarray.mdx b/docs/(05-decision-space)/greedy/(problems)/53_maximum-subarray.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/greedy/(problems)/53_maximum-subarray.mdx rename to docs/(05-decision-space)/greedy/(problems)/53_maximum-subarray.mdx diff --git a/apps/docs/content/(05-decision-space)/greedy/(problems)/55_jump-game.mdx b/docs/(05-decision-space)/greedy/(problems)/55_jump-game.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/greedy/(problems)/55_jump-game.mdx rename to docs/(05-decision-space)/greedy/(problems)/55_jump-game.mdx diff --git a/apps/docs/content/(05-decision-space)/greedy/index.mdx b/docs/(05-decision-space)/greedy/index.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/greedy/index.mdx rename to docs/(05-decision-space)/greedy/index.mdx diff --git a/apps/docs/content/(05-decision-space)/intervals/(problems)/435_non-overlapping-intervals.mdx b/docs/(05-decision-space)/intervals/(problems)/435_non-overlapping-intervals.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/intervals/(problems)/435_non-overlapping-intervals.mdx rename to docs/(05-decision-space)/intervals/(problems)/435_non-overlapping-intervals.mdx diff --git a/apps/docs/content/(05-decision-space)/intervals/(problems)/56_merge-intervals.mdx b/docs/(05-decision-space)/intervals/(problems)/56_merge-intervals.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/intervals/(problems)/56_merge-intervals.mdx rename to docs/(05-decision-space)/intervals/(problems)/56_merge-intervals.mdx diff --git a/apps/docs/content/(05-decision-space)/intervals/(problems)/57_insert-interval.mdx b/docs/(05-decision-space)/intervals/(problems)/57_insert-interval.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/intervals/(problems)/57_insert-interval.mdx rename to docs/(05-decision-space)/intervals/(problems)/57_insert-interval.mdx diff --git a/apps/docs/content/(05-decision-space)/intervals/index.mdx b/docs/(05-decision-space)/intervals/index.mdx similarity index 97% rename from apps/docs/content/(05-decision-space)/intervals/index.mdx rename to docs/(05-decision-space)/intervals/index.mdx index b7eb845..1bdce9e 100644 --- a/apps/docs/content/(05-decision-space)/intervals/index.mdx +++ b/docs/(05-decision-space)/intervals/index.mdx @@ -23,7 +23,7 @@ Every one of those questions reduces to a single primitive: deciding whether two It is easier to state when two intervals do not overlap. Interval `a` sits entirely before interval `b` when `a` finishes before `b` begins, that is `a.end < b.start`. By symmetry they also miss each other when `b` ends before `a` starts. If neither of those holds, the intervals overlap. -![Overlap](../../../public/intervals_overlap-condition.svg) +![Overlap](/intervals_overlap-condition.svg) Negating `a.end < b.start` or `b.end < a.start` gives the positive test directly: two intervals overlap exactly when `a.start <= b.end` and `b.start <= a.end`. That pair of comparisons is the whole overlap check. @@ -31,13 +31,13 @@ Negating `a.end < b.start` or `b.end < a.start` gives the positive test directly When two intervals overlap, you can merge them into one interval that covers both. Its start is the earlier of the two starts, and its end is the later of the two ends: `[min(a.start, b.start), max(a.end, b.end)]`. The merged interval begins as soon as either interval starts, and it ends only once both have stopped. -![Merge formula](../../../public/intervals_merge-formula.svg) +![Merge formula](/intervals_merge-formula.svg) ## The Shared Region When two intervals overlap, the part they share is itself an interval. Its start is the later of the two starts, and its end is the earlier of the two ends: `[max(a.start, b.start), min(a.end, b.end)]`. The overlap begins only once both intervals have started, and it ends the moment either one stops. -![Overlap formula](../../../public/intervals_overlap-formula.svg) +![Overlap formula](/intervals_overlap-formula.svg) :::tip Merge grows, overlap shrinks. Merge uses `[min, max]` and covers both intervals. Overlap uses `[max, min]` and keeps only the part both share. diff --git a/apps/docs/content/(05-decision-space)/multi-d-grid-dp/(problems)/1143_longest-common-subsequence.mdx b/docs/(05-decision-space)/multi-d-grid-dp/(problems)/1143_longest-common-subsequence.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/multi-d-grid-dp/(problems)/1143_longest-common-subsequence.mdx rename to docs/(05-decision-space)/multi-d-grid-dp/(problems)/1143_longest-common-subsequence.mdx diff --git a/apps/docs/content/(05-decision-space)/multi-d-grid-dp/(problems)/62_unique-paths.mdx b/docs/(05-decision-space)/multi-d-grid-dp/(problems)/62_unique-paths.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/multi-d-grid-dp/(problems)/62_unique-paths.mdx rename to docs/(05-decision-space)/multi-d-grid-dp/(problems)/62_unique-paths.mdx diff --git a/apps/docs/content/(05-decision-space)/multi-d-grid-dp/(problems)/64_minimum-path-sum.mdx b/docs/(05-decision-space)/multi-d-grid-dp/(problems)/64_minimum-path-sum.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/multi-d-grid-dp/(problems)/64_minimum-path-sum.mdx rename to docs/(05-decision-space)/multi-d-grid-dp/(problems)/64_minimum-path-sum.mdx diff --git a/apps/docs/content/(05-decision-space)/multi-d-grid-dp/index.mdx b/docs/(05-decision-space)/multi-d-grid-dp/index.mdx similarity index 100% rename from apps/docs/content/(05-decision-space)/multi-d-grid-dp/index.mdx rename to docs/(05-decision-space)/multi-d-grid-dp/index.mdx diff --git a/apps/docs/content/index.mdx b/docs/index.mdx similarity index 100% rename from apps/docs/content/index.mdx rename to docs/index.mdx diff --git a/apps/docs/content/keywords.mdx b/docs/keywords.mdx similarity index 100% rename from apps/docs/content/keywords.mdx rename to docs/keywords.mdx diff --git a/apps/docs/content/progress.mdx b/docs/progress.mdx similarity index 100% rename from apps/docs/content/progress.mdx rename to docs/progress.mdx diff --git a/docs/site.json b/docs/site.json new file mode 100644 index 0000000..53553c0 --- /dev/null +++ b/docs/site.json @@ -0,0 +1,538 @@ +{ + "folders": { + "(01-linear-structures)": { + "title": "1. Linear Structures", + "order": 1 + }, + "(01-linear-structures)/binary-search": { + "title": "Binary Search", + "order": 5, + "icon": "search", + "display": "page" + }, + "(01-linear-structures)/hash-table": { + "title": "Hash Table", + "order": 1, + "icon": "hash", + "display": "page" + }, + "(01-linear-structures)/monotonic-stack": { + "title": "Monotonic Stack", + "order": 7, + "icon": "trending-up", + "display": "page" + }, + "(01-linear-structures)/prefix-sum": { + "title": "Prefix Sum", + "order": 2, + "icon": "sigma", + "display": "page" + }, + "(01-linear-structures)/sliding-window": { + "title": "Sliding Window", + "order": 4, + "icon": "app-window", + "display": "page" + }, + "(01-linear-structures)/stack": { + "title": "Stack", + "order": 6, + "icon": "layers", + "display": "page" + }, + "(01-linear-structures)/two-pointers": { + "title": "Two Pointers", + "order": 3, + "icon": "arrow-left-right", + "display": "page" + }, + "(02-nodal-grid)": { + "title": "2. Nodal & Grid", + "order": 2 + }, + "(02-nodal-grid)/hybrid-structures": { + "title": "Hybrid Structures", + "order": 9, + "icon": "combine", + "display": "page" + }, + "(02-nodal-grid)/linked-list": { + "title": "Linked List", + "order": 8, + "icon": "link", + "display": "page" + }, + "(02-nodal-grid)/matrix-index-math": { + "title": "Matrix Index Math", + "order": 10, + "icon": "grid-3x3", + "display": "page" + }, + "(03-hierarchical)": { + "title": "3. Hierarchical", + "order": 3 + }, + "(03-hierarchical)/binary-search-tree": { + "title": "Binary Search Tree", + "order": 13, + "icon": "git-fork", + "display": "page" + }, + "(03-hierarchical)/heap-priority-queue": { + "title": "Heap / Priority Queue", + "order": 14, + "icon": "triangle", + "display": "page" + }, + "(03-hierarchical)/tree-bfs": { + "title": "Tree BFS", + "order": 12, + "icon": "network", + "display": "page" + }, + "(03-hierarchical)/tree-dfs": { + "title": "Tree DFS", + "order": 11, + "icon": "folder-tree", + "display": "page" + }, + "(03-hierarchical)/trie": { + "title": "Trie", + "order": 15, + "icon": "spell-check", + "display": "page" + }, + "(04-relational)": { + "title": "4. Relational", + "order": 4 + }, + "(04-relational)/graph-bfs": { + "title": "Graph BFS", + "order": 17, + "icon": "radar", + "display": "page" + }, + "(04-relational)/graph-dfs": { + "title": "Graph DFS", + "order": 16, + "icon": "waypoints", + "display": "page" + }, + "(04-relational)/topological-sort": { + "title": "Topological Sort", + "order": 18, + "icon": "workflow", + "display": "page" + }, + "(04-relational)/union-find": { + "title": "Union-Find", + "order": 19, + "icon": "merge", + "display": "page" + }, + "(05-decision-space)": { + "title": "5. Decision Space", + "order": 5 + }, + "(05-decision-space)/1d-dynamic-programming": { + "title": "1-D Dynamic Programming", + "order": 21, + "icon": "columns-3", + "display": "page" + }, + "(05-decision-space)/backtracking": { + "title": "Backtracking", + "order": 20, + "icon": "undo-2", + "display": "page" + }, + "(05-decision-space)/greedy": { + "title": "Greedy", + "order": 23, + "icon": "coins", + "display": "page" + }, + "(05-decision-space)/intervals": { + "title": "Intervals", + "order": 24, + "icon": "calendar-range", + "display": "page" + }, + "(05-decision-space)/multi-d-grid-dp": { + "title": "Multi-D / Grid DP", + "order": 22, + "icon": "table", + "display": "page" + } + }, + "redirects": [ + { + "from": "/1", + "to": "/hash-table/two-sum" + }, + { + "from": "/2", + "to": "/hybrid-structures/add-two-numbers" + }, + { + "from": "/3", + "to": "/sliding-window/longest-substring-without-repeating-characters" + }, + { + "from": "/11", + "to": "/two-pointers/container-with-most-water" + }, + { + "from": "/15", + "to": "/two-pointers/3sum" + }, + { + "from": "/20", + "to": "/stack/valid-parentheses" + }, + { + "from": "/21", + "to": "/linked-list/merge-two-sorted-lists" + }, + { + "from": "/23", + "to": "/heap-priority-queue/merge-k-sorted-lists" + }, + { + "from": "/33", + "to": "/binary-search/search-in-rotated-sorted-array" + }, + { + "from": "/34", + "to": "/binary-search/find-first-and-last-position-of-element-in-sorted-array" + }, + { + "from": "/35", + "to": "/binary-search/search-insert-position" + }, + { + "from": "/39", + "to": "/backtracking/combination-sum" + }, + { + "from": "/42", + "to": "/two-pointers/trapping-rain-water" + }, + { + "from": "/46", + "to": "/backtracking/permutations" + }, + { + "from": "/48", + "to": "/matrix-index-math/rotate-image" + }, + { + "from": "/49", + "to": "/hash-table/group-anagrams" + }, + { + "from": "/53", + "to": "/greedy/maximum-subarray" + }, + { + "from": "/54", + "to": "/matrix-index-math/spiral-matrix" + }, + { + "from": "/55", + "to": "/greedy/jump-game" + }, + { + "from": "/56", + "to": "/intervals/merge-intervals" + }, + { + "from": "/57", + "to": "/intervals/insert-interval" + }, + { + "from": "/62", + "to": "/multi-d-grid-dp/unique-paths" + }, + { + "from": "/64", + "to": "/multi-d-grid-dp/minimum-path-sum" + }, + { + "from": "/70", + "to": "/1d-dynamic-programming/climbing-stairs" + }, + { + "from": "/71", + "to": "/stack/simplify-path" + }, + { + "from": "/73", + "to": "/matrix-index-math/set-matrix-zeroes" + }, + { + "from": "/74", + "to": "/binary-search/search-a-2d-matrix" + }, + { + "from": "/78", + "to": "/backtracking/subsets" + }, + { + "from": "/98", + "to": "/binary-search-tree/validate-binary-search-tree" + }, + { + "from": "/102", + "to": "/tree-bfs/binary-tree-level-order-traversal" + }, + { + "from": "/104", + "to": "/tree-dfs/maximum-depth-of-binary-tree" + }, + { + "from": "/121", + "to": "/sliding-window/best-time-to-buy-and-sell-stock" + }, + { + "from": "/125", + "to": "/two-pointers/valid-palindrome" + }, + { + "from": "/127", + "to": "/graph-bfs/word-ladder" + }, + { + "from": "/128", + "to": "/hash-table/longest-consecutive-sequence" + }, + { + "from": "/133", + "to": "/graph-dfs/clone-graph" + }, + { + "from": "/134", + "to": "/greedy/gas-station" + }, + { + "from": "/141", + "to": "/linked-list/linked-list-cycle" + }, + { + "from": "/146", + "to": "/hybrid-structures/lru-cache" + }, + { + "from": "/150", + "to": "/stack/evaluate-reverse-polish-notation" + }, + { + "from": "/153", + "to": "/binary-search/find-minimum-in-rotated-sorted-array" + }, + { + "from": "/155", + "to": "/stack/min-stack" + }, + { + "from": "/162", + "to": "/binary-search/find-peak-element" + }, + { + "from": "/167", + "to": "/two-pointers/two-sum-ii-input-array-is-sorted" + }, + { + "from": "/189", + "to": "/two-pointers/rotate-array" + }, + { + "from": "/198", + "to": "/1d-dynamic-programming/house-robber" + }, + { + "from": "/199", + "to": "/tree-bfs/binary-tree-right-side-view" + }, + { + "from": "/200", + "to": "/graph-dfs/number-of-islands" + }, + { + "from": "/205", + "to": "/hash-table/isomorphic-strings" + }, + { + "from": "/206", + "to": "/linked-list/reverse-linked-list" + }, + { + "from": "/207", + "to": "/topological-sort/course-schedule" + }, + { + "from": "/208", + "to": "/trie/implement-trie-prefix-tree" + }, + { + "from": "/209", + "to": "/sliding-window/minimum-size-subarray-sum" + }, + { + "from": "/210", + "to": "/topological-sort/course-schedule-ii" + }, + { + "from": "/211", + "to": "/trie/design-add-and-search-words-data-structure" + }, + { + "from": "/215", + "to": "/heap-priority-queue/kth-largest-element-in-an-array" + }, + { + "from": "/217", + "to": "/hash-table/contains-duplicate" + }, + { + "from": "/226", + "to": "/tree-dfs/invert-binary-tree" + }, + { + "from": "/230", + "to": "/binary-search-tree/kth-smallest-element-in-a-bst" + }, + { + "from": "/235", + "to": "/binary-search-tree/lowest-common-ancestor-of-a-binary-search-tree" + }, + { + "from": "/236", + "to": "/tree-dfs/lowest-common-ancestor-of-a-binary-tree" + }, + { + "from": "/238", + "to": "/prefix-sum/product-of-array-except-self" + }, + { + "from": "/242", + "to": "/hash-table/valid-anagram" + }, + { + "from": "/290", + "to": "/hash-table/word-pattern" + }, + { + "from": "/303", + "to": "/prefix-sum/range-sum-query-immutable" + }, + { + "from": "/322", + "to": "/1d-dynamic-programming/coin-change" + }, + { + "from": "/323", + "to": "/union-find/number-of-connected-components-in-an-undirected-graph" + }, + { + "from": "/344", + "to": "/two-pointers/reverse-string" + }, + { + "from": "/347", + "to": "/hash-table/top-k-frequent-elements" + }, + { + "from": "/387", + "to": "/hash-table/first-unique-character-in-a-string" + }, + { + "from": "/392", + "to": "/two-pointers/is-subsequence" + }, + { + "from": "/417", + "to": "/graph-dfs/pacific-atlantic-water-flow" + }, + { + "from": "/424", + "to": "/sliding-window/longest-repeating-character-replacement" + }, + { + "from": "/435", + "to": "/intervals/non-overlapping-intervals" + }, + { + "from": "/451", + "to": "/hash-table/sort-characters-by-frequency" + }, + { + "from": "/525", + "to": "/prefix-sum/contiguous-array" + }, + { + "from": "/560", + "to": "/prefix-sum/subarray-sum-equals-k" + }, + { + "from": "/567", + "to": "/sliding-window/permutation-in-string" + }, + { + "from": "/643", + "to": "/sliding-window/maximum-average-subarray-i" + }, + { + "from": "/684", + "to": "/union-find/redundant-connection" + }, + { + "from": "/704", + "to": "/binary-search/binary-search" + }, + { + "from": "/739", + "to": "/monotonic-stack/daily-temperatures" + }, + { + "from": "/853", + "to": "/monotonic-stack/car-fleet" + }, + { + "from": "/875", + "to": "/binary-search/koko-eating-bananas" + }, + { + "from": "/973", + "to": "/heap-priority-queue/k-closest-points-to-origin" + }, + { + "from": "/974", + "to": "/prefix-sum/subarray-sums-divisible-by-k" + }, + { + "from": "/977", + "to": "/two-pointers/squares-of-a-sorted-array" + }, + { + "from": "/994", + "to": "/graph-bfs/rotting-oranges" + }, + { + "from": "/1004", + "to": "/sliding-window/max-consecutive-ones-iii" + }, + { + "from": "/1143", + "to": "/multi-d-grid-dp/longest-common-subsequence" + }, + { + "from": "/1448", + "to": "/tree-bfs/count-good-nodes-in-binary-tree" + }, + { + "from": "/1832", + "to": "/hash-table/check-if-the-sentence-is-pangram" + } + ] +}