Files
CalendarOverview/CLAUDE.md
T
2026-07-17 20:23:52 +02:00

75 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this is
A single-user local web app: paste a Google Calendar ICS URL, see a two-year
(current + next) calendar overview. Each day is split into three segments —
morning (00:0012:00), afternoon (12:0018:00), evening (18:0024:00) — that
light up based on events, colored by emoji found at the start of the event
title (configured in `tags.json`). No build step, no frontend framework,
one runtime dependency (`node-ical`).
## Commands
- `npm start` (or `./start.sh`, which runs on port 4000 instead of the
default 3000) — starts the server. Loads `.env` if present via Node's
native `--env-file-if-exists` flag (no dotenv dependency).
- `npm test` (or `node test_days.js`) — runs the one test file, a plain
`assert`-based script (no test framework) covering the recurrence/segment
logic in `days.js`.
- `PORT=<n> npm start` — override the port (default 3000).
## Architecture
- **`days.js`** — the only file with real logic, and the only one covered
by tests. `buildOverview(icsText, tagsConfig, rangeStartStr, rangeEndStr)`
is a pure function: parses ICS text via `node-ical`, expands every VEVENT
(recurring or not) with `ical.expandRecurringEvent(..., { expandOngoing: true })`
— this one call correctly handles EXDATE and RECURRENCE-ID overrides, so
there's no manual recurrence-expansion or override-merging code here.
Returns `{ "YYYY-MM-DD": { segments: [...], tooltip: "..." } }`.
- Each `segments[i]` is `null` (nothing), `'muted'` (untagged event
present but not called out), a color name string (one leading emoji
matched, from `tagsConfig`), or a 2-element array of color names (two
leading emoji — see below).
- **Emoji detection**: `leadingEmojis()` reads up to 2 emoji from the
start of the event title, tolerating a single space between them (so
both `"✈️💜 Title"` and `"✈️ 💜 Title"` count as a pair). A title with
one leading emoji not present in `tagsConfig` still gets a color (the
`UNKNOWN_EMOJI_COLOR` fallback), so it stands out from plain
untagged/muted events.
- **All-day events**: DTEND is exclusive per RFC 5545, handled by
subtracting 1ms before computing the local day.
- **No-TZID timestamps**: treated as floating local time per RFC 5545
(formatted in the system's own timezone, not UTC) — this matches how
`node-ical` itself encodes date-only DTSTART values, so the two stay
consistent. Don't default these to UTC; that was a real bug once.
- **`server.js`** — a bare `node:http` server, no framework (routes are
simple enough not to need one). `GET /api/overview?url=<ics>` fetches the
URL server-side and calls `buildOverview` — this exists only because
Google's ICS endpoint can't be fetched from the browser (CORS). Validates
the URL is `http`/`https` before fetching (the one trust-boundary check,
since the server will fetch whatever URL it's given). `tags.json` is read
fresh on every request (no caching), so editing it takes effect on the
next reload with no restart needed — only changes to `.js` files need a
server restart.
- **`public/index.html`** — the entire frontend: HTML, CSS, and JS inline in
one file, no build step. Fetches `/api/overview` and `/tags.json`, renders
a 12-month grid per year. Colors are CSS custom properties
(`TAG_COLOR_VAR` maps a color name from `tags.json` to a `--var`); a
segment holding a 2-color array renders as a `linear-gradient` split
top/bottom instead of a flat fill. Tooltips use the native `title`
attribute (no custom tooltip JS) — hover text is built server-side in
`days.js`.
- **`tags.json`** — user-editable, not code. Maps an emoji to
`{ label, color }`. `color` must be one of the names in `TAG_COLOR_VAR` in
`index.html` (`blue`, `aqua`, `yellow`, `green`, `violet`, `red`,
`magenta`, `orange`, `gray`, `purple`, `lightblue`, `white`) — an unrecognized
color name silently falls back to blue rather than erroring, so a typo
here won't crash anything but will look wrong.
- **`.env`** (gitignored) — optional `CALURL=<ics url>`, used only to
prefill/auto-load the URL input on page load via `GET /api/default-url`.
Never log or print its value; it's a private calendar URL.