Skip to content

Commit 3916697

Browse files
authored
Merge pull request #296 from Programcode/t3/retitle-dedupe-ai-debate-pages
Give each AI-debate cluster page its own search intent (Task 2)
2 parents f9405c2 + de23584 commit 3916697

35 files changed

Lines changed: 265 additions & 185 deletions

File tree

‎docs/operations.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ The landing page and the free tools also exist in Hungarian and Spanish, each at
6868

6969
[`scripts/llms-full.mjs`](../scripts/llms-full.mjs) then writes `dist/llms-full.txt`: `llms.txt` followed by the visible text of `/`, `/about`, `/best-ai-for-debate` and `/press`, read from their built HTML. It is a build output, not a file in `public/`.
7070

71-
[`scripts/check-site.mjs`](../scripts/check-site.mjs) runs next and fails the build when an internal link, a `#fragment`, a sitemap URL or an `llms.txt` URL does not resolve (it applies the `vercel.json` redirects and rewrites), when JSON-LD does not parse or carries HTML entities, when a FAQPage question is not visible on its page, when the home page's entity graph has an unresolved `@id`, a `WebSite` or `SoftwareApplication` not named for the product, or no `Organization.sameAs`, when a page's `publisher` or `about` names an `@id` the home page does not define, when an indexed page other than `/terms`, `/privacy`, and `/expocit/sources` lacks its question H2 (`#short-answer`) as the first H2 (a page whose H1 is already that question may instead put the answer in a `#short-answer` paragraph before the first H2), when a page's first heading is not its H1 or a heading skips a level (H1 to H3), when an image has no alt text, a button has no name or a form field has no label, or when a page's `hreflang` links do not resolve, leave out the page itself or `x-default`, disagree with `<html lang>`, or differ from the set on a page they name.
71+
[`scripts/check-site.mjs`](../scripts/check-site.mjs) runs next and fails the build when an internal link, a `#fragment`, a sitemap URL or an `llms.txt` URL does not resolve (it applies the `vercel.json` redirects and rewrites), when JSON-LD does not parse or carries HTML entities, when a FAQPage question is not visible on its page, when the home page's entity graph has an unresolved `@id`, a `WebSite` or `SoftwareApplication` not named for the product, or no `Organization.sameAs`, when a page's `publisher` or `about` names an `@id` the home page does not define, when an indexed page other than `/terms`, `/privacy`, and `/expocit/sources` lacks its question H2 (`#short-answer`) as the first H2 (a page whose H1 is already that question may instead put the answer in a `#short-answer` paragraph before the first H2), when a page's first heading is not its H1 or a heading skips a level (H1 to H3), when an image has no alt text, a button has no name or a form field has no label, when two indexed pages share a `<title>`, or when a page's `hreflang` links do not resolve, leave out the page itself or `x-default`, disagree with `<html lang>`, or differ from the set on a page they name.
7272

7373
[`scripts/check-links.mjs`](../scripts/check-links.mjs) runs after it and fails the build when a sitemap page other than `/terms`, `/privacy` and `/expocit/sources` has fewer than 3 inbound links from other sitemap pages, or when the home page links to fewer than 15 distinct sitemap pages. `node scripts/check-links.mjs --report` prints every count after a build. The home page reaches the guides and tools through [`GuideHub.tsx`](../src/components/landing/GuideHub.tsx).
7474

