5,226 years. One JSON per year. 99.85% of events name a source; every year declares which regions its sources do not reach. A year-by-year editorial folio of human civilisation, researched by AI. Translations are up for grabs — the Icelandic backfill (19/5,226 years done) and any new language. The pipeline is built; what's needed is native-speaker review. See TRANSLATION.md.
🌐 timeline.sumarhus.com · GitHub · MIT License · 🇮🇸 Þýðingarverkefni
Active sprinting is paused. The corpus validates 100%, the site is live and stable, and the open lanes below are documented well enough for anyone (human or agent) to pick up. The dated change record lives at timeline.sumarhus.com/updates (source: frontend/src/data/updates.ts).
| Lane | State | Entry point |
|---|---|---|
| Icelandic backfill | 19/5,226 years translated; pipeline ready, ~$35 at batch pricing | TRANSLATION.md — up for grabs |
| Any new language | Pipeline is locale-generic; smaller languages especially welcome | TRANSLATION.md — up for grabs |
| Phase 4 era research | 3/21 eras have evidence dossiers; 18 open | scripts/eras/registry-expansion-2026-07.json |
| Publishing migrated era dossiers | 8 dossiers schema-valid but unpublished; needs a phase2-migrated UI treatment + editorial review |
evidence-layer/…/migrated-v1/MIGRATION-NOTES.md |
| Re-research of eras 05–08, 12 | Pre-schema files lack per-paper metrics; needs fresh Scite runs | same MIGRATION-NOTES |
| Layer | What it is | Shipped |
|---|---|---|
| Layer 1 — Corpus | 5,226 ICCRA-schema JSON files, one per year, 2025 CE → 3,200 BCE | ✅ 2026-04-13 |
| Layer 2 — Evidence | Scholarly deep-dives per era via the Scite MCP; 7 eras validated, 8 migrated to schema v1 (unpublished), 5 pending re-research, 21 newly registered and pending research | ✅ 2026-04-17, expanded 2026-07-29, migration pass 2026-07-31 |
| Frontend — Chronograph | Notebook editorial folio (reading), Stratum instrument view (per-year dashboard), Atlas orthographic globe (spatial), era scrubber + era selector — Next.js 16 + React 19 | ✅ 2026-04-18, UX pass 2026-07-29 |
| Translation layer (EN → IS) | CI-integrated pipeline localizing the corpus into Icelandic. Locked verbatim Icelandic system prompt, Gemini Flash 3 Preview pinned, six-guard correctness chain, idempotent SHA256 manifest, GitHub Action auto-translates on push | ✅ 2026-04-18 (pipeline); backfill paused at 19/5,226 years (0.36%) |
The daemon ran for 57.7 hours across 2026-04-10 – 2026-04-13 and finished the entire corpus without a single failed year.
| Final metric | Value |
|---|---|
| Years researched | 5,226 / 5,226 (100%) |
| Events documented | 17,991 |
| Graph edges | 13,130 cross-year connections |
| Failed years | 0 |
| Total runtime | 57.7 hours |
| Total API cost | ~$15.68 (Sonnet 4.6 via batch API, ~$0.003/year) |
| Model | Claude Sonnet 4.6, exclusively |
| Source attribution | 99.85% of events name a source (17,964 of 17,991) |
| Certainty distribution | Confirmed 80.6%, probable 15.7%, approximate 3.5%, other 0.2% |
Every year is a structured JSON with events, primary sources, certainty levels, geographic-gap declarations, and cause-effect graph edges. See the Methodology page on the site for the full optimisation journey.
After the year-level corpus shipped, a second complementary layer was built under evidence-layer/: scholarly-evidence deep-dives per scholarly era, produced via the Scite MCP with the scite-preflight / scite-research skill system.
| Phase | Eras | Status |
|---|---|---|
| Phase 3 (schema v1.0.0) | 7 (Archaic Greece, Persian Achaemenid Empire, Classical Athens, Warring States China, Hellenistic World, Mauryan India, Pax Romana) | ✅ validated |
| Phase 2 (pre-schema, drifted) | 13 (pre-agricultural → Iron Age Aegean) | ⚙ 8 migrated to schema v1 on 2026-07-31 (scripts/migrate_phase2_eras.py, content verbatim, not yet published to the site) · ⛔ 5 (eras 05–08, 12) lack per-paper scite metrics and need re-research |
| Education pilots | 3 (Classical Athens · paideia, Islamic Golden Age · madrasah, AI Inflection · cognitive threshold) | ✅ VALOR-sourced |
| Unresearched | 2 eras | 📋 backlog |
| Phase 4 (expansion, registered 2026-07-29) | 21 — 6 thematic, 8 crisis, 7 regional | 🔬 research in progress |
Highlights:
- 161 bibliography entries harvested from Scite and VALOR.
- 9 validation missions including the Mediterranean-diet / CVD validation run that caught the PREDIMED retraction.
- 143 citations from the VALOR education corpus catalogued for cross-reference.
Until July 2026 an era was a slice of the linear chronological sweep, and an event belonged to at most one. That cannot express the questions the corpus should answer: the Haitian Revolution belongs to the Age of Revolutions and the Age of Abolition and the modern sweep at the same time.
The registry (scripts/eras/registry-expansion-2026-07.json, merged by
scripts/eras/build-era-index.mjs) now carries four kinds of era in reserved id
bands, so the chronological space stays clean:
| Kind | Ids | Count | What it is |
|---|---|---|---|
chronological |
1–50 | 22 | the original linear sweep |
thematic |
100–119 | 6 | Big-History lenses that cross geography — Scientific Revolution, Age of Revolutions, Decolonisation |
crisis |
120–139 | 8 | rupture, suffering, environmental shock — the Transatlantic Slave Trade, the Black Death, the World Wars |
regional |
140–159 | 7 | spheres outside the Western default — West African Golden Age, Tang & Song, Classic Maya |
Each entry also carries tone (golden / sombre / neutral), which drives
palette from data rather than from a hardcoded list of era ids, and
careLevel. Seven eras are careLevel: high: they cover mass atrocity,
enslavement or genocide. That is not a publish gate — it is a research-depth
and provenance requirement. Contested figures are shown as ranges with named
provenance, victim counts never rest on a single source, and contested naming
conventions are attributed to whoever uses them.
scripts/eras/validate-era-index.mjs enforces the id bands, unique ids and
start < end. It deliberately does not reject overlapping date ranges —
overlap is the point.
No single model both proposes a claim and supplies its own support.
- Angles (
scripts/research/grok_angle_mission.py) — Grok 4.5 generates falsifiable research claims per era plus a source-bias audit naming what the standard account distorts and where an English-web-trained model reliably fails. It is explicitly barred from producing citations. - Evidence — a separate Scite MCP pass retrieves the actual literature and returns a verdict per claim.
The separation earns its keep: on the first run the evidence pass overruled
the angle pass three times — African demographic decline 1700–1850 downgraded
from supported to contested (the offsetting term has never been estimated,
so no net figure exists), Black Death mortality corrected from a 40–60% band to
the literature's 30–60%, and Classic Maya equinoctial alignments downgraded to
contested ("deeply rooted but unfounded"). Raw dossiers live under
evidence-layer/ (gitignored — see the note in .gitignore); the aggregated
derivatives ship in frontend/public/data/eras/.
- Scite MCP whitepaper + case study available under
/methodology/scite-mcpon the live site.
See evidence-layer/README.md for the full layer 2 inventory.
The original prototype frontend ("Eternal Codex" — dark-gold grimoire aesthetic) was removed and rebuilt on 2026-04-18 using the Claude Design handoff bundle.
Three coordinated surfaces, one navigation:
- 📖 Notebook (
/) — the default reading experience. Cream parchment with ruled lines, oxblood editorial stamps, Newsreader serif display + Inter Tight chrome + IBM Plex Mono. Virtualised 5,226-row timeline with a bookmark lane for era/decade rules. Every year is a folio entry. - 🔬 Stratum (
/stratum) — dataset-as-instrument. Near-black dashboard, click-anywhere-to-jump year strip, 3-panel stats (event count by category, certainty distribution, source-mix bar), VB-style event grid with category rail, contempo/later source indicators, and full per-year dissent block. - 🌍 Atlas (
/atlas) — orthographic globe with 17,515 plotted events. Imperative RAF-driven canvas draw reads rotation from a ref (no React re-render per frame over 17k items); d3-geo convention for drag so right-drag reveals east. Time-brush histogram across all 5,226 years, category filter, Off-Earth / orbital toggle (Apollo coords don't plot in the Pacific), LOD that switches heat → cluster → pin by zoom.
Shell:
- Primary nav (brand · Methodology · GitHub) always visible.
- Secondary nav (Notebook · Stratum · Atlas) only on the three interactive routes.
- Year folio (
/year/[id]), Era dossier (/era/[id]), and Methodology pages render without the view switcher — pure reading.
Era navigation (2026-07-29):
- Era scrubber — one strip above the timeline carrying an event-density histogram across all 5,225 years (sqrt-normalised, because under linear scaling the modern buckets flatten everything before 1500 into a line), all 43 eras packed into lanes so their deliberate overlap is legible, and drag-to-select a year span.
- Era selector — a menu replacing what had become a 43-item horizontal scroll strip. Grouped by kind, two columns, type-to-filter, and each row shows its span plus whether a dossier is filed.
- Eras registered before their research lands render a registry stub — label, span, kind, focus and the editorial care note — rather than the "Era not found" state a missing dossier used to produce.
- Proper Open Graph card rendering a cream folio preview (not the Vercel ▲).
Tech: Next.js 16.2, React 19.2, Tailwind v4, TanStack Virtual + Query, motion/react, d3-geo + topojson-client. All visual tokens from a single 3-variant system (--fg / --stamp / --rule / --accent) with WCAG AA contrast verified by scripts/qa-contrast.mjs.
A dedicated pass took the interface from "database inspector" to editorial product, all guarded by tests:
- Classical Music & Opera timeline (
/music) — 233 works across 29 eras, Layer-1 draft + Layer-2 scholarship (161 peer-reviewed sources verified against Crossref/DataCite), also overlaid onto the main timeline as a filterable "musical" category. - Follow-the-thread — music entries link to the earlier/later work in the chronology, landing on the exact entry (scroll + focus), turning the overlay into a guided narrative.
- Proportional era ribbon — the home era filter is a single band whose segment widths follow each era's real time-span across the 5,226 years; a map of deep time that doubles as the filter.
- Reading-first folio — narrative always visible, citations behind an explicit control; global "expand all sources"; a vertical era minimap.
- Durable quality — a committed Playwright E2E suite + GitHub Actions CI (
frontend/e2e/) asserting, on every route: zero console errors, no horizontal overflow (320→1440), the signature features work, and zero serious/critical axe-core a11y violations. 30 tests. - Accessibility — skip link, keyboard-navigable controls (roving-tabindex era ribbon, arrow-key nav), labelled globe controls + canvas, meaningful link/button names, WCAG-AA contrast across all three themes.
- Discoverability — dynamic
sitemap.xml(5,255 URLs: every year + era),robots.txt, and a working Open Graph card (1200×630 cream folio). - Sovereign hosting — served from an owned Hetzner edge (Caddy + Docker, auto-TLS) at timeline.sumarhus.com; the old Vercel host 308-redirects. Ideal-state criteria tracked in
docs/ideal-state-criteria.md.
Looking for Icelandic-speaking contributors. See TRANSLATION.md for the full methodology, fixed-terminology glossary, and how to review or extend a locale. Live coverage:
outputs/translations/is/STATUS.json.
The corpus is being localized into Icelandic via a CI-integrated pipeline at scripts/translate/. The architecture is generic and supports additional locales; PRs adding new languages welcome.
Model: gemini-3-flash-preview (pinned — -latest tags are forbidden in production runs so dialect/format doesn't shift mid-backfill).
Strategy: The model is prompted in Icelandic — the full system prompt is written in Icelandic to lock the model into the Icelandic latent space. Prompt is verbatim at scripts/translate/prompts/system.is.md and content-hash-pinned in the manifest so any edit invalidates the entire locale cache.
Six-guard correctness chain (every translated file must pass all six or it's not written):
- Locked verbatim Icelandic system prompt (rule #1: translate values only, keep keys in English).
- Glossary preamble in the user message listing every JSON key + every ICCRA enum value that must be preserved.
- API-level
responseMimeType: "application/json"forcing strict JSON output. - Structural diff rejecting any file where keys, enums, IDs, numerics, or array lengths differ from source.
cross_referencesandcoordinates_approxallow English parenthetical annotations to translate but require the ID/numeric prefix to match. - Deterministic key-rename auto-repair for the mechanical
extra_key + missing_key with same ICCRA-enum valueclass of model errors (e.g. model produces"certain": "confirmed"instead of"certainty": "confirmed"— schema is authoritative, rename is logged, content is untouched). - Ajv schema validator rejecting any file that breaks ICCRA.
Atomic .tmp → rename ensures no partial writes ever land on disk. Idempotent via SHA256(source) + SHA256(prompt) per file per locale in .translation-manifest.json.
Fixed Icelandic terms (from scripts/translate/glossary.json): BCE → f.Kr., CE → e.Kr., Antiquities of the Jews → Fornsögur Gyðinga, Proleptic Gregorian → fyrirframreiknað gregorískt tímatal. Canonical Icelandic forms for historical names (Ágústus, Heródes mikli, Parþaveldið, Silkivegurinn, Jósefus) handled by the locked prompt.
Automation: .github/workflows/translate-is.yml — on push to main touching outputs/json/** or scripts/translate/**, runs the IS pipeline (manifest-skipped for cached files), re-aggregates frontend chunks, opens a PR via peter-evans/create-pull-request. workflow_dispatch supports a reset_manifest flag for forced full re-translation. Concurrency group prevents overlapping runs.
Frontend consumption: frontend/scripts/aggregate-data.mjs emits per-locale chunk trees at frontend/public/data/<locale>/{chunks,manifest.json}. Missing-locale years fall back to English with a _locale_fallback: "en" marker so the UI never sees a hole in the timeline. frontend/src/i18n/ provides a hasLocale() typeguard, dictionary loader, and native-speaker-authored EN / IS dictionaries for chrome, category labels, certainty labels, and era names. The app/[lang]/ route restructure is scheduled as a follow-up session.
Run locally:
cd scripts/translate
npm install
echo "GOOGLE_AI_API_KEY=..." >> ../../.env
npm run dry-run:is # 3-file smoke
npm run run:is # full 5,226-year backfill (resumable)Every year follows the ICCRA schema (see RESEARCH_PROMPT.md):
{
"year": 1066,
"year_label": "1066 CE",
"era_context": "High medieval period...",
"documentation_level": "rich",
"geographic_coverage_gaps": ["Sub-Saharan Africa", "Southeast Asia"],
"events": [
{
"id": "1066-001",
"title": "Battle of Hastings",
"region": "England",
"category": "military",
"description": "...",
"key_figures": ["William, Duke of Normandy"],
"sources": [{"name": "Bayeux Tapestry", "type": "primary_text", "contemporary": true}],
"certainty": "confirmed",
"certainty_note": "Extensively documented..."
}
],
"disconfirming_evidence": "...",
"historiographic_note": "...",
"graph_edges": [{"from": "1066-001", "to": "1086-001", "relation": "led_to", "note": "..."}],
"_meta": {"model": "claude-sonnet-4-6", "processed_at": "2026-04-11T..."}
}Design principles: no fabrication · anti-sycophancy (disconfirming evidence mandatory) · no anachronism · global coverage (gaps declared, not hidden) · named source typing (primary text, archaeological, epigraphic, numismatic, chronicle, oral tradition).
Started as a subscription-based CLI daemon; evolved through five phases to complete the corpus 29× faster at 98.6% lower cost.
| Metric | Phase 1 (CLI) | Phase 5 (API) | Improvement |
|---|---|---|---|
| Cost per year | $0.22 | $0.003 | 99% cheaper |
| Years per cycle | 5 | 25 | 5× throughput |
| Cycle interval | 20 min | 60 sec | 20× faster |
| Schedule | Off-hours only | 24/7 | Unrestricted |
| Total time | ~70 days (est.) | 57.7 hours (actual) | 29× faster |
| Total cost | ~$1,150 (est.) | ~$15.68 (actual) | 98.6% cheaper |
Full breakdown of each phase, including the failed Haiku experiment and the Kimi-analysed plan-mode migration, lives on /methodology.
Self-contained prompt, no dependencies — drop into any capable AI:
You are a historical research agent. Produce structured JSON for a single year.
YEAR TO RESEARCH: [INSERT YEAR, e.g., 1453 or -776]
RULES:
1. Negative years = BCE. Positive = CE.
2. Research significant events across ALL regions.
3. Modern years: 15-25 events. Ancient: 0-5. Zero is acceptable.
4. Every event names its source. "General knowledge" is not acceptable.
5. Every event has a certainty level: confirmed/probable/approximate/traditional/legendary.
6. Include "disconfirming_evidence" — what's disputed about this year?
7. Include "geographic_coverage_gaps" — what regions are you missing?
8. Do NOT fabricate. Empty events with honest context > hallucinated entries.
9. Respond with VALID JSON ONLY.
SCHEMA: {"year": int, "year_label": str, "era_context": str,
"documentation_level": "rich|moderate|sparse|minimal|negligible",
"geographic_coverage_gaps": [str], "events": [{id, title, region,
category, description, key_figures, sources, certainty, certainty_note}],
"disconfirming_evidence": str, "historiographic_note": str,
"graph_edges": [{from, to, relation, note}]}
# Full ICCRA schema validation
python3 scripts/validate_corpus.py
# Auto-fix compound categories ("political | military" -> "political")
python3 scripts/fix_categories.py
# Backfill _meta on files missing model/cost tracking
python3 scripts/backfill_meta.pyFinal validation pass: 100% valid — 5,226 files, 17,991 events, 0 errors.
chronograph/
├── LICENSE MIT
├── README.md this file
├── RESEARCH_PROMPT.md ICCRA prompt template (locked)
├── LEDGER.md append-only progress log
├── CONTRIBUTING.md how to do adversarial review
│
├── scripts/ Python 3.11 async daemon (Phase 1) + TS translate
│ ├── api_client.py direct Anthropic API, tiered models
│ ├── orchestrator_optimized.py main daemon loop
│ ├── batch_processor.py 5 years per API call
│ ├── validate_corpus.py ICCRA schema validator
│ ├── fix_categories.py auto-fix compound categories
│ ├── health_check.sh quick status
│ └── translate/ EN → IS translation pipeline (2026-04-18)
│ ├── gemini.ts @google/genai client, pinned flash-3-preview
│ ├── schema.ts Ajv ICCRA validator (mirrors Python)
│ ├── structural-diff.ts post-translation keys/enums/IDs guard
│ ├── key-repair.ts deterministic schema-key auto-repair
│ ├── translate.ts per-file orchestration
│ ├── manifest.ts SHA256 idempotency
│ ├── run.ts CLI entrypoint
│ ├── prompts/system.is.md locked verbatim Icelandic prompt
│ └── glossary.json DO_NOT_TRANSLATE + fixed IS terms
│
├── docker/ reboot-persistent daemon container
│
├── outputs/
│ ├── json/ 5,226 ICCRA JSON files (Layer 1)
│ ├── haiku_experiment/ archived quality comparison
│ └── gemini_experiment/ archived quality comparison
│
├── evidence-layer/ Layer 2 — Scite/VALOR deep-dives
│ ├── methodology/ scite-skill-system + validation missions
│ ├── eras/ phase2-eras-01-13, phase3-eras-14-20
│ └── education-layer/ VALOR-sourced education pilots
│
├── frontend/ Chronograph v0.2 (Notebook)
│ ├── src/app/ Next.js 16 App Router
│ │ ├── page.tsx / (Notebook timeline)
│ │ ├── atlas/page.tsx /atlas (orthographic globe)
│ │ ├── stratum/page.tsx /stratum (instrument dashboard)
│ │ ├── year/[id]/ per-year folio
│ │ ├── era/[id]/ per-era scholarly brief
│ │ ├── methodology/ editorial methodology page
│ │ └── opengraph-image.tsx OG card (cream folio, 1200×630)
│ ├── src/components/
│ │ ├── shell/ two-layer nav (primary + secondary)
│ │ ├── notebook/ folio timeline + year layout
│ │ ├── stratum/ instrument dashboard
│ │ └── atlas/ globe + imperative draw loop
│ ├── scripts/
│ │ ├── aggregate-data.mjs Layer 1: chunk JSON (+ per-locale trees)
│ │ ├── aggregate-evidence.mjs Layer 2: flatten evidence to per-era JSON
│ │ ├── qa-tour.mjs Playwright screenshot sweep
│ │ ├── qa-font-audit.mjs DOM sweep for <14px text
│ │ └── qa-contrast.mjs WCAG AA computed-contrast checker
│ ├── src/i18n/ locale loader + en/is dictionaries
│ └── public/data/ aggregated chunks + era bundles + locale trees
│
└── state/
├── progress.json daemon progress (completed/failed/in_progress)
└── cache/ SHA256-keyed response cache
MIT License for all code (see LICENSE).
Corpus data (JSON under outputs/json/, aggregated chunks, evidence layer) released under CC BY 4.0 — attribution required.
Layer 1 is complete. Layer 2 and the frontend are living work.
- Adversarial review — prove the AI wrong. Find fabrications, bad sources, anachronisms
- Regional deep dives — African, East Asian, Indigenous American, Pacific
- Graph edges — cause/effect chains, parallel developments, trade routes
- Evidence layer — re-research the 13 Phase-2 eras under schema v1.0.0
- Frontend — file issues for broken UX or missing affordances
See CONTRIBUTING.md.
Built by Magnús Smári Smárason — one daemon, 5,226 years, zero fabrication. Frontend design by Claude Design (claude.ai/design), implementation by Claude Code.