Skip to content

सच है sacchai

primary documents, not propaganda प्राथमिक दस्तावेज़, प्रचार नहीं

A bilingual (English · Hindi) factcheck-archive documenting Indian political parties, RSS, government actions, and ongoing movements. Every claim links to a primary source.

License: MIT Content: CC BY-SA 4.0 Build Status Node Next.js


Mission

Show all failures. Hide no one. Start with the ruling party, but the scope is the system, not one organization.

Every claim is a citation. Every citation is a chip. Every chip is a link to a primary source — court order, statute text, archival material, official notification, or named-publisher news report.

Opinion lives in the marginal notes, marked editor's note. The body is the document.

Stack

Layer Choice
Framework Next.js 14 (App Router, static export)
Language TypeScript
Styling Tailwind CSS + @tailwindcss/typography
Content MDX via next-mdx-remote
Markdown remark-gfm, rehype-slug, rehype-autolink-headings
Icons lucide-react
Fonts Inter, Source Serif 4, JetBrains Mono (next/font)
Hosting Vercel (primary) + Cloudflare Pages (mirror)
Search FlexSearch (static index, lazy-loaded)

Quick start

npm install
npm run dev          # http://localhost:3000 (auto-redirects to /en)
npm run build        # static export to ./out
npx serve out        # preview the built site
npm run lint         # eslint
npm run lint:sources # verify every <Source> id exists in src/lib/sources.ts
npm run typecheck    # tsc --noEmit
npm run build:search # rebuild public/search-index.json
npm run build:sitemap # rebuild public/sitemap.xml
npm run build:feed   # rebuild public/feed.xml (RSS 2.0, en + hi)

The prebuild hook automatically runs lint:sources, build:search, build:sitemap, build:feed, and typecheck before next build, so npm run build alone keeps the search index, sitemap, and RSS feed in sync.

Repository layout

.
├─ content/
│  ├─ en/
│  │  ├─ stories/        # long-form pieces (/*.mdx)
│  │  ├─ faq/            # debate-aid answers (/*.mdx)
│  │  ├─ people/         # public figures (/*.mdx)
│  │  └─ protests.json   # state-by-state protest index
│  ├─ hi/                # Hindi mirrors of /en/
│  └─ _research/         # primary-source archive (gitignored)
│
├─ src/
│  ├─ app/
│  │  ├─ layout.tsx              # root layout, fonts, metadata
│  │  ├─ globals.css             # tailwind + custom CSS
│  │  ├─ [locale]/
│  │  │  ├─ layout.tsx           # locale layout, header/footer, SearchProvider
│  │  │  ├─ page.tsx             # home (featured story)
│  │  │  ├─ stories/             # index + [slug]
│  │  │  ├─ faq/                 # index + [slug]
│  │  │  ├─ people/              # index + [slug]
│  │  │  ├─ protests/            # state-grouped table
│  │  │  ├─ sources/             # master registry (filters)
│  │  │  ├─ about/               # mission
│  │  │  ├─ editorial/           # editorial policy
│  │  │  ├─ legal/               # disclaimers, warrant canary
│  │  │  ├─ right-to-reply/      # response mechanism
│  │  │  └─ contact/             # PGP, email
│  │  └─ middleware.ts           # / → /en
│  ├─ components/
│  │  ├─ SourceChip.tsx          # hover-tooltip citation chip
│  │  ├─ SourceCitation.tsx      # inline MDX <Source /> renderer
│  │  ├─ SourcePinPanel.tsx      # slide-over citation reader
│  │  ├─ SearchProvider.tsx      # client context for global search
│  │  ├─ SearchSheet.tsx         # Cmd/Ctrl+K full-screen sheet
│  │  ├─ SearchTrigger.tsx       # header button
│  │  ├─ FilterChips.tsx         # pill buttons with active state
│  │  ├─ Breadcrumbs.tsx         # server component
│  │  ├─ TableOfContents.tsx     # client, IntersectionObserver
│  │  ├─ ReadingProgress.tsx     # fixed top bar
│  │  ├─ ShareBar.tsx            # copy link + X + WhatsApp
│  │  ├─ EditorNote.tsx          # MDX <EditorNote /> aside
│  │  ├─ StoriesIndex.tsx        # client, topic filter wrapper
│  │  ├─ SourcesBrowser.tsx      # client, type/orientation filter
│  │  ├─ PeopleIndex.tsx         # client, status/party filter
│  │  ├─ LanguageToggle.tsx      # client component
│  │  ├─ MobileMenu.tsx          # full-height slide-in drawer
│  │  └─ layout/Header.tsx, Footer.tsx
│  └─ lib/
│     ├─ sources.ts              # central source registry
│     ├─ content.ts              # MDX / JSON loaders
│     ├─ i18n.ts                 # locale config
│     ├─ edit-link.ts            # build GitHub edit URLs
│     └─ utils.ts                # cn() helper
│
├─ scripts/
│  ├─ verify-sources.mjs         # prebuild: audit <Source> ids
│  ├─ build-search-index.mjs     # prebuild: FlexSearch JSON
│  ├─ build-sitemap.mjs          # prebuild: sitemap.xml
│  └─ build-feed.mjs             # prebuild: RSS 2.0 (en + hi)
│
├─ public/
│  ├─ _redirects                 # / → /en (Cloudflare Pages)
│  ├─ robots.txt                 # blocks AI scrapers
│  ├─ sitemap.xml                # generated
│  ├─ search-index.json          # generated, lazy-loaded by SearchSheet
│  └─ feed.xml                   # generated, two RSS channels (en, hi)
│
└─ .github/
   ├─ workflows/ci.yml           # typecheck + lint + build + preview deploy
   └─ ISSUE_TEMPLATE/            # bug / correction / source

