Repository navigation
SEO Content Architecture
Canonical source:
docs/05-frontend/seo-content-architecture.md— this page is rendered from it on every publish; edit the source.
Audited: 2026-08-30 · Site: https://claw-ai.co ·
Scope: every public route under app/(marketing), plus every discovery
document derived from the content registry.
Companion to multilingual-discovery.md, which
documents the mechanism. This document is the content decision: what exists,
what is missing, what gets built, and what was deliberately refused.
The discovery layer is finished and self-maintaining. Do not rebuild any of it, and do not hand-write anything it derives.
PUBLIC_CONTENT_DEFINITIONS (constants/content-registry.constants.ts) is the
single authority. From one registry entry, a page automatically reaches:
| Surface | Route | Derived how |
|---|---|---|
| Sitemap index | /sitemap.xml |
Chunked per locale, counts pages rather than assuming one chunk |
| Sitemap documents | /sitemaps/{locale}/pages-N.xml |
getIndexablePagesForLocale |
| Global feed | /rss.xml |
All 13 locales, one fixed URL, each item language-tagged |
| Locale feed |
/feed.xml, /{locale}/feed.xml
|
Locale-negotiated |
| Topic + chat feeds |
/feeds/topics.xml, /feeds/chats.xml
|
RssFeedKind |
| Robots | /robots.txt |
Named crawler groups, sitemap advertised |
| AI discovery | /llms.txt |
Registry |
| Footer | marketing-footer |
Published entries only |
| Canonical + hreflang + OG + Twitter | every page | buildRequestPublicPageMetadata |
| Feed advertisement | <link rel="alternate"> |
alternates.types, both locale and global |
Four tests fail the build if the chain breaks: sitemap-coverage.test.ts,
lighthouse-coverage.test.ts, marketing-footer.test.tsx,
content-registry.utility.test.ts. Lighthouse CI asserts accessibility,
best-practices and SEO at minScore: 1 — a hard error, not a warning.
Consequence for this work: a new page is a registry entry plus content. It is never a hand-written canonical, sitemap line, feed item or footer link.
| Group | Paths |
|---|---|
| Product |
/, /about, /features, /how-it-works, /pricing, /use-cases
|
| Models | /supported-models |
| Coding |
/coding-agent, /coding-agent/install
|
| Private AI |
/local-first-ai, /architecture, /security-and-privacy
|
| Comparison |
/compare + 9 rivals (chatgpt, claude, gemini, perplexity, copilot, kimi, qwen, glm, deepseek) |
| Support |
/faq, /contact
|
| Legal |
/terms, /privacy, /cookies, /acceptable-use
|
| Dynamic | /share/chat/[publicShareId] |
Locales: en ar fr it de es ru pt hi ja th fa zh — 13, two RTL (ar, fa).
PLANNED_CONTENT_CONFIGS declares multi-provider-ai, model-routing,
advanced-orchestration, memory-and-context, rag-and-files,
workspace-connectors, desktop-agent, self-hosting, ai-safety,
observability, supported-providers, and four guides/* slugs. None has a
route. They are NOINDEX with empty locales, so they leak into nothing — but
they encode a different information architecture from the one being built.
Four guides/* slugs collide with the /learn GUIDE hub decided in §4; a
fifth, workspace-connectors, was added to this list once the boardroom
review (§8) put /integrations in scope — it collides with that WORKSPACE
hub the same way.
Resolved: the hub scheme wins (see §4). Colliding PLANNED slugs are retired rather than left as a second, contradictory roadmap.
The page states its own policy:
"A static list of fashionable model versions becomes inaccurate quickly and can imply credentials that are not configured."
It names implemented provider families and defers to the live catalog. It also records that AWS Bedrock is connector scaffolding whose model synchronisation is not implemented, and is therefore excluded on purpose.
This is correct and stays. It does, however, mean there is no existing source for model-level content, and no public catalog API — the live registry sits in routing-service behind auth and varies per deployment.
Resolved (2026-09-09): a curated, dated MODEL_FACTS module (§3) becomes
the single source for every model-level page, explicitly framed as a reviewed
editorial snapshot rather than the live catalog.
Superseded (2026-09-12). The premise above — "no public catalog API" — was true when written and is not any more: connector-service publishes one, and the model-level pages read it. The editorial snapshot had drifted to naming 16 models where the product served 170, and to Claude Opus 4 where the deployment ran Opus 5. See §3 for what replaced it and why the boundary objection is answered rather than ignored.
Update (2026-09-30). /supported-models now renders the SAME live roster the home
page uses (ModelRosterSection: each provider with a real model count and a sample of
real model names, plus a dialog with the full list), read from the connector catalog
and empty when the catalog is unavailable. It is the public "models available" page
and is in the sitemap (/sitemaps/en/pages-1.xml lists /en/supported-models,
checked live 2026-09-30; the registry marks it FeedEligibility.PUBLISHABLE and
AdEligibility.INELIGIBLE, so the roster changes no ad eligibility). The private
/models dashboard stays authenticated. No new route was added: every home section
already has its own indexed page (/pricing, /supported-models, /how-it-works,
/features, /coding-agent, /features/pay-as-you-go-credit,
/features/administration-and-access-control, /local-first-ai), and repeating a
section's copy on a second URL is the thin, duplicated content the AdSense
eligibility work guards against
(adsense-eligibility.md).
Batch 3 deviation, discovered at build time: this document names the
cluster /models. That path is already taken — src/app/(portal)/models/**
is a private, authenticated route (the model catalog/discovery dashboard
linked from the sidebar). A marketing page at the same path would collide
with it, and sitemap-google-readability.test.ts catches exactly this
(PRIVATE_ROUTE_PREFIXES in constants/private-route-prefixes.constants.ts
includes /models). The cluster was built at /model-providers instead —
same six children (openai, anthropic, google, deepseek, xai,
local-ai), same registry category (ContentCategory.PROVIDERS), same
AdEligibility.INELIGIBLE / FeedEligibility.PUBLISHABLE decisions. Every
other detail in §3–§8 below is unchanged; read /models there as
/model-providers.
buildGlobalRssResponse maps every indexable registry page into the feed,
so /terms, /privacy, /cookies, /acceptable-use and /contact are feed
items whose pubDate is the site-wide review date. A subscriber is notified
about the cookie policy.
Resolved: the feed filters to categories with genuine publication semantics. The mechanism is a category predicate, so the rule stays in one place and a new cluster opts in deliberately.
Both rank for one broad head term and cover many intents in one document. Each is the natural parent of a cluster and currently has no children.
Resolved: both become hubs, keeping their existing URL and improving rather than replacing them. No redirect, no lost equity.
/use-cases built 2026-09-09 — hub (unchanged URL, unchanged registry
status) + 7 task pages: coding-and-development,
research-and-fact-finding, writing-and-editing, comparing-model-answers,
workspace-automation, structured-data-extraction,
private-and-local-deployment. The plan named only "task pages" with no
fixed list for this cluster; these 7 were chosen against real capabilities —
PlanFeature (RESEARCH_MODE, COMPARE_MODE, JUDGE_MODE, CRITIC_REVIEW,
CONTEXT_PACKS, MEMORY, WORKSPACES, WEB_EXTRACT), PaygSurface
(CODING_AGENT, COMPARE, JUDGE, WORKSPACE_ACTION), WorkspaceProvider
(14 connectors) and RoutingMode (LOCAL_ONLY, PRIVACY_FIRST) — not
invented. writing-and-editing deliberately reuses a ModelFitTask string
value: the two live at different route prefixes (/use-cases/* vs.
/model-fit/*) so there is no collision, and the two pages answer different
questions — /model-fit/* is "which model fits this task" (ad-INELIGIBLE,
names and evaluates models), /use-cases/* is "how do I do this job with
ClawAI" (ad-ELIGIBLE per §8.2, product-capability-oriented). Every task
cross-links to /learn/*, /model-fit/* and /integrations rather than
re-explaining a concept already covered by those clusters. The pre-existing
legacy grid section (ten shorter jobs, marketing-use-cases.constants.ts) is
unchanged; the new cluster is an additional "go deeper" section beneath it.
/features built 2026-09-09 — hub (unchanged URL, unchanged registry
status) + 6 capability pages: model-routing-and-orchestration,
memory-and-context, workspace-connectors, file-and-document-handling,
observability-and-transparency, security-and-data-handling. The plan gave
a starting split of 6 as a suggestion, not a fixed list; this split was
verified against the actual code rather than applied as-is. It maps directly
onto 6 of the 9 existing /features sections — providers is deliberately
excluded (already its own /model-providers cluster) and generation is
folded into file-and-document-handling rather than kept separate (it is one
mechanism — image/document/research output — not a distinct capability area).
routing and orchestration are merged into one page because RoutingMode
(7 members: AUTO, MANUAL_MODEL, LOCAL_ONLY, PRIVACY_FIRST,
LOW_LATENCY, HIGH_REASONING, COST_SAVER) and the PaygSurface.ORCHESTRATION
"nine advanced orchestration labs" (Compare, Consensus, Escalation, Best-of-N,
Repair, Verify, Role packs, Pipelines, Judge/Critic — confirmed against the
marketing.features.orchestration.* and marketing.features.routing.* i18n
keys) are one continuous mechanism: which model answers, and what happens
when one model is not enough. Every other page is grounded the same way:
memory-and-context in PlanFeature.MEMORY / PlanFeature.CONTEXT_PACKS;
workspace-connectors in the 14 WorkspaceProvider members and
PaygSurface.WORKSPACE_ACTION; file-and-document-handling in
PaygSurface.FILE_GENERATION / PaygSurface.IMAGE and the
marketing.features.files.* / marketing.features.generation.* i18n copy;
security-and-data-handling in the literal mechanisms already shipped and
described in marketing.features.security.* (Argon2, AES-256-GCM, TLS, RBAC,
per-service database isolation) — explicitly not a compliance claim, mirroring
that same i18n block's own "ClawAI holds no compliance certifications today"
line. Every page cross-links to /learn/* rather than re-explaining a concept
already covered there, and to /use-cases/* for the job-shaped version of the
same capability. The pre-existing 9 sections on /features
(FeaturesHeroSection, FeaturesProvidersSection, FeaturesRoutingSection,
FeaturesOrchestrationSection, FeaturesMemorySection,
FeaturesFilesSection, FeaturesWorkspaceSection,
FeaturesGenerationSection, FeaturesObservabilitySection,
FeaturesSecuritySection) are unchanged; the new cluster is an additional
"go deeper" section (FeaturesCapabilityCardsSection) beneath them, same
pattern as the /use-cases batch above.
Flagship pages added 2026-09-26 (full-AI-workspace repositioning, slogan
"Every AI, one workspace") — 11 more capability pages on the same route and
component: multimodal-ai, files-from-chat, smart-attachments,
narrated-research-and-web-crawling, orchestration-labs,
conversation-power-tools, read-aloud, image-generation, reliability,
pay-as-you-go-credit, administration-and-access-control. Copy lives in
constants/features-flagship-content/<locale>.constants.ts and is merged into
FEATURES_CLUSTER_CONTENT_BY_LOCALE by mergeFeaturesClusterDictionary, so
the original six pages' files are untouched. The flagship set has its own
review date (FEATURES_FLAGSHIP_REVIEW_DATE, via
getFeatureCapabilityReviewDate). Every claim was audited against shipped
code first; the audit's "do not claim" list is binding on edits:
- no public status page (none exists — observability stays admin-side);
- no signed/expiring upload URLs — only generated files expire (1 hour, then a free rebuild);
- no OCR, captcha solving or scraper sidecars as standard (off by default);
- the provider breaker covers exhausted-credit accounts only, not outages;
- no team workspaces, seats, invitations, SSO/SAML or SCIM — the
administration page says "not yet" in every locale, and
marketing.home.enterprise.point3Bodyno longer mentions SSO; - no plan-tier promises for gated features (image generation, read aloud, vision helper, labs, file-generation allowances, video length are DB rows), and no prices or top-up amounts (versioned price rows only);
- pay-as-you-go credit is off until an operator enables it; gateways are PayPal and Paymob, not Stripe.
"15+ providers & smart routing", Coding Agent and local-first/privacy were
NOT given new pages: they are already covered by /model-providers,
/features/model-routing-and-orchestration, /coding-agent,
/local-first-ai and /security-and-privacy, and a second page on the same
topic would be thin duplicate content (rule 38). The homepage gained two
positioning bands (HomeValueBandSection with MARKETING_HOME_PAYG_BAND and
MARKETING_HOME_TEAMS_BAND) and the organisations band was reframed as
local-first; the footer gained a Features column
(MARKETING_FOOTER_FEATURE_PATHS, resolved server-side). Adding the pages
raised RSS_GLOBAL_MAX_ITEMS to 4,000. Guarded by
features-flagship-content.test.ts (discovery, real translations, audited
omissions) and repositioning-slogan-keys.test.ts (slogan keys × 13 locales).
Video sections (2026-10-13). Twelve placements show video, all named in
MarketingVideoPlacement and driven by ONE registry, MARKETING_VIDEOS
(constants/marketing-videos.constants.ts): the "ClawAI Threads in 30 seconds" video on
/features/threads, /threads/discover and the home Threads band; a neutral "Watch the ClawAI
Threads video in three parts" strip on /features/threads; "See ClawAI in action" on the home page
(three Shorts); the two "free AI" Shorts on /pricing, /supported-models and /model-providers;
one open-source Short each on /local-first-ai, /how-it-works, /architecture and
/integrations. A page adds <MarketingVideoSection placement={...} />.
What the copy may claim: we do not know what each clip narrates, so the self-hosted clips are
described neutrally ("ClawAI Threads video, part 1 of 3") with no step story, and the Shorts show
their YouTube title VERBATIM from the registry (never translated or paraphrased; the four
same-title Shorts get #1 to #4 only to tell them apart). There are no spoken-language badges,
because the audio language is unconfirmed; the Arabic locale puts Arabic-titled Shorts first by
the title's script, not by a claim about the audio.
Self-hosted mp4s (MarketingVideo) are a click-to-load facade like the Shorts: a lazy poster and a
play button in a reserved 16:9 box, so no <video> and no mp4 request exists before the click;
click, Enter or Space swaps in <video controls autoplay preload="metadata"> and moves focus to
it. They get VideoObject JSON-LD. The clips carry an audio track; muted stays only for the
media-has-caption lint rule, and the owner supplies .vtt captions if the clips contain speech.
YouTube Shorts (YouTubeShortFacade) are the same pattern over a local thumbnail, so there is no
YouTube request before the click, and the CSP frame-src names only
https://www.youtube-nocookie.com. next.config.mjs serves /marketing/videos/:path* with
Cache-Control: public, max-age=31536000, immutable: files are replaced by a new name, never
edited in place (no test covers next.config headers, so this is documented here). Copy is
marketing.videos.* in marketing-videos-translations.ts (13 locales). Each section adds a
heading and a line of text, so a page never becomes video-only (rule 38).
Nothing covers the informational half of the funnel: what model routing is, what orchestration is, which model suits which task, what each connector actually does. Every existing page is bottom-of-funnel. This is the entire opportunity.
/models/qwen, /models/kimi and /models/glm were proposed. ConnectorProvider
has no such members; Qwen, Kimi and GLM exist only as /compare rivals
(competitor products), and as open-weight models a user may run locally through
Ollama. A provider page would claim a connector that does not exist.
Resolved: not built as provider pages. Their open-weight local story is
covered honestly by /models/local-ai.
Deferred twice with a written reason (needs a hosted key file, a new env var, and the full 18-item infra propagation; Bing Webmaster manual submission covers the same ground), reaffirmed 2026-08-30, and reversed on 2026-09-07 once the operator generated a key in Bing Webmaster Tools.
Two of the three original costs turned out not to apply:
-
No env var, so no 18-item propagation. The key is a committed constant
(
constants/indexnow.constants.ts). It cannot be a secret — the protocol verifies ownership by fetching the same value back from a public URL — so hiding it in.envwould have spread a published string across seven compose files and both installers, and introduced a way for the served file and the submittedkeyto drift per environment. That drift is answered with a flat 403 that names nothing. -
The key file is a route, not hosted infrastructure.
app/<key>.txt/route.tsserves the bare key and 404s whenshouldNoIndexEverything()— a staging host must not claim ownership of the canonical origin, or its URLs could be submitted as the real site's.
What remains true is that manual submission covers the same ground. The reason
to automate it anyway is timing: tools/indexnow/index.mjs runs after a
successful production deploy, so a page edited at 14:00 is submitted at 14:00
rather than whenever someone next opens the console.
It submits changes, not the site. IndexNow answers repeated submission of
unchanged URLs with 429 and reads it as spam, and the sitemap already publishes
lastmod. The submitter walks the sitemap index, follows each child, and takes
only entries whose lastmod falls inside a window (default 7 days). Undated and
foreign-host entries are dropped — one foreign URL makes the endpoint reject the
whole batch with 422.
Consequences worth knowing before reading the logs:
-
Most deploys will submit nothing, and that is correct. Marketing pages carry a hand-maintained
lastReviewed, so they only qualify when their content genuinely changed. As measured on 2026-09-07: 0 URLs inside the 7-day window, 806 with the window opened to ten years. -
A first-time seed is a deliberate, separate action. To submit everything once, run the submitter with the window widened:
SITE_URL=https://claw-ai.co \ INDEXNOW_KEY=<key> \ INDEXNOW_WINDOW_DAYS=3650 \ node tools/indexnow/index.mjs
-
A failure never fails the deploy. The step is
continue-on-errorand the script swallows its own errors: search-engine notification is not part of the deploy's contract, and a third party's 429 must not turn a healthy release red. Read the step's log for 403 (key file not served), 422 (a URL outside the host) or 429 (submitted too often).
Found while verifying the capability inventory. Three counts are written as
prose into public-comparison-content/<locale>.constants.ts — thirteen
copies each — and two are now wrong:
| Claim on the site | Reality in code | Verdict |
|---|---|---|
| "Twelve workspace connectors" |
WorkspaceProvider has 14, all with adapters |
Wrong. Google Calendar and Outlook Calendar were added and never propagated |
| "Five routing modes" |
RoutingMode has 7, and ROUTING_MODE_LABELS renders all 7 in the product |
Wrong. The homepage collapses three router biases into one bullet; the count then under-reports what a user actually sees |
| "Nine frontier model families" |
ConnectorProvider has 5 cloud providers + 2 local runtimes |
Unverifiable as written. Nothing in the code produces the number nine |
This is the drift the brief warns about, and it happened in the ordinary way: someone added a connector and had no reason to think a comparison page in Thai depended on the count.
Resolved: counts are derived, never typed. Content strings take a
placeholder — '{connectorCount} workspace connectors' — and the count comes
from the enum at render time, through the same interpolation the comparison
pages already use for {rival}
(utilities/public-comparison.utility.ts). A count can then only be wrong if
the enum is wrong, and adding a connector updates 13 locales for free.
PRODUCT_COUNTS in the shared facts module owns every derived number.
Every current page owns a distinct intent. /compare/* (rival products) and the
new /compare/models/* (model-vs-model) are different intents and must stay
visibly different, which is the one real cannibalisation risk introduced here.
Superseded 2026-09-12. The source is now the live connector catalog, not a frontend constant.
GET /api/v1/internal/connectors/public-catalog, fetched server-side and read
through selectModelsForProviderPage. It is built from the SAME query that
fills the in-app model picker (findExposedForCatalog), so a page cannot
advertise a model nobody can select, nor omit one users have.
constants/model-facts.constants.ts held 16 models across 5 providers, copied
by hand from model-cost-seed.constants.ts and carrying a manual review date.
The design below argued that was safer than crossing a service boundary. In
practice it failed in the way hand-copied data always does:
- It drifted from the product. The seed it mirrored is a PRICING list, not an availability list — this deployment actually exposes 170 models across 4 providers, and the page named 16.
- It was not even the only copy.
subscription-marketing.constants.tsheld a SECOND roster for the home page that disagreed with it — naming Moonshot Kimi, Zhipu GLM, Alibaba Qwen and Amazon Bedrock (none of which can serve a model here), plus MiniMax and NVIDIA models that appear nowhere in this codebase. Its test asserted those names, so the fiction was load-bearing. - Nothing could catch either. Both were internally consistent and fully typed. The drift detector D1 below describes a manual check that was never run.
The boundary concern the original decision raised was real, and is answered rather than ignored: the endpoint is service-token guarded, the token never reaches a browser, and the payload carries no price, no rate and no connector identity (rule 37). The qualitative-cost rule survives — the backend simply does not send a number for a page to leak.
MODEL_PAGE_CONNECTOR_PROVIDERS maps each /model-providers/<page> to the
connector providers behind it, because the two vocabularies are different:
ConnectorProvider names an adapter, ModelProviderPage names a page, and
local-ai is deliberately one page over two runtimes.
The editorial copy per provider — positioning, strengths by task, FAQ — is
unchanged and still lives in constants/models-content/<locale>.constants.ts in
13 locales. Only the MODEL LIST moved. A failed catalog fetch renders that copy
with an "unavailable" note rather than an invented list.
Rules:
-
Dated.
MODEL_FACTS_REVIEW_DATErenders on every consuming page and drivesdateModified. Moving it without re-checking the claims is prohibited. - Qualitative, never benchmarked. No invented scores, no fabricated latency, no leaderboard positions.
- One copy. A page that needs a model fact imports it. A second hardcoded list anywhere is a defect.
- Provider families are the honest unit. Where a claim would need a version number to be true, it belongs to the family, not the page.
Existing URLs are unchanged. Everything below is additive.
| Hub | Children | Intent |
|---|---|---|
/learn |
concept pages (what-is-*, cloud-ai-vs-local-ai, ollama-vs-llamacpp, …) |
Informational |
/model-providers |
openai, anthropic, google, deepseek, xai, local-ai (built at /model-providers, not /models — see note below) |
Commercial investigation |
/compare/models |
model-vs-model pairs | Comparison |
/model-fit |
task pages (coding, complex-reasoning, writing-and-editing, research-with-sources, private-local-workloads) — built at /model-fit, not /best-ai-model//models/for — see note below |
Commercial investigation |
/integrations |
14 verified connectors | Commercial investigation |
/use-cases (exists)
|
task pages | Commercial investigation |
/features (exists)
|
capability pages | Commercial investigation |
/solutions |
role pages | Commercial investigation |
/industries |
private-deployment verticals | Commercial investigation |
/prompts |
prompt guides | Informational |
/tools |
model selector | Transactional-adjacent |
Cannibalisation rules, decided once:
-
/best-ai-model/codingis the single canonical page for "best AI/LLM/model for coding/programming"./learn/best-ai-model-for-codingis not built. -
/use-cases/*targets the task;/solutions/*targets the role. A role page that would only restate a task page is not built. -
/compare/*is product vs product;/compare/models/*is model vs model. Different H1 shapes, different tables, cross-linked. -
/learn/*defines a concept;/features/*documents ClawAI's implementation of it. Every learn page links to its feature page and vice versa; neither duplicates the other's job.
Each batch is independently gated, committed and pushed.
| Batch | Contents | Priority |
|---|---|---|
| 0 | This document, retire the four guides/* PLANNED slugs, shared SEO components, cluster scaffolding, RSS publication-semantics fix |
P0 |
| 1 |
/learn hub + concept pages |
P0 |
| 2 |
/integrations hub + 14 connector pages, retire the workspace-connectors PLANNED slug |
P0 |
| 3 |
Built 2026-09-09 — /model-providers hub (not /models, see note below) + 6 provider pages, MODEL_FACTS
|
P0 |
| 4 |
Built 2026-09-09 — /use-cases hub (unchanged URL) + 7 task pages, see F4 note above |
P1 |
| 5 |
/model-fit half built 2026-09-09 — hub + 5 task pages (was /best-ai-model, not /models/for, see note below). /compare/models still outstanding |
P1 |
| 6 |
/features hub + capability pages |
P1 |
| 7 |
/solutions + /industries
|
P2 |
| 8 |
/prompts + /tools
|
P2 |
| 9 | Internal-link sweep, nav/footer clusters, final verification | P0 |
Definition of done for every page, enforced by the four coverage tests plus
Lighthouse: route resolves under /{locale}/; registry entry PUBLISHED +
REVIEWED + INDEXABLE; 13 locales of SEO metadata and body copy; URL in
lighthouserc.json; reachable from footer or hub; exactly one h1; breadcrumbs
with BreadcrumbList; no fabricated claim.
Recording these so they are not re-proposed as oversights.
| Refused | Why |
|---|---|
/model-providers/qwen, /model-providers/kimi, /model-providers/glm
|
No such connector exists (F6) |
/model-providers/amazon-bedrock |
Scaffolding only; already excluded by /supported-models (F2) |
/learn/best-ai-model-for-coding |
Same intent as /best-ai-model/coding (§4) |
| Benchmark tables | No trustworthy first-party data; fabrication is forbidden |
| Compliance claims (SOC 2, ISO 27001, HIPAA, FedRAMP, GDPR certification) | Not held. Industry pages say "designed for private deployment" |
/tools/local-ai-hardware-calculator |
Requires hardware data the repo does not have; a guessed recommendation is worse than none |
| Built 2026-09-07 — the deferral in F7 was reversed once a key existed | |
| Indexable tool permutation URLs | Combinatorial thin content; the tool is one canonical page |
Every factual claim on a new page must trace to a row here. Each was read out of the code on 2026-08-30, with the file that proves it. Nothing on a public page may assert a capability absent from this table.
packages/shared-types/src/enums/connector-provider.enum.ts
| Provider | Adapter | Seeded models | Public page |
|---|---|---|---|
| OpenAI | yes |
gpt-5, gpt-5-mini, gpt-4o, gpt-4o-mini, o3, o4-mini
|
/models/openai |
| Anthropic | yes |
claude-opus-4, claude-sonnet-4, claude-haiku-4-5
|
/models/anthropic |
| Google Gemini | yes |
gemini-2.5-pro, gemini-2.5-flash, gemini-2.5-flash-lite
|
/models/google |
| DeepSeek | yes |
deepseek-chat, deepseek-reasoner
|
/models/deepseek |
| xAI Grok | yes |
grok-4, grok-3-mini
|
/models/xai |
| Ollama | yes | open-weight, operator-chosen | /models/local-ai |
| llama.cpp | yes | open-weight, operator-chosen | /models/local-ai |
| AWS Bedrock | scaffolding only | none | no page |
Model list from apps/claw-routing-service/src/modules/router-models/constants/model-cost-seed.constants.ts
— the 16 models the product actually prices.
packages/shared-types/src/enums/routing-mode.enum.ts
AUTO · MANUAL_MODEL · LOCAL_ONLY · PRIVACY_FIRST · LOW_LATENCY ·
HIGH_REASONING · COST_SAVER
apps/claw-chat-service/src/modules/chat-messages/constants/payg.constants.ts
REPAIR · VERIFY · CONSENSUS · ESCALATION_CHAIN · BEST_OF_N ·
COST_ENSEMBLE · ROLE_PACK · PIPELINE · TASK_DECOMPOSITION
Judge and Compare are separate surfaces (PaygSurface.JUDGE, .COMPARE), not
labs. COST_ENSEMBLE, ROLE_PACK and TASK_DECOMPOSITION were not in the
original brief and are genuine uncovered opportunities.
packages/shared-types/src/enums/plan-feature.enum.ts
COMPARE_MODE · JUDGE_MODE · RESEARCH_MODE · WEB_SEARCH · WEB_FETCH ·
WEB_EXTRACT · CRITIC_REVIEW · WORKSPACES · MEMORY · CONTEXT_PACKS
packages/shared-types/src/enums/workspace-provider.enum.ts, adapters verified
in apps/claw-workspace-service/src/**/adapters/
GitHub · GitLab · Bitbucket · Slack · Jira · Confluence · Figma · ClickUp · Google Drive · Gmail · SharePoint · OneDrive · Google Calendar · Outlook Calendar
Google Calendar and Outlook Calendar were absent from the brief; both have real adapters and get pages.
CHAT · COMPARE · JUDGE · ORCHESTRATION · IMAGE · FILE_GENERATION ·
CODING_AGENT · WORKSPACE_ACTION · ROUTING
Research is deliberately not a paid model surface — it reaches search
providers and is metered as WEB_SEARCH / WEB_FETCH / WEB_EXTRACT. A page
claiming "research uses your model credit" would be wrong.
Reviewed 2026-08-30 before any page was written. Business-owner, CTO and operations lenses each returned VETO. Every finding below is resolved in the plan rather than deferred, because a veto at plan time is cheap and the same veto after eight batches is not.
MODEL_FACTS may not carry speed. The only in-repo source for latency is
apps/claw-routing-service/src/modules/router-models/constants/cloud-model-intelligence.constants.ts,
whose own header attributes latency classes to "ClawAI's router benchmark suite
(see qa/routing-latency-baseline/)". That directory does not exist.
Publishing a speed band in 13 languages citing a benchmark suite that is not in
the repository is a fabricated claim with a fabricated citation.
→ Speed is not published. Not qualitatively, not at all, until a real measurement exists.
That file must not be a source at all. It carries unsourced disparagement of
named third-party products — avoidFor: ['high_reasoning', 'legal_drafting']
for GPT-4o-mini, weakDomains for GPT-4o. Those are internal routing
heuristics. Published, they are trade-libel exposure in 13 languages. It also
lists mistral-large-latest, command-r-plus, qwen-max and
moonshot-v1-128k — providers with no ConnectorProvider member — which
would silently undo F6.
→ MODEL_FACTS is grounded only in model-cost-seed.constants.ts plus
vendor documentation. Every entry carries a source and a vendor URL. A fact
with no vendor URL is not published.
Naming a model is an entitlement claim. /pricing already refuses it in as
many words: "this page does not promise a specific model on a specific tier.
Confirm the live catalog before choosing a plan for one model."
docs/business/plan-allowances.md records a prior incident where one page
overstated the enforced allowance fifteen-fold. This plan would multiply that
claim surface from one page to ~1,300 documents, and
docs/business/rollout-and-notice.md discharges allowance-change notice on the
pricing page — a mechanism that cannot work once the claim lives in 1,300 places.
→ Every page naming a model carries the same "confirm the live catalog"
qualifier, in all 13 locales, and links to /pricing. No page pairs a model
name with a signup CTA implying that model is included in a given tier.
| Was planned | Now | Why |
|---|---|---|
/industries/healthcare, /industries/financial-services, /industries/government
|
Not built | An industry page addressed to a regulated buyer is an implied offer of a contractual instrument. public-launch-content/en.constants.ts records that ClawAI offers no data-processing agreement. HIPAA needs a BAA; "designed for private deployment" does not discharge one. Blocked pending a regulated-vertical claims decision in docs/business/ (not written yet) |
/industries/* generally |
Hub over unregulated verticals only | Same reasoning; agencies, software teams, research and education carry no implied instrument |
/compare/models/gpt-vs-claude (model vs model) |
/compare/models/* reframed: "how ClawAI's router chooses between them"
|
Comparative advertising in de fr it es pt must compare verifiable material features objectively (Dir. 2006/114/EC art. 4; German UWG §6 is competitor-enforced). A page comparing two products we sell neither of, with benchmarks refused, substantiates nothing. A claim about our own routing behaviour is substantiable |
/best-ai-model/coding |
/model-fit/coding — "Choosing a model for coding" (see note below — not /models/for/coding, the path this row originally named) |
"Best" is an unsubstantiated superlative about third-party products, and §6 refuses benchmarks, so it is unsubstantiable by construction. Same intent, same traffic, no superlative |
| "14 verified connectors" | "14 connectors", copy generated from the provider registry | 8 of 14 have webhooks: false (three were flipped from true because they were lying); Bitbucket and Jira webhook verifiers are no-op stubs returning signatureValid: true; both calendars support exactly one write action. "Real-time sync" and "signature-verified" are false for specific connectors |
/model-fit route note (batch 5, built 2026-09-09), the "see note below" this
table points to. The row above originally read /models/for/coding. That
collides for the identical reason the model-providers cluster moved off
/models in batch 3 (commit efed5669f): PRIVATE_ROUTE_PREFIXES
(constants/private-route-prefixes.constants.ts) blocks any path whose string
starts with /models — the authenticated model catalog dashboard at
src/app/(portal)/models/** — and /models/for/coding starts with /models.
/models-for/* was considered next and rejected too: it also starts with
the literal string /models, so PRIVATE_ROUTE_PREFIXES's prefix match
(path.startsWith(prefix)) blocks it exactly as hard. The cluster ships at
/model-fit instead — /model-fit/coding, /model-fit/complex-reasoning,
/model-fit/writing-and-editing, /model-fit/research-with-sources,
/model-fit/private-local-workloads — which shares no prefix with the private
route. Five task pages, not the "coding, reasoning, writing, …" placeholder
list in §4's table: the user's approved backlog for this cluster specifies
coding, complex reasoning, writing and editing, research with sources, and
private/local workloads.
Ad eligibility, decided rather than defaulted. /compare/* is
INELIGIBLE on recorded reasoning — "a page whose job is to be a fair,
checkable comparison of named competitors does not also carry ad inventory."
That reasoning applies verbatim to every new cluster that names a third party.
→ INELIGIBLE: /compare/models/*, /models/*, /model-fit/* (built at this
path instead of the originally-planned /models/for/* — see the route note
above; the ad-ineligibility reasoning is unchanged), /integrations/*,
/industries/*. ELIGIBLE: /learn/*, /use-cases/*, /features/*,
/solutions/*. Publishing ~100 pages at once ad-eligible is also an AdSense
scaled-content exposure.
/prompts ad-eligibility (batch 6, built 2026-09-09) — the decision this
table's row above (/prompts + /tools, P2) did not itself make. §4 lists
/prompts with informational intent and this table names the P2 build order,
but neither states an AdEligibility value for it the way this section does
explicitly for every other cluster. Decided here, applying the same test the
rest of this section uses (does the page name and evaluate a third party, or
does it explain a technique): /prompts/* explains prompt-writing technique —
clarity, examples, step-by-step reasoning, structured output, system vs. user
prompts, iterating on a result, and how the approach shifts by task type — and
names no third-party AI product anywhere in its copy. That is the /learn/*
shape, not the /compare/* or /model-fit/* shape, so /prompts/* is
ELIGIBLE, same reasoning and same category (ContentCategory.GUIDE) as
/learn/*. FeedEligibility.PUBLISHABLE, also matching /learn/*: evergreen
editorial content with genuine publication semantics.
reviewDate is mandatory for every cluster naming a third party, not just
/compare/*. A comparison without a visible date is a claim with no expiry.
| Fork | Decision |
|---|---|
| Fan-out shape at 100 pages |
ADR-084. The existing comparison cluster fans out only one of five layers (registry config). Slug enum, SEO copy, route file and Lighthouse URL are all hand-written ×9. At 100 pages that is 100 enum members, 1,300 SEO blocks, 100 route files. Resolved as a dynamic segment per cluster (/learn/[topic] + generateStaticParams off the cluster order array) |
PUBLIC_PAGE_SEO_BY_LOCALE |
Pluggable. 168 KB / 2,391 lines today for 28 slugs; ~770 KB and ~10,900 lines at 128 — the exact shape TypeScript checks superlinearly, and every batch would edit the same file. Each cluster owns its SEO copy beside its body copy; buildLocalizedMetadata resolves through a registry of providers |
| Feed eligibility |
A field on the definition, not a category predicate. The registry already answers "which surfaces does this page appear on" per definition (indexability, adEligibility). Keying on ContentCategory would mean two pages in one category can never differ, and adds a second way to express the same kind of fact |
| Lifecycle |
RETIRED added. There is currently no way to un-publish. Rolling back a batch deletes the routes and turns every already-crawled URL into a soft 404 across 13 locales at once — worse than the bad page. The point of no return is the first successful production deploy, not the merge |
Lighthouse CI timings from four consecutive real runs: 13.9 s per audit,
±5 s. 28 URLs × numberOfRuns: 2 = 56 audits = 13 min today.
| Cumulative URLs | Audits | Lighthouse step |
|---|---|---|
| 28 (today) | 56 | 13 min (measured) |
| 61 | 122 | ~28 min |
| 128 (end state) | 256 | ~59–77 min |
lighthouse-coverage.test.ts asserts the audited set equals the indexable set
in both directions, so this growth is mandatory — there is no sampling
escape hatch — and categories:best-practices at minScore: 1 means one bad
audit in 256 fails the run. The workflow has no concurrency: group and no
timeout-minutes.
→ Batch 0 adds both guards, repairs docs/09-testing/lighthouse-ci.md (which
already says "eight published public pages" and 0.95, against a reality of 28
and 1), records the minutes ≈ URLs × 0.46 formula, and caps
/compare/models explicitly — C(16,2) = 120 pairs are possible and nothing
bounded it.
RSS_GLOBAL_MAX_ITEMS = 2000 truncates silently, and the truncation eats
pages rather than chats: global-rss.service.ts sorts by publishedAt
descending before slicing, page items carry a fixed lastReviewed while chat
items carry live timestamps, so every chat sorts ahead of every page. Two
documents already compute the ceiling from "13 locales × 16 pages" and are stale
at 28.
→ Batch 0 fixes the arithmetic, the comments, and adds a test that fails before the ceiling is reached.
Not affected, asserted rather than assumed: nginx needs no change (the
catch-all location / is declared last, and these are not API paths); no new
env vars; no compose changes; SITEMAP_URL_CHUNK_SIZE = 40_000 is three orders
of magnitude clear. Build time is a non-event (~36 s → ~90–150 s) because every
marketing page reads headers() and renders dynamically, so locale is not a
build multiplier.
| # | Debt | Trigger to repay |
|---|---|---|
| D1 |
MODEL_FACTS duplicates a routing-service constant across a workspace boundary with no drift detector |
A model added to or removed from MODEL_COST_SEED, or MODEL_COST_SEED_VERSION bumped |
| D2 | The footer's explorePages is a deny-list; every new page joins one site-wide column |
Inverted to opt-in in Batch 0, not Batch 9 — seven batches would otherwise pass Lighthouse on a footer degrading each time |
| D3 |
/llms.txt partitions by deny-list too, so all ~100 new pages flatten into one "Product" section |
Same Batch 0 change |
| D4 | Retiring PLANNED slugs empties PLANNED_CONTENT_CONFIGS, leaving ContentLifecycleStatus.PLANNED with zero instances and its fallback branch untested |
Decided in Batch 0/2: retire only the colliding slugs (the four guides/* in Batch 0, plus workspace-connectors in Batch 2 once /integrations shipped), keep the rest |
| D5 | CI cannot detect a production SITE_URL misconfiguration — lighthouse.yml sets its own |
Post-deploy verification step added to §5 |
MODEL_FACTS as a frontend constants module was challenged and confirmed
correct. Crossing to routing-service's live catalog would require a new
unauthenticated public endpoint exposing precisely what that service refuses to
expose (a full provider rate card is a margin input); it would make marketing
pages vary per deployment (a self-hoster with no OpenAI key rendering an empty
/models/openai that Google has already indexed); and it would put a runtime
frontend→routing-service dependency on the SEO critical path, where an outage
empties indexed pages.
Verified 2026-09-09 against the live registry, lighthouserc.json and the
codebase — not against the originally-planned URLs in §4. Every deviation
recorded in a "Deviation"/"note" callout above is now DONE and reconciled here
in one place.
| Batch | Cluster | Planned route (§4) | Actual route shipped | Hub + children | Commit | Status |
|---|---|---|---|---|---|---|
| 0 | Infra (RSS fix, footer opt-in, llms.txt sections, retired 4 guides/* PLANNED slugs) |
— | — | — | 2bd88f5cc |
DONE |
| 1 | /learn |
/learn |
/learn (unchanged) |
hub + 30 topics (not 12/18 as informally reported elsewhere — verified by reading LEARN_TOPIC_ORDER) |
014eec326 |
DONE |
| 2 | /integrations |
/integrations |
/integrations (unchanged) |
hub + 14 connector topics | 014eec326 |
DONE |
| 3 | /models |
/models |
/model-providers — /models collides with the private, authenticated src/app/(portal)/models/** catalog dashboard; PRIVATE_ROUTE_PREFIXES blocks any path starting with /models (F2, §8.2) |
hub + 6 providers (openai, anthropic, google, deepseek, xai, local-ai) |
efed5669f |
DONE |
| 4 | /use-cases |
/use-cases |
/use-cases (unchanged URL, converted single page → hub) |
hub + 7 task pages | 502fcc80d |
DONE |
| 5 |
/best-ai-model (or /models/for/*) |
/best-ai-model |
/model-fit — /models/for/* and /models-for/* were both considered and rejected: both start with the literal string /models, so PRIVATE_ROUTE_PREFIXES's path.startsWith(prefix) match blocks them exactly as hard as /models itself (§8.2 route note) |
hub + 5 task pages (coding, complex-reasoning, writing-and-editing, research-with-sources, private-local-workloads) |
62feb4aeb |
DONE |
| 6 | /features |
/features |
/features (unchanged URL, converted single page → hub) |
hub + 6 capability pages | 3350f5c5b |
DONE |
| 8 | /prompts |
/prompts |
/prompts (unchanged) — ad/feed eligibility decided explicitly in §8.2 (ELIGIBLE / PUBLISHABLE, same reasoning as /learn/*) |
hub + 7 prompt-writing guides | a9ecd6cd2 |
DONE |
| 7 |
/solutions, /industries
|
— |
Not built — /industries/* blocked pending a regulated-vertical claims decision in docs/business/, not written yet (§8.2); /solutions has no commit in this project's history |
— | — | NOT BUILT |
| 8 | /tools |
/tools |
Not built — no commit in this project's history | — | — | NOT BUILT |
| 9 | Internal-link sweep, nav/footer clusters, final verification | — | — | — | (this verification pass; no code changes were required) | DONE |
Lighthouse coverage, counted from lighthouserc.json directly (2026-09-09):
28 pre-existing marketing pages + /learn (31) + /integrations (15) +
/model-providers (7) + /model-fit (6) + /prompts (8) + /use-cases
children (7, hub already counted in the pre-existing 28) + /features
children (6, hub already counted in the pre-existing 28) = 108 URLs total,
matching docs/09-testing/lighthouse-ci.md after this batch's correction (it
previously read 74, stale since /model-providers, /model-fit, /use-cases,
/features and /prompts landed after that count was written).
Footer verification: MARKETING_FOOTER_EXPLORE_PATHS
(constants/marketing-footer.constants.ts) is confirmed opt-in (D2, resolved
in Batch 0) and alphabetically ordered, carrying all 6 hub roots from this
project (/features, /integrations, /learn, /model-fit,
/model-providers, /prompts, /use-cases) alongside the pre-existing
entries. No footer edit was needed — the /model-fit and /prompts additions
made by the orchestrating session were already correct and complete. Every
cluster's children are reached from their hub's own internal links (the
/learn reference pattern: a hub component calls a build*HubCards /
build*Siblings utility off the cluster's *_ORDER array), not from
individual footer entries — confirmed consistent across all 6 clusters.
Cross-link and stale-path audit: spot-checked cross-links
(/prompts/prompting-for-structured-output → /learn/what-are-structured-ai-outputs,
/use-cases/private-and-local-deployment → /model-providers/local-ai,
/features/model-routing-and-orchestration → /learn/what-is-llm-orchestration)
all resolve to real registry slugs. A repo-wide search for the pre-rename
/models/openai-style and /models/for/* paths found none — every reference
to /models in src/ is either the private portal route (topbar, sidebar,
breadcrumb) or an explanatory comment about the rename decision, not a stale
marketing link.
Ad/feed eligibility, verified against the registry (not assumed):
/model-providers/* and /model-fit/* are AdEligibility.INELIGIBLE;
/use-cases/*, /features/*, /learn/* and /prompts/* are
AdEligibility.ELIGIBLE. /prompts was verified against git show a9ecd6cd2 and the registry rather than assumed — it is ELIGIBLE /
FeedEligibility.PUBLISHABLE, the /learn/* shape, per the explicit decision
in §8.2.
Built 2026-09-10, closing the row in §8.2's table ("/compare/models/gpt-vs-claude
(model vs model)" → "/compare/models/* reframed"). Ships at
/compare/models — a hub plus six pair pages, nested under the existing
/compare route rather than a new top-level path, since the reframed claim
("how ClawAI's own router chooses between two families") is still fundamentally
a comparison page and belongs with its siblings.
Capping decision. §8.4 warned nothing bounded /compare/models: a full
model-level matrix is C(16,2) = 120 pairs; even capped to the 5 cloud
provider families in ModelProviderPage it is C(5,2) = 10 pairs, and 11 with
local-ai folded in as an eleventh "family". This batch builds 6 pairs,
capped at family level (never individual models), chosen editorially rather
than exhaustively:
| Pair | Why it is in the set |
|---|---|
openai-vs-anthropic |
The two most consistently searched cloud families; also the most commonly conflated in support questions |
openai-vs-google |
Completes the three-way "big cloud" triangle with the pair above |
anthropic-vs-google |
Completes the triangle; the two cost-tier shapes differ enough to be worth a page (Anthropic has no CHEAP-class seeded model, Google does) |
openai-vs-deepseek |
The cost-tier contrast pair — DeepSeek's entire seeded catalog sits in STANDARD, worth explaining against OpenAI's wider CHEAP→PREMIUM range |
openai-vs-xai |
Covers the fifth and smallest cloud catalog (XAI, 2 seeded models) against the largest (OPENAI, 6) |
cloud-vs-local |
Not a named-vendor pair — cloud (any of the five families) against Ollama/llama.cpp. Given ClawAI's local-first positioning, this is the single most important pair in the cluster, and it is structurally different from the other five: the question is where a request runs, not which vendor answers it |
Deliberately not built in this batch: anthropic-vs-deepseek,
google-vs-deepseek, anthropic-vs-xai, google-vs-xai, deepseek-vs-xai
— the remaining 5 of the 10 possible cloud-family pairs. No claim-liability
reason blocks them; they were simply lower search-intent priority than the
six above. Trigger to add one: real query volume evidence, or a support
pattern showing users actually confusing two of these specific families.
Grounding. Every cost-class claim (CHEAP/STANDARD/PREMIUM/ULTRA,
rendered as "budget"/"standard"/"premium"/"highest") is read directly from
constants/model-facts.constants.ts (itself diffed against
model-cost-seed.constants.ts — see that file's own grounding rules, reused
here rather than re-verified independently). Every routing-mode claim (Auto,
Manual Model, High Reasoning, Cost Saver, Local-Only, Privacy-First) is a
real member of packages/shared-types/src/enums/routing-mode.enum.ts. No
page in this cluster claims a speed/latency difference between named
vendors, matching every other cluster's refusal (§6) — the locale-completeness
test (compare-models-content-locale-completeness.test.ts) asserts this
mechanically in all 13 locales, and separately asserts no locale's
translation implies a winner via "best"/"better than"/"superior to"/
"outperforms".
Router-behaviour framing, not a product ranking. Per §8.2, no page
declares a winner between the two named families, not implicitly and not by
ordering — every page's productNote and FAQ entries were written to state
what ClawAI's own router weighs (cost class, routing mode fit, whether a
workload must stay local) rather than which vendor is "better". Verified by:
(1) the locale-completeness test above running in all 13 locales, (2) a
manual re-read of the English source and every FAQ/productNote field before
translation, (3) spot-checking two non-English locale files for the same
property after each translation agent's self-report.
Not done in this batch: no link was added from the existing /compare
hub page to the new /compare/models hub. ComparisonHubContent (a
13-locale content type) would need a new field to carry that link's copy,
and this batch judged that scope not worth the risk of touching another
cluster's already-shipped locale content. The new hub is fully discoverable
today — registered in the content registry, sitemap, llms.txt and
Lighthouse CI — and every pair page cross-links out to /model-providers/*,
/model-fit, and /learn/*. Trigger to revisit: if search-console data
shows /compare/models under-discovered relative to its sibling clusters.
The guest-facing Threads board is registry slug threads/board, path /threads/board, in all 13 locales
(ADR-178 addendum). It follows the
cluster rules above rather than adding a new mechanism:
-
Registry and sitemap. One
content-registry.constants.tsentry (category GUIDE,AdEligibility.INELIGIBLE,FeedEligibility.PUBLISHABLE, review date 2026-10-14). The sitemap, hreflang alternates (13 locales plusx-default= English) andllms.txtall read the registry, so no sitemap code changed. Guarded bysitemap-coverage.test.ts(a route on disk for every published path and the reverse) andcontent-registry.utility.test.ts. -
Metadata. Title and description come from
chat.threadPublicBoardSeoTitleand...SeoDescriptioninthread-public-board-translations.ts;threads-public-board-seo.constants.tsderives the registry copy from that single source and is added toCLUSTER_SEO_SOURCES.buildPublicPageMetadata(threads/board, locale)yields the self-canonical, hreflang, OpenGraph and Twitter tags andindex,follow. The SEO title carries no brand suffix: the root metadata template (%s | ClawAI) adds it, and a suffix in the copy made the<title>read "... | ClawAI | ClawAI". Thread URLs (canonical, hreflang, sitemap, RSS) use one percent-encoded slug segment (threadCanonicalUrl), so Arabic, Persian, Hindi and Japanese slugs are advertised in the same form the page serves./sitemaps/<locale>/all-threads-N.xmlalso lists Threads of every other language under/<locale>/threads/<slug>; the page canonicalises to the content-language URL, so these are discovery aids and not duplicates (ADR-182). -
Structured data.
buildPublicBoardJsonLd(structured-data.utility.ts) emits aCollectionPagewhosemainEntityis anItemListof the first page of Threads (name and canonical/<locale>/threads/<slug>URL only,numberOfItems= total) plus aBreadcrumbList. No rating, author or private field. -
Rule 38 (low-value content). The page is a listing of content that already has its own indexable page, so the
risk is thin content. It carries a heading and real introductory copy (
threadPublicBoardIntro, at least 120 characters in every locale, tested) that says what is on the board, how it is vetted and what a guest can and cannot do; every card links to a full Thread; the page is ad-ineligible. The canonical of every Thread stays/threads/<slug>. -
Member Board stays out.
/boardis inPRIVATE_ROUTE_PREFIXES(robots disallow for every locale),noindexand absent from the sitemap.robots.test.tspins that/boardis blocked and/threads/boardis not. -
Audit lists.
lighthouserc.json,lighthouserc.pr.jsonandapps/claw-frontend/scripts/responsive-audit/audit.cjsinclude the page;lighthouse-coverage.test.tsfails if an indexable page is missing from the Lighthouse set.
ClawAI Wiki · source-controlled under wiki/ · generated/mirrored from the current repository knowledge layer.
ClawAI Wiki
Product
- Product-Vision
- Flagship-Features
- Business-Overview
- Requirements-Register
- Drift-Log
- Runtime-Managed-Connector-Providers
- Threads
- ADR-Index
Architecture
- System-at-a-Glance
- Architecture-Map
- System-Architecture
- Message-Flow
- End-to-End-Data-Flow
- Routing-Engine
- Event-Bus
- Security-Architecture
Platform
- Backend-Services
- Frontend-Architecture
- Shared-Packages
- Database-Reference
- API-Reference
- Provider-Catalog
- Memory-and-Context-Architecture
- File-Support-Architecture
- Research-and-Web-Crawling
Runtime & Ops
- Docker-Guide
- Nginx-Reference
- CI-CD-Pipeline
- Logging-and-Observability
- Operational-Runbooks
- Troubleshooting
Quality
Engineering OS
- AI-Native-Engineering-OS
- Generated-Manifests
- Rules-Catalog
- Skills-Catalog
- Context-Layer
- Engineering-Memory
- Tooling-and-Automation
- Scripts-and-Operations
- ESLint-and-Architecture-Enforcement
- Extended-Work-Skills
Agents
Reference