when-am-i / commit
docs: document new calendars, anchor events, colors, and authoring skill
README.md | 40 ++++++++++++++++++++++++++++++---- plan.md | 73 +++++++++++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 91 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 4149632..79e28de 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [](https://gitlab.com/PavelSafronov/when-am-i/-/pipelines) [](https://pavelsafronov.gitlab.io/when-am-i)  - +   @@ -23,14 +23,33 @@ Imagine you're a time traveler who ends up in a random place and time in history - Interactive world map for selecting a location - Date picker for choosing any year in history - **Shuffle** button that drops you at a random place and date on a non-default - (non-Gregorian) calendar — Islamic, Persian, Julian, or a lunar reckoning + (non-Gregorian) calendar — e.g. Chinese, Japanese, Saka, Buddhist, Ethiopian, + Islamic, Persian, Julian, or a lunar reckoning - Displays the local calendar year and system for the chosen place and time - Shows **all** valid answers at once — different people at the same place and time reckoned dates differently (e.g. colonial Julian alongside an Indigenous lunar calendar) +- For calendars with no numbered year, gives the kind of answer a local would — + the moon and night ("a full moon, about night 15"), and, where the data + records a documented event, **"about N winters since <event>"** - A global **modern Gregorian default** for dates from 1927 on, so most recent dates resolve anywhere — shown alongside any local calendar (e.g. modern Iran → Persian *and* Gregorian) - Educational and fun way to explore world calendars and history +### Supported calendars + +The engine returns every valid answer for a place and date. A calendar is +**convertible** when a deterministic rule yields a numbered local year, or +**descriptive-only** when it has no continuous numbered year (we still place you +in time — the moon/season, the named year, and "winters since" a recorded event — +and show the Gregorian year for reference). + +- **Convertible:** Gregorian, Julian, Islamic (Umm al-Qura), Hebrew, + Persian (Solar Hijri), Indian (Saka), Buddhist, Japanese (nengō, Meiji onward), + Ethiopian. +- **Descriptive-only:** the Chinese sexagenary cycle (the named 60-year + stem-branch / zodiac-animal year), the Japanese pre-1873 lunisolar calendar, + and observational lunar calendars such as the Algonquian (Massachusett) reckoning. + ## Tech Stack - React (SPA) @@ -170,15 +189,28 @@ overlapping regions are expected, not an error. The colonial Julian and Gregorian eras live in `colonial-america.json`, whose larger polygon overlaps this one — so a Boston click resolves against both files. +An era for a calendar with no numbered year may also carry **`anchorEvents`** — a +list of documented, dated, **sourced** events a local reckons time from. The app +reports the most recent one at or before the date as "about N winters since +<event>" (e.g. the Massachusett reckoning counts winters since the +1616–1619 epidemic). Anchor events must be real and cited — the engine only +counts the winters, it never invents the event. + ### Schema -See `src/data/calendars/SCHEMA.json` for the schema and field documentation. +See `src/data/calendars/SCHEMA.md` (human-readable field docs) and +`scripts/validate-data.mts` (the authoritative, machine-checkable schema run by +`validate:data`). ### How to contribute - Add or edit files in `src/data/calendars/` via pull request. - Use [geojson.io](https://geojson.io/) or similar tools to draw and export region polygons. -- Include sources/references for each era. +- Include sources/references for each era (and for every anchor event). +- Working in Claude Code? The repo ships a **`new-calendar`** skill + (`.claude/skills/new-calendar/`) that walks the whole flow — researching a + place/people's calendar with citations, drafting the polygon and eras, picking + the right converter (or descriptive-only), then validating and screenshotting. - Run `npm run screenshot:calendar -- src/data/calendars/<your-file>.json` and attach the images to your PR, so the region, eras, and conversions can be reviewed visually. diff --git a/plan.md b/plan.md index 56107a9..d753f39 100644 --- a/plan.md +++ b/plan.md @@ -214,14 +214,34 @@ A `screenshot:calendar -- <file.json>` script that renders **one calendar's** an `@js-temporal/polyfill@0.5.1` (throws on every date — "Unexpected leap month suffix"; no fixed version available), so **Persian/Iran** was used instead. + **The five most-populous calendars added 2026-06-20** (covering the most + humans × time, per Pavel's request): [china.json](src/data/calendars/china.json) + (Chinese sexagenary cycle — descriptive-only, hand-rolled stem-branch/zodiac + naming, since the Temporal lunisolar `chinese` is broken), + [india.json](src/data/calendars/india.json) (Saka, existing Temporal converter), + [japan.json](src/data/calendars/japan.json) (a pre-1873 lunisolar + descriptive era + the modern Japanese **nengō** era, Temporal `japanese` using + `era`/`eraYear`, scoped to Meiji+), + [thailand.json](src/data/calendars/thailand.json) (Buddhist Era, existing + converter), and [ethiopia.json](src/data/calendars/ethiopia.json) (Ethiopian + calendar, **hand-rolled via JDN** because the Temporal `ethiopic` *and* + `coptic` calendars also throw in this polyfill — same class of bug as + `chinese`). New converters live in + [src/utils/calendars.ts](src/utils/calendars.ts) with tests. + --- ## Stretch goals (post-MVP) ### AI-assisted dataset authoring (requested 2026-06-20) -Add Claude Code **skill(s)** under `.claude/skills/` that help an author create a new -calendar region file end-to-end, lowering the bar to contribute data. Sketch of what a -"new calendar dataset" skill would do: +**✅ Shipped 2026-06-20** as the `new-calendar` skill +([.claude/skills/new-calendar/SKILL.md](.claude/skills/new-calendar/SKILL.md)): +it walks an author through researching a place/people's calendar(s) with sourced +citations, drafting the GeoJSON polygon and eras, picking the right converter (or +descriptive-only), and running `validate:data` / `gen:badges` / +`screenshot:calendar` so the result is PR-ready — with the project's "never +invent, always source" rule front and centre. A review/QA skill remains a +possible follow-up. Original sketch of what the skill does: - Take a place + people/polity as input (e.g. "the Mughal Empire", "Coast Salish around Puget Sound") and **research** the calendar system(s), era boundaries, and *whose* reckoning it is (`usedBy`), gathering **citable sources** (the project's acceptance gate @@ -254,13 +274,16 @@ authoring/build-time tooling being considered: ("draw your own region"). Keep authoring output reproducible either way. ### Scalable calendar colors (requested 2026-06-20) -Today [src/utils/calendarColor.ts](src/utils/calendarColor.ts) already computes colors -**on the fly** — colors are *not* stored in the data files. A small curated `PALETTE` -covers the common calendars for recognizability; anything else falls back to a -deterministic `hsl(hashHue(name), 60%, 75%)`, so it already scales to many calendars. -The weakness at scale is **distinctness**, not hard-coding: `hashHue` can map two names -to near-identical hues, and the fixed saturation/lightness can clash or wash out. -Future work — generate visually separable colors: +**✅ Shipped 2026-06-20** in [src/utils/calendarColor.ts](src/utils/calendarColor.ts). +The new `calendarColorScale(names)` assigns colors to the *set* of calendars on +screen at once: curated calendars keep their `PALETTE` color, the rest are walked +in a stable (sorted) order and handed **golden-angle-spaced** hues so they fan out +around the wheel instead of colliding on a raw name hash. [App.tsx](src/App.tsx) +builds one scale from every calendar in the dataset, so a calendar keeps the same +color across the map, the timeline, and as you scrub dates (legend ↔ map ↔ timeline +agree). `calendarColor(name)` is retained for a lone name / fallback (now also +golden-angle-based). Possible later polish: contrast/accessibility tuning so no +generated color vanishes on the map tiles. Original framing of the problem: - Space hues apart (e.g. golden-angle increments) instead of a raw name hash, and/or assign colors **per render** so the calendars actually on screen are maximally distinct. - Keep determinism where it matters (legend ↔ map agreement within a view) and keep the @@ -283,15 +306,29 @@ This is authentic *in structure*. Its pieces split sharply by cost: `src/utils/calendars.ts` computes the night-of-the-moon + phase from the Julian Day Number; the "Algonquian lunar calendar" answer now leads with it (still `convertible: false`, no year) instead of the bare "no year count" note. - - **Eclipse-anchored "winters since"** — if the reference event is an eclipse, the date - is computable. -- **Needs curated, sourced per-region data (the expensive part — hence stretch):** + - **Eclipse-anchored "winters since"** — superseded by the per-region + `anchorEvents` approach below (eclipses turned out to need either a heavy + ephemeris or the same curated data, so they're just one *kind* of anchor). +- **"Winters since" via per-region anchor events — ✅ Shipped 2026-06-20.** Pavel's + call (the most reliable option): each anchor must be a **documented, sourced** + event supplied in the data; the engine only counts the winters, never invents + one. Implemented as an optional `Era.anchorEvents` (`{ name, date, sources }[]`), + with `wintersSince()` in [src/utils/calendars.ts](src/utils/calendars.ts) + picking the most recent anchor at/before the date and reporting "about N winters + since <event>" (counted as completed calendar years so anniversaries aren't + off-by-one). Wired through [resolve.ts](src/utils/resolve.ts) → + `CalendarConversion.reckoning` → a sourced line in + [ResultPanel.tsx](src/components/ResultPanel.tsx); validated in + `validate-data.mts` and the data loader; documented in SCHEMA.md/README. + [massachusett.json](src/data/calendars/massachusett.json) carries the first + anchor (the 1616–1619 epidemic → "about 84 winters since" in 1700). +- **Still needs curated, sourced per-region data (the expensive part — hence stretch):** - **Moon names** — vary by nation, inconsistently attested; must be cited, not invented. - - **Historical-event anchors for "winters since"** — must be real, dateable, locally - remembered events (e.g. the **1616–1619 epidemic**), **not** invented ones. The - "asteroid crash" in the brainstorm is illustrative only — no such event occurred. -- **Schema (when we do this):** add optional `reckoning` text and an optional `moons` - list / `anchorEvents` to descriptive-only calendars. Optional + back-compatible. + - More **historical-event anchors** for other descriptive calendars — must be real, + dateable, locally remembered events, **not** invented ones (the "asteroid crash" in + the brainstorm was illustrative only — no such event occurred). +- **Schema:** the `anchorEvents` part shipped (above); optional `reckoning` text and a + `moons` list remain optional + back-compatible future additions. ### Data accuracy - Refine Boston's generic `"Algonquian lunar calendar"` to the specific **Massachusett**