Skip to main content
@AGENTS.md

Blog — SEO/GEO Content System

Overview

The blog at /blog is powered by Sanity CMS (project htd46ya1, single dataset production). Content lives only in Sanity: posts are authored as drafts via the Sanity MCP (or the Studio), a human reviews and publishes in the Studio, and a Sanity webhook revalidates the site and pings IndexNow. Non-production deployments read the same dataset with the drafts perspective, so drafts preview there tagged with a Draft badge. There is no git-side content pipeline. The former HTML/manifest/CI publishing system (publish scripts, content/blog/, the publish workflow, Supabase image staging) was removed deliberately — do not recreate any part of it. The authoring contract for agents is .agents/skills/author-blog-posts/SKILL.md (draft-first workflow, schema shapes, document-id rules, FAQ guidance). Read it before creating or editing posts.

Content validation (mandatory before a draft is handed to review)

  1. Humanizer — no banned AI words, no em dashes, no bold-header-colon patterns
  2. Compliance — no buy/sell/hold advice, no guaranteed-return language
  3. Formatting — no raw URLs, no exclamation marks

What NOT to touch

  • studio/schemaTypes/postType.ts — Sanity post schema (schema changes require a Studio redeploy)
  • components/blog/blog-portable-text.tsx — Portable text renderer
  • Blog route structure (app/(public)/blog/)

File structure

SEO rules

  • Canonical URLs use the www host (https://www.cfosilvia.com) — the apex redirects to it
  • Document _ids must never contain a dot — dotted “path” ids are invisible to unauthenticated reads and silently blank the public blog
  • JSON-LD: Article + Breadcrumb on every post; FAQPage when faq is authored (rendered visibly too — Google requires visible FAQ content)
  • Internal links: 2-3 related articles per post (same category preferred)
  • robots.txt: AI crawlers ALLOWED (GPTBot, ClaudeBot, PerplexityBot) — this is intentional for GEO; /llms.txt serves a crawler index
  • Sitemap auto-includes all published posts
  • IndexNow: fired automatically by /api/revalidate/sanity for indexable slugs

Security rules

  • NEVER commit Sanity tokens, API keys, or .env files
  • Agents author drafts; production publishing is a human action in the Studio unless the user explicitly authorizes otherwise
  • IndexNow endpoint requires Bearer token auth

Environment variables

Modulus design system (second system, in progress)

A second design system is being built alongside the existing one, for the landing and product A/B test. It lives in components/modulus/ and is namespaced Modulus/* in Storybook. Until the test concludes, both systems must render correctly and independently.

Frozen. Do not edit these files.

  • app/themes.css — staging-frozen per docs/THEMING.md. The live dark-theme experiment keys its rules on control / dark-variant-b here, so an edit can corrupt a running test.
  • app/globals.css — staging-frozen; also holds the nine .sam-* utility classes, which are unscoped
  • tailwind.config.ts under theme.screens and theme.fontFamily, both of which replace Tailwind defaults rather than extending them. Existing components rely on xs, desktop and 2xl, and on font-sans.
If a change to any of the above seems necessary, stop and ask rather than editing.

Being retired. Deleting is allowed, editing still is not.

  • components/ui/** — the pre-Modulus component library
  • Any existing *.stories.tsx outside components/modulus/
Deleting is allowed, under docs/MODULUS-CLEANUP-PLAN.md: remove a module that has no importers, or migrate its call sites to the Modulus equivalent and then remove it. A story is deleted together with the component it covers. Editing one is still a stop-and-ask. Do not restyle, extend, or add a variant to anything in here. Measured on 2026-09-18: 19 files under app/(public) still import components/ui, only 12 of its 64 modules have a Modulus counterpart at all, and the landing A/B test is still running. So these components still paint live surfaces, and changing one reaches all of them at once — which is exactly what the original blanket freeze was protecting. What changed, and what did not. The freeze read “read-only, do not edit” because both design systems had to coexist while the product A/B test ran. The in-app half of that is over: app/layout-with-sidebar-client.tsx stamps data-theme-variant='modulus' unconditionally, with no flag, so inside app/(app)/** there is no control arm left to protect. The landing and the public routes are a different story and are not settled, which is why this is a narrowing of the rule rather than its removal. components/modulus/** is the new system and is not part of this cleanup — including the components that are built but not yet adopted. Do not delete something there for being unreferenced.

Additions must be scoped, never global

  • New CSS variables go under the agreed data-theme-variant selector in a new stylesheet, never in :root and never in themes.css. Bare :root declarations would restyle the existing components, since names such as --surface-card, --surface-hover and --surface-selected already exist.
  • New Tailwind keys carry a modulus- prefix. Tailwind keys are global regardless of CSS scoping, and surface (26 keys) and cta already exist.
  • New Storybook stories are titled Modulus/*. Components/*, Navigation/* and UI/* are taken.
  • Do not enable autodocs globally. It is off repo-wide, and enabling it would generate docs for all 895 existing story entries across 221 files. Opt in per story instead.

Notes

  • Opacity tokens carrying com.modulus.cssTransform: divideBy100 in their $extensions must be divided by 100 for CSS. Figma stores opacity as percent. Without the transform, disabled controls render fully opaque.
  • .storybook/preview.tsx imports globals.css then themes.css, and the order is load-bearing. Any new stylesheet must load after both.