> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cfosilvia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CLAUDE

@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

```
app/(public)/blog/
  page.tsx              — Blog index
  [slug]/page.tsx       — Article page
  layout.tsx            — Blog layout

components/blog/
  blog-disclaimer.tsx   — Financial disclaimer (on every article)
  blog-faq-json-ld.tsx  — FAQ schema.org JSON-LD
  blog-faq-section.tsx  — Visible FAQ section (same data as the JSON-LD)
  blog-reading-time.tsx — "X min read" display
  blog-related-posts.tsx — Related articles section
  blog-table-of-contents.tsx — Collapsible TOC
  blog-post-json-ld.tsx — Article + Breadcrumb JSON-LD
  blog-portable-text.tsx — Body renderer (don't touch)

lib/blog/
  freshness.ts          — Content staleness detection
  internal-links.ts     — Related article linking
  metadata.ts           — Page metadata builder
  indexnow.ts           — Shared IndexNow submission

lib/sanity/             — Client, queries, image URLs, fetch helpers
studio/                 — Standalone Sanity Studio (single production workspace)

docs/blog/              — Architecture, agent rules, Sanity setup, SEO docs
```

## SEO rules

* Canonical URLs use the www host (`https://www.cfosilvia.com`) — the apex
  redirects to it
* Document `_id`s 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

```
NEXT_PUBLIC_SANITY_PROJECT_ID    — Sanity project ID
NEXT_PUBLIC_SANITY_API_VERSION   — API version date
SANITY_PREVIEW_READ_TOKEN        — Viewer token for non-prod draft previews
                                   (legacy name SANITY_STAGING_READ_TOKEN works)
SANITY_REVALIDATE_SECRET         — Webhook revalidation secret
INDEXNOW_KEY                     — IndexNow API key
```

## 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.