75-
The guide pages in `public/<slug>/index.html` are generated, not hand-edited: run `python3 scripts/generate_seo_content.py` and commit its output. The same command writes the seven comparison and prep pages from [`scripts/seo_comparison_pages.py`](../scripts/seo_comparison_pages.py), each with its own layout, and lists them in `llms.txt`. Those pages link to `/best-ai-for-debate` while that page is still a separate draft; [`scripts/check-site.mjs`](../scripts/check-site.mjs) allows that one missing path until its file exists. Short pages live in its `PAGES` list; pages with long-form copy, their own highlight-card descriptions, and their own FAQs (which also feed the page's FAQPage JSON-LD) live in [`scripts/seo_guides.py`](../scripts/seo_guides.py). Each page's "Related" block shows up to six pages from its own topic cluster and one from each of two other clusters, picked from `CLUSTERS`; every page in `PAGES` must sit in exactly one cluster, and the generator stops when one does not. Paragraphs also get up to three in-body links from `LINK_PHRASES`, specific phrases first; `python3 scripts/generate_seo_content.py --links` lists the links each page got. A guide entry can set a later `modified` date for that page alone; no page is dated earlier than `CONTENT_DATE`, which covers the copy every guide shares, including the related and in-body links, so move it when `CLUSTERS` or `LINK_PHRASES` change. Sections hold paragraphs and `h3`, `steps`, `list`, `table` and `example` blocks, plus a `speech_times` block that builds the speech-times table from the free timer's formats in [`src/lib/free-tools/formats.ts`](../src/lib/free-tools/formats.ts), so the guide and the timer cannot disagree; a guide's `definition` (question, term, text) renders a definition list under the short answer and a `DefinedTerm` in its JSON-LD; paragraphs, list items and table cells can hold Markdown-style links to a site path or, for a primary source, an `https://` URL. Every guide needs FAQs of its own in `seo_guides.py`; the generator refuses a page without them, so no two pages share boilerplate questions. [`src/test/seo/guide-content.test.ts`](../src/test/seo/guide-content.test.ts) fails when a guide has fewer than 700 words outside its related and About blocks, or when a sentence longer than 60 characters appears on more than three guides.
75+
The guide pages in `public/<slug>/index.html` are generated, not hand-edited: run `python3 scripts/generate_seo_content.py` and commit its output. The same command writes the seven comparison and prep pages from [`scripts/seo_comparison_pages.py`](../scripts/seo_comparison_pages.py), each with its own layout, and lists them in `llms.txt`. Those pages link to `/best-ai-for-debate` while that page is still a separate draft; [`scripts/check-site.mjs`](../scripts/check-site.mjs) allows that one missing path until its file exists. Short pages live in its `PAGES` list; pages with long-form copy, their own highlight-card descriptions, and their own FAQs (which also feed the page's FAQPage JSON-LD) live in [`scripts/seo_guides.py`](../scripts/seo_guides.py). Each page's "Related" block shows up to six pages from its own topic cluster and one from each of two other clusters, picked from `CLUSTERS`; every page in `PAGES` must sit in exactly one cluster, and the generator stops when one does not. Paragraphs also get up to three in-body links from `LINK_PHRASES`, specific phrases first; `python3 scripts/generate_seo_content.py --links` lists the links each page got. A guide entry can set a later `modified` date for that page alone; no page is dated earlier than `CONTENT_DATE`, which covers the copy every guide shares, including the related and in-body links, so move it when `CLUSTERS` or `LINK_PHRASES` change. Sections hold paragraphs and `h3`, `steps`, `list`, `table` and `example` blocks, plus a `speech_times` block that builds the speech-times table from the free timer's formats in [`src/lib/free-tools/formats.ts`](../src/lib/free-tools/formats.ts), so the guide and the timer cannot disagree; a guide's `definition` (question, term, text) renders a definition list under the short answer and a `DefinedTerm` in its JSON-LD; paragraphs, list items and table cells can hold Markdown-style links to a site path or, for a primary source, an `https://` URL. A guide can set a `disclosure` line, shown under its byline, and a numbered list: `items` (name, URL) pairs that become an `ItemList` in its JSON-LD, each matched in order by an `h3` that starts "1. Name"; a title with `{item_count}` gets the real count, and the generator stops when the headings and `items` disagree. Every guide needs FAQs of its own in `seo_guides.py`; the generator refuses a page without them, so no two pages share boilerplate questions. [`src/test/seo/guide-content.test.ts`](../src/test/seo/guide-content.test.ts) fails when a guide has fewer than 700 words outside its related and About blocks, or when a sentence longer than 60 characters appears on more than three guides.
7676

7777
Every guide opens with the reader's main question as its first H2 and an answer of at most 100 words, kept in [`scripts/seo_answers.py`](../scripts/seo_answers.py); the generator refuses a page without one or with a longer answer. Each guide shows visible "Published" and "Updated" dates (`PUBLISHED_DATE` or a `published` key, and the `modified` date) that match its Article JSON-LD. Prices and plan facts are stated "as of" the month in `factsCheckedOn` in `src/config/site.json`; move that date when you recheck them, and the pricing page's About block, `llms.txt` and the landing page follow. Every other guide has a one-line About that links to the landing page and pricing instead of repeating the plan facts. Copy in `seo_guides.py` and `seo_answers.py` that names the month itself (prices and competitor facts) needs the same edit: search those files for "As of". The generator also writes `public/llms.txt`, the plain summary for AI agents. `public/robots.txt` allows AI crawlers by name in their own group, which repeats the private-route rules because a crawler that matches a named group ignores `*`.
7878

0 commit comments

Comments
 (0)