75 lines
4.3 KiB
Markdown
75 lines
4.3 KiB
Markdown
# 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:00–12:00), afternoon (12:00–18:00), evening (18:00–24: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.
|