The Source chip system

Every factual claim in MDX content uses inline <Source id="..." />. The chip renders as a superscript number that hovers/focuses to show:

  • Title
  • Publisher · date
  • Orientation tag (mainstream / left / right / judicial / academic)
  • Notes
  • Link to the canonical URL
  • Wayback snapshot link (if any)

The /sources page auto-renders the full registry from src/lib/sources.ts.

Adding content

A new source

Add an entry to src/lib/sources.ts. The id you set is what you reference as <Source id="..." /> in MDX. Required fields: id, title, type, orientation. Optional: date, publisher, author, url, archiveUrl, notes.

A new story

Create content/en/stories/your-slug.mdx:

---
title: "Your story title"
slug: "your-slug"
date: "2026-01-15"
topic: "rss" # rss | government | individual | movement
summary: "One-sentence summary for the index card."
sources:
  - rss-constitution-1949
  - britannica-rss
---

Your story text. Use the <Source id="..." /> MDX component
inline to cite. Hover any chip to see the source.

Mirror to content/hi/stories/your-slug.mdx.

A new FAQ / person / protest

Same pattern, different frontmatter. See content/en/faq/is-rss-samaj-sevak.mdx, content/en/people/narendra-modi.mdx, and content/en/protests.json for examples.

Locale routing

  • / → middleware redirects to /en/
  • All content lives under /[locale]/...
  • English is canonical; if Hindi diverges, EN wins
  • Language toggle in the header switches between /en/... and /hi/...

Deployment

Vercel (primary)

vercel link --yes
vercel deploy --prod --yes

Build command: npm run build Output directory: out Environment variables: none required

Cloudflare Pages (mirror)

Build command: npm run build Build output directory: out Environment variables: none required

The public/_redirects file handles //en/.

Contributing

See CONTRIBUTING.md. For corrections, open an issue using the Correction template. For sources, use the Source addition template.

Editorial policy

See /editorial. Eight principles. Source over summation. Document over opinion.

Right to reply

Any named individual named in published content may request a published response. See /right-to-reply.

Licensing

  • Source code: MIT
  • Original content: CC BY-SA 4.0
  • Quoted primary documents: original copyrights, used under fair use for criticism, comment, and news reporting

Anonymous collective

sacchai editorial collective is a pseudonym used by the contributors. The site is published from a non-Indian jurisdiction. See /legal including the warrant canary.

No warranty

This is a journalism project. It is not legal advice. It is not a substitute for primary reading. Verify against the source.

About

Bilingual (en/hi) factcheck-archive: primary documents, not propaganda

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages