Graphify — Optional local knowledge graph over the repo (python3 -m pip install graphifyy). Cursor loads .cursor/rules/graphify.mdc; run /graphify . to generate (gitignored). See . listed in public Privacy/Terms — local dev only; see §7.
SEO & Discovery
Last updated: 2026-02-24
Summary of how the site is made discoverable to search engines and AI answer engines. For the full plan (metrics, events, AEO), see ANALYTICS_SEO_PLAN.md.
Sitemap
Theme
Source of truth:components.json with style: "radix-maia" Last updated: 2026-02-27
Native Maia theme (enforced)
Use native shadcn Maia theme components only. Do not add custom style overrides that deviate from the theme.
UI Components
Source:components/ui — shadcn registry (radix-maia theme) Last updated: 2026-03-19
shadcn components
All components are from the shadcn registry unless noted. Add or update with:
Code
npx shadcn@latest add <component> --overwrite
components.json
Tailwind CSS v4 — Utility-first styling via @tailwindcss/postcss
pgvector — Vector embeddings for RAG (Ask page); chunk metadata includes href (locale-prefixed /en/... for pages), optional citationFragment for anchor deep links. Retrieval: query expansion → pgvector candidate pool → lexical rerank (lib/rag/retrieval-query.ts, lib/rag/rerank.ts). Regression: npm run eval:rag. See ARCHIVE_RAG_PLAN.md and I18N_AND_TRANSLATIONS.md §12
next-intl — Locale-based routing, messages, getTranslations/useTranslations, Link/usePathname/useRouter from @/i18n/navigation
Locales:en (default), localePrefix: 'always' → /en/about, etc.
Messages:messages/en.json with namespaces (Common, HomePage, StoriesPage, etc.)
RAG/Ask: Indexed chunk text and model answers default to English; citation URLs use /en/.... Ask UI copy lives in messages/* (parity tracked as PB-45). See I18N_AND_TRANSLATIONS.md §12
Contents: Base pages (home, stories, about, newsroom, pricing, ask, docs, legal), story projects, and newsroom posts — all locales (en, hi, ar, es)
Source:getLocalizedUrl() from lib/site.ts; NEXT_PUBLIC_SITE_URL for base URL
Robots
File:app/robots.ts
URL:/robots.txt
Rules:User-Agent: * — allow /, disallow /api/
Sitemap: Points to {baseUrl}/sitemap.xml
AI Crawlers
We allow the following AI crawlers to access public content (AEO / answer engine optimization):
Crawler
Purpose
GPTBot
OpenAI (ChatGPT)
PerplexityBot
Perplexity
Google-Extended
Google AI Overviews
anthropic-ai
Claude
CCBot
Common Crawl
Each has explicit allow: "/" and disallow: ["/api/"] in app/robots.ts. This supports appearing as a cited source for queries like "who is Gagan Malik" or "how to contact Gagan Malik."
Canonicals
Canonical URLs are set via metadata.alternates.canonical on key pages (layout, stories, newsroom, pricing, story detail) to avoid duplicate content. Uses getLocalizedUrl() for locale-prefixed URLs.
All UI components (Select, Button, Dialog, etc.) must come from the shadcn registry.
No className overrides for radius, background, or border on shadcn primitives. If something looks wrong, fix the component file or theme, not the usage.
Add or update components with:
Code
npx shadcn@latest add <component> --overwrite
Do not change style or baseColor in components.json unless intentionally switching themes.
Design tokens
Radius: Defined in app/globals.css via @theme inline and :root.
Muted (light mode):--muted: oklch(0.92 0 0) — tuned for perceptible ghost-button hover contrast against --background (WCAG 2.1 non-text contrast).
--radius: 1rem (large). Derived: --radius-sm through --radius-4xl.
Use the same radius token for related elements (e.g. trigger and dropdown).
Spacing scale
Use a consistent spacing scale for padding, margins, and gaps:
Pixels
Tailwind
Usage
4
p-1, gap-1
Tight gaps, icon padding
8
p-2, gap-2
Small gaps between related elements
16
p-4, gap-4
Default padding, card gaps
24
p-6, gap-6
Section spacing
32
p-8, gap-8
Large section breaks
Avoid arbitrary values unless aligning to a specific design spec.
Accessibility
WCAG AA — Text contrast and non-text contrast (ghost buttons, borders).
Focus states — All interactive elements have visible focus rings (focus-visible:ring-2, focus-visible:ring-ring).
Semantic markup — Use aria-label, aria-describedby, role where appropriate. Prefer native elements (<button>, <a>) over divs.
Reduced motion — Respect prefers-reduced-motion for animations. Transitions should clarify state, not distract.
Motion (Framer Motion)
Library: Framer Motion is available for declarative enter/exit, layout, and gesture-based animations.
Import path: Use @/lib/motion (re-exports motion, AnimatePresence, and types) so future reduced-motion or config changes stay centralized.
Reduced motion: When adding animated components, use the useReducedMotionTransition() hook from @/lib/motion for the transition prop so animations are disabled when the user prefers reduced motion. See lib/hooks.ts for usePrefersReducedMotion().
Guideline: Use motion to clarify state and hierarchy; avoid decorative or distracting animation.
Touch targets
Minimum 44×44pt for interactive elements (buttons, links, form controls).
Use min-h-11 (44px) or size-11 for touch-friendly buttons on mobile.
Ensure adequate spacing between tap targets to prevent mis-taps.
Dropdowns and popovers
Corner radius: Dropdowns/popovers that attach to triggers must use the same corner radius as the trigger.
first:rounded-l-lg last:rounded-r-lg (8px, on first/last cell per row)
—
Switch: Use for boolean toggles (e.g. Ask voice settings: Voice mode, Wake word). Wrap in min-h-[44px] min-w-[44px] flex items-center justify-center when a larger touch target is needed. From shadcn registry; has built-in focus-visible ring.
Table row cells: First and last cells (th or td) in each row use rounded-l-lg / rounded-r-lg (8px) in default and hover states. Requires border-separate border-spacing-0 on the table for rounded corners to render correctly.
Toggle (Voice mode, Wake word in Ask voice settings sheet)
Table
table.tsx
Data tables
Textarea
textarea.tsx
Multi-line text input
Tooltip
tooltip.tsx
Hover tooltips
Custom components (in components/ui)
Component
File
Description
Tracked Link
tracked-link.tsx
Link wrapper with analytics (trackEvent)
Page skeletons (components/skeletons)
Route-level loading UIs use shared skeleton components. They mirror mobile vs desktop layouts: tighter horizontal padding (px-4 → sm:px-6 / sm:px-8), primary CTA pairs stacked vertically with full-width controls on small viewports (flex-col → sm:flex-row), newsroom bento heroes with image on top and text block below on mobile (matching WritingsBentoCard), and section headers with title + “View archive” stacked on narrow screens. Inline skeletons in app/**/loading.tsx (e.g. pricing, archive, legal index) follow the same padding pattern.