Skip to content

feat(api): year-partitioned markdown event records - #216

Merged
themightychris merged 4 commits into
developfrom
feat/events-storage-layout
Oct 14, 2026
Merged

themightychris merged 4 commits into
developfrom
feat/events-storage-layout

Conversation

@themightychris

Copy link
Copy Markdown
Member

Summary

Implements plan events-storage-layout (specs from #215). No schema or contract change.

  • Configs mirrored from codeforphilly-data feat(api-skeleton): fastify scaffold with envelope, error mapper, rate limit, idempotency, OpenAPI #17: events/<YYYY>/<slug>.md (native ${{ startsAt: YYYY }} bucket, description as the markdown body), event-series/<slug>.md, event-rsvps/<YYYY>/<eventSlug>/<personSlug>.toml.
  • Write path: RSVPs carry the event's partition year; an update whose startsAt crosses a year boundary moves the event file and its RSVP files in one commit, like a slug rename (slug history only for the rename). Purge cascade computes the year the same way.
  • Markdown bodies: upserts opt in to a missing body (events and series without a description are normal); a body-less file reads back as '', which the serializers present as null. gitsheets trims the body's trailing newline; everything else round-trips byte-for-byte.
  • Legacy redirects: /events/<handle> (live series slug, no event) and segment URLs 301 to /events/series/<handle>.
  • Spec/plan wording updated from the slice expression to the native bucket.

Merge with

codeforphilly-data #17 (empty), #18 (fixture), #19 (legacy-import) — merge together so check:sheet-configs stays green. published is adopted by the release session in lockstep with the release.

Validation

  • check:sheet-configs --ref=events-layout-empty matches
  • events / event-series / sheet-configs / legacy-redirect suites: 70 passed
  • type-check, lint clean; full suite running

🤖 Generated with Claude Code

https://claude.ai/code/session_01SNuYr7DujdhkHoY2h3yYgu

themightychris and others added 3 commits October 14, 2026 00:50
Mirrors codeforphilly-data #17 (empty) byte-for-byte: events live at
events/<YYYY>/<slug>.md with the description as the markdown body
(gitsheets' native `${{ startsAt: YYYY }}` bucket, UTC year), series at
event-series/<slug>.md likewise, RSVPs at
event-rsvps/<YYYY>/<eventSlug>/<personSlug>.toml. Monthly events accrete
forever; a flat directory would not. The RSVP sheet's `year` joins the
write-injected path fields in the config guard test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNuYr7DujdhkHoY2h3yYgu
Write path for the new layout (specs/behaviors/storage.md#sheet-layout):

- RSVP records carry the event's partition year next to eventSlug and
  personSlug. The year is the UTC year of startsAt, which equals what
  gitsheets renders for the event sheet's own bucket because every
  stored timestamp is a `Z` string. The purge cascade computes it the
  same way.
- An update whose startsAt crosses a year boundary now moves the event
  file and every RSVP file in the same commit, exactly as a slug rename
  does; slug history is written only for the rename, since the
  partition never shows in a URL.
- Markdown-bodied upserts opt in to a missing body: an event or series
  without a description is normal, and our upserts always carry the full
  record so the erase-guard adds nothing. A body-less file reads back as
  '' and the serializers present that as null.
- `/events/<handle>` for a live series slug with no event of that slug,
  and the old segment URLs, 301 to `/events/series/<handle>` — a laddr
  "event" was a whole program (specs/behaviors/events.md#legacy-urls).

Tests cover the on-disk paths, the markdown round trip, and a Dec 31 →
Jan 1 move.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNuYr7DujdhkHoY2h3yYgu
… bucket

The spec and plan still described the year partition as a slice
expression; the configs use gitsheets' native `${{ startsAt: YYYY }}`
bucket, which also lets range filters prune partitions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNuYr7DujdhkHoY2h3yYgu
@themightychris
themightychris force-pushed the feat/events-storage-layout branch from 491e353 to 429619d Compare October 14, 2026 04:54
Shipped with codeforphilly-data #17–#19. Notes record the gitsheets
body-less upsert opt-in, the '' read-back and trailing-newline
normalization, and the shared partitionYear helper; the backfill plan
inherits the upsert option.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TGqwPjt3v4eMfiHpPmCKnR
@themightychris
themightychris merged commit 33c8bdf into develop Oct 14, 2026
1 check passed
@themightychris
themightychris deleted the feat/events-storage-layout branch October 14, 2026 12:59
@themightychris themightychris mentioned this pull request Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant