Skip to content

Commit 2d97f4f

Browse files
wislerttclaude
andcommitted
docs: generate per-problem docs pages
Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 384ad32 commit 2d97f4f

339 files changed

Lines changed: 31021 additions & 1494 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dev/active/01-docs-seo/context.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Context: Docs Site SEO — leetcode-py
2+
3+
Companion to a future `tasks.md` (checklist). This file holds everything an agent needs to plan SEO work: verified live findings, known bug classes, transferable decisions from the bakefile repo's completed SEO pass, and the opportunities specific to this repo. Dates are absolute. Verify anything marked "verify" against the live site before acting — this reflects 2026-08-24 state.
4+
5+
## Project facts
6+
7+
- Repo: `github.com/wislertt/leetcode-py`, main branch `main`.
8+
- Product: Python package that generates LeetCode practice environments (problem README, typed solution stub, parametrized pytest suite with 10+ cases, helpers, playground notebook) from JSON templates.
9+
- PyPI package name: `leetcode-py-sdk` (NOT `leetcode-py`). CLI: `lcpy`.
10+
- Scale: 307 problems under `leetcode/`, six collections (Grind 75, Grind, Blind 75, NeetCode 150, NeetCode 250, AlgoMaster 75).
11+
- Author: Wisaroot Lertthaweedech (`wisl.dev`). Same author as bakefile (`bakefile.wisl.dev`).
12+
- Docs: Mintlify site at `https://leetcode-py.wisl.dev` (custom domain; default deployment host `leetcode-py.mintlify.app`).
13+
14+
## Docs stack and conventions
15+
16+
- Mintlify, `docs/docs.json` at root of `docs/`. Theme `mint`. Site name `leetcode-py` (rendered title suffix is ` - leetcode-py`, 14 chars, so frontmatter titles must stay ≤~46 chars).
17+
- `docs/skill.md` is hand-written, served raw at `/skill.md`, overrides Mintlify's auto-generated one. Do not delete, no MDX-only components in it.
18+
- `markdown.instructions` in docs.json is injected into `llms.txt`, `llms-full.txt`, and Markdown page exports.
19+
- **Catalog pages are GENERATED** by `scripts/gen_catalog.py` (`docs/catalog/*.mdx` carry "GENERATED — do not edit" headers). Any change to catalog content = edit the generator, not the mdx.
20+
- Docs verification command: `bake docs-check` (runs `mintlify broken-links`). `bake docs` runs the dev server. Both defined in `bakefile.py`.
21+
- Examples policy: docs examples are backed by tests, copy verbatim from tested sources.
22+
23+
## Verified live state (2026-08-24)
24+
25+
Checked directly against production:
26+
27+
- `robots.txt`: Mintlify default, clean. AI bots allowed, `Content-Signal: ai-train=yes, search=yes, ai-input=yes`. Do not replace.
28+
- `sitemap.xml`: auto-generated, 21 pages, fresh `lastmod`.
29+
- `llms.txt`: HTTP 200.
30+
- Canonical: set globally (`https://leetcode-py.wisl.dev`).
31+
- Organization schema with `sameAs` (GitHub, PyPI, wisl.dev).
32+
- PyPI `[project.urls]` links to docs site (dofollow backlink).
33+
- README links deeply into docs pages (collections, why-page, CLI guide).
34+
35+
## Known bugs (same class as bakefile, verified live 2026-08-24)
36+
37+
1. **Duplicated title tag.** Homepage renders `<title>leetcode-py - leetcode-py</title>` (page title + site name identical). Fix: give `docs/index.mdx` a keyword title, e.g. "LeetCode practice environments in Python" or similar; the suffix carries the brand.
38+
2. **og:image resolves to wrong host.** Meta emits `https://leetcode-py.mintlify.app/img/brand/og-card-light.png` (default deployment host) because `seo.metatags.og:image` in docs.json is a relative path. Fix: absolute URL `https://leetcode-py.wisl.dev/img/brand/og-card-light.png`. Verify the custom domain serves `/img/brand/*` (bakefile's did).
39+
3. **Product-label titles.** "Installation", "Quickstart", "Catalog", "Troubleshooting", "lcpy", "bakefile" (contributing) are labels, not search queries. Same fix pattern as bakefile: query-shaped titles + `sidebarTitle` for anything long.
40+
41+
## Measurement status (head start — mostly done)
42+
43+
- GSC **Domain property `wisl.dev` verified 2026-08-24** via DNS TXT at Spaceship registrar. A Domain property covers ALL subdomains, so `leetcode-py.wisl.dev` is already verified. No new verification needed.
44+
- Bing Webmaster imported from GSC same day (covers Bing + DuckDuckGo + Yahoo).
45+
- **Pending:** submit `https://leetcode-py.wisl.dev/sitemap.xml` in GSC (Sitemaps page) and confirm Bing copied it via import.
46+
- **Pending:** GSC baseline (~2 weeks after sitemap submit): record indexed pages, impressions, queries.
47+
48+
## The big opportunity: 307 problem long-tail pages
49+
50+
This repo has an asset bakefile does not: 307 problems whose names are high-volume search queries ("two sum python", "longest substring without repeating characters python solution"). Current state:
51+
52+
- Catalog pages (`docs/catalog/blind-75.mdx` etc.) list problems but link them **offsite to GitHub** (`github.com/.../leetcode/two_sum/README.md`). All that content lives on github.com, not the docs domain. SEO value leaks offsite.
53+
- `leetcode/<problem>/README.md` per problem exists in-repo with rich content.
54+
55+
Decision space (for the planning agent, not settled):
56+
57+
- Generate per-problem docs pages from the same JSON/templates (mirror `gen_catalog.py` approach). 307 pages of real content, each targeting "[problem name] python". Mintlify scales fine; nav would need a hidden or collapsed section (`seo.indexing: "all"` exists for pages outside navigation).
58+
- Collection names are themselves high-volume keywords ("blind 75", "neetcode 150", "grind 75"). Those pages already exist — they mostly need title/description/intro strengthening, not creation.
59+
- Risks to weigh: thin-content pages if problem pages are just stubs, maintenance coupling to problem JSON.
60+
61+
## Transferable decisions from the bakefile SEO pass (2026-08-24, same author, same Mintlify setup)
62+
63+
Applied and verified on bakefile.wisl.dev — reuse the patterns:
64+
65+
- Homepage/landing `title` frontmatter = keywords ("Python task runner, Makefile alternative"), site-name suffix carries brand. Never put the brand word in the title when the suffix already appends it.
66+
- `sidebarTitle` frontmatter decouples long SEO titles from sidebar labels. Mintlify supports it natively.
67+
- `og:image` must be absolute; relative resolves against the mintlify.app host.
68+
- Query-shaped H2s on comparison pages ("Make vs bakefile") match "X vs Y" queries. FAQ sections only with genuinely matching Q&A content.
69+
- Troubleshooting: split into per-error pages with the exact error string as title, hub page links all with error strings as anchor text. Behavioral notes stay on the hub.
70+
- Internal linking: no orphan pages (footer/sidebar links don't count — content links only), first-mention descriptive anchors, both directions between related pages.
71+
- Titles: unique, 50–60 chars rendered (including the ` - leetcode-py` suffix).
72+
- Verification: `bake docs-check` after every docs edit; `curl -s <url> | grep '<title>'` for rendered-title checks after deploy.
73+
74+
## Off-repo SEO context (separate track, informational)
75+
76+
- No blog on the docs site (author decision). Articles will live on wisl.dev (planned Astro blog) linking into both docs sites. Cross-posting to Medium/dev.to only with canonical → wisl.dev.
77+
- Backlinks create authority: awesome-list PRs, launch posts, GitHub topics, SO answers.
78+
- Both docs sites are subdomains of wisl.dev; the GSC Domain property covers them all.
79+
80+
## Verification commands
81+
82+
```bash
83+
bake docs-check # broken links
84+
curl -s https://leetcode-py.wisl.dev/ | grep '<title>' # rendered title
85+
curl -s https://leetcode-py.wisl.dev/ | grep 'og:image' # og image host
86+
curl -s https://leetcode-py.wisl.dev/sitemap.xml | grep -c '<loc>' # page count
87+
```
88+
89+
## Non-goals (decided, do not relitigate)
90+
91+
- No blog section on the docs site.
92+
- No custom `robots.txt` / `sitemap.xml` (Mintlify defaults correct).
93+
- No structured-data changes beyond Mintlify's automatic JSON-LD + existing organization block.
94+
- Not moving docs off the subdomain.

.dev/active/01-docs-seo/tasks.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Tasks: Docs Site SEO — leetcode-py
2+
3+
Companion to `context.md` (facts, rationale, verification commands). Work phases in order. Decisions locked 2026-08-24:
4+
5+
- All 307 problem pages, 100% generated, no hand-written prose per problem
6+
- Problem pages out of nav, `seo.indexing: "all"` in docs.json
7+
- Problem page URLs: `/problems/<kebab-slug>/`
8+
- Collection head terms ("blind 75") = strengthen only, not chase
9+
- Non-goals in `context.md` stand
10+
11+
## Phase 0 — Measurement setup (do first)
12+
13+
- [x] Submit `https://leetcode-py.wisl.dev/sitemap.xml` in GSC (Sitemaps page, Domain property `wisl.dev`) — done 2026-08-24
14+
- [x] Confirm Bing Webmaster copied sitemap via GSC import — done 2026-08-24
15+
- GSC state 2026-08-24: sitemap Success (21 URLs), 0 indexed, homepage "Crawled - currently not indexed" (canonical correct, crawl clean). Clicked "Request indexing" on homepage (priority crawl queue). Baseline numbers on 2026-09-07 should reflect this
16+
- [ ] Record GSC baseline snapshot on **2026-09-07** (~2 weeks): indexed pages, impressions, queries (where to record: append to this file, section at bottom)
17+
18+
## Phase 1 — Bug fixes (bounded)
19+
20+
- [x] `docs/index.mdx`: keyword title, ≤46 chars, no brand word (suffix adds it). Done 2026-08-24: "LeetCode Practice Environments in Python". Post-deploy: verify rendered `<title>` no longer duplicates
21+
- [x] `docs/docs.json`: `seo.metatags.og:image` → absolute. Done 2026-08-24. `/img/brand/og-card-light.png` verified serving on custom domain (HTTP 200) before switching
22+
- [x] Query-shaped titles + `sidebarTitle` for label pages. Done 2026-08-24 (catalog/index moved to Phase 3, generated file):
23+
- `getting-started/installation` → "Install the lcpy CLI via pip or uv"
24+
- `getting-started/quickstart` → "lcpy Quickstart: First Problem in 5 Commands"
25+
- `getting-started/why-leetcode-py` → "LeetCode Practice Environment vs a Bare Editor"
26+
- `troubleshooting/index` → "Fixing lcpy Setup and Generation Errors"
27+
- `cli/lcpy` → "lcpy CLI: Generate and List Problems"
28+
- `cli/collections` → "Blind 75, Grind 75, NeetCode and Others"
29+
- `contributing/problem-creation` → "Add a New LeetCode Problem End to End"
30+
- `contributing/bakefile` → "Contributor Reference: bakefile Tasks"
31+
- [x] `bake docs-check` passed 2026-08-24 (no broken links)
32+
- [ ] Post-deploy `curl` checks: homepage title, og:image host on all pages
33+
34+
## Phase 2 — Tooling niche (repo already ranks #4-10 for these queries)
35+
36+
- [x] Retitle/strengthen `docs/practice/testing.mdx`. Done 2026-08-24: "LeetCode Python Test Cases with pytest" (38 chars, renders 52), sidebarTitle Testing, query-shaped description + intro split pointing to the local page
37+
- [x] New guide page `docs/practice/test-locally.mdx` (in nav, Practice group, after problem-anatomy): "Test LeetCode Solutions Locally with pytest" (43 chars, renders 57), sidebarTitle "Test locally". All output blocks captured from a real `lcpy gen -n 1` run in a temp dir (red 15 failed/3 passed, green 18 passed, `-k` 1 passed/17 deselected, root-run all-problems)
38+
- [x] Interlinks: test-locally <-> testing, -> problem-anatomy, -> cli/lcpy, -> cli/collections, -> catalog; inbound links from quickstart step 3, cli/lcpy, why-leetcode-py "The loop"
39+
- [x] `bake docs-check` passed 2026-08-24. Post-deploy title checks pending (see Phase 5)
40+
41+
## Phase 3 — Collection page strengthening (edit `scripts/gen_catalog.py`, never the mdx)
42+
43+
- [x] Query-shaped frontmatter per collection. Done 2026-08-24. Titles (all ≤46, unique, generator asserts both; `sidebarTitle` carries the old nav label):
44+
- grind-75 "Grind 75 in Python with Tested Solutions", blind-75 / neetcode-150 / neetcode-250 / neetcode / algo-master-75 same pattern
45+
- grind: "Grind Collection in Python: Tested Solutions"
46+
- catalog/index: "LeetCode Problem Catalog: All Collections" / sidebarTitle Catalog
47+
- catalog/all: "All LeetCode Problems in Python with Tests" / sidebarTitle All Problems
48+
- Descriptions rewritten query-shaped: "All {n} problems in the {list} list: each generates a tested Python practice environment..."
49+
- [x] Two-sentence keyword intro per collection in `COLLECTION_INTRO` (what the list is + tested environments; `lcpy gen -t <tag>` block stays directly below). neetcode-250 intro notes coverage still growing
50+
- [x] `catalog/neetcode.mdx` stays out of nav (hidden-page pattern untouched; docs.json nav unchanged)
51+
- [x] Regenerated 9 pages, `bake docs-check` passed, `gen_catalog.py --check` no drift, ruff + ty clean. Post-deploy title checks pending (Phase 5)
52+
53+
## Phase 4 — Problem pages (new generator, biggest chunk)
54+
55+
- [x] New `scripts/gen_problems.py` — done 2026-08-25. PoC on two_sum first, then all 310 (catalog grew from 307):
56+
- Slug: kebab-case (`two_sum``/problems/two-sum/`)
57+
- Title: tiered formula ("X Python Solution with Tests" → "X Python Solution" → bare name → word-boundary truncate), uniqueness + ≤46 asserted
58+
- Descriptions standalone (no title repeat), ≤120 chars asserted
59+
- Body: intro + `lcpy gen` CTA (both `-n` and `-s`, snake_case name) / Problem (statement, examples, constraints, follow-up) / Solution verbatim / Complexity table / Tags → catalog links
60+
- Executed pytest output dropped (user decision: not useful info). Case count lives in intro CTA sentence; CI backs the "passes" claim; generator runs no pytest = fast + fully deterministic
61+
- `mdx_escape` on all JSON prose (`<` `{` `}` outside fences/inline code); `validate_mdx` post-render assert catches future breakers at gen time
62+
- Data fix: walls_and_gates example had unbalanced nested fences (5 markers) + stray trailing backtick — fixed in JSON + README
63+
- [x] `docs/docs.json`: `seo.indexing: "all"` added 2026-08-25
64+
- [x] `gen_catalog.py`: Problem column → `/problems/<slug>/`; GitHub solution.py stays as the one secondary link per row
65+
- [x] Bakefile: `bake docs-problems` task added next to `docs-catalog`
66+
- [x] Regenerated everything. Checks passed 2026-08-25: `mintlify broken-links` clean, both `--check` modes no drift, all 310 problems linked from catalog/all. Post-deploy: llms.txt < 100k, sitemap ~331 URLs (Phase 5)
67+
- Note: `bake check-consistency` force-regenerates leetcode/ from templates and clobbered implemented solutions (recovered via git restore). Never run it casually; JSON+README edits must be applied to both manually
68+
69+
## Phase 5 — Post-deploy verification
70+
71+
- [ ] Immediately after deploy: `curl` rendered titles on ~5 sample problem pages, og:image host, sitemap count
72+
- [ ] GSC checkpoint **2026-09-21** (week 4, monitor only, no gate): index coverage rate, impressions by query, "Crawled - not indexed" volume. If problem pages show mass canonical-loss signals (inspect URL tool pointing at github.com) or soft-404, fix template and regenerate
73+
- [ ] Append findings + numbers to this file
74+
75+
## GSC snapshots
76+
77+
(baseline 2026-09-07 and week-4 2026-09-21 numbers go here)

bakefile.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ def docs_catalog(self):
103103
self.ctx.run("uv run python scripts/gen_catalog.py")
104104
console.success("Catalog pages regenerated into docs/catalog/")
105105

106+
@command("docs-problems", help="Regenerate docs/problems/ pages from JSON templates")
107+
def docs_problems(self):
108+
console.echo("Regenerating docs/problems/ pages...")
109+
self.ctx.run("uv run python scripts/gen_problems.py")
110+
console.success("Problem pages regenerated into docs/problems/")
111+
106112
@command("check-test-cases", help="Find problems with few test cases")
107113
def check_test_cases(
108114
self,

0 commit comments

Comments
 (0)