Cloudflare Worker that aggregates public lectures, readings, and discussions across Frankfurt — Polytechnische Gesellschaft, Haus am Dom, Jüdische Gemeinde, FGZ StreitClub, Literaturhaus, Goethe-Uni Bürgeruniversität, Institut für Sozialforschung, Evangelische Akademie, Sigmund-Freud-Institut, Denkbar, Romanfabrik, DIG Frankfurt, OPEN BOOKS, plus talk-shaped events from museum, theatre, and academic venues that the hub tags talk:*.
Live: frankfurt.lehr.salon
GitHub Action (.github/workflows/scrape.yml — lehrhaus job)
↓ daily cron 06:30 UTC
↓ runs `bun apps/lehrhaus/scripts/scrape.ts`
↓ writes apps/lehrhaus/src/scrape-data.ts (typed module)
↓ commits + pushes if content actually changed
Cloudflare git integration
↓ redeploys the worker with the new bundled data
Worker
↓ imports SCRAPE_DATA, in-memory filters serve every read path
Three formats — Vortrag (monologic lecture), Lesung (literary reading / book launch), Diskussion (panel / debate). The format comes from the hub's talk:vortrag / talk:lesung / talk:diskussion label (classified upstream in @museumsufer/classify, talk.ts).
The default home view is a rolling next-7-days list grouped per date; /tag/YYYY-MM-DD is the single-day permalink.
- Cloudflare Workers (TypeScript)
- Hono v4 + JSX SSR
- htmx for date / range partial swaps
- Bundled
src/scrape-data.ts(no D1 reads for content) - D1 (
lehrhaus-db) for Web Push subscriptions only
"The editor's annotated quarto." Foxed paper (#F2E9D5) + iron-gall ink (#1C1812) + cinnabar rubric (#A33222) + marginalia blue + book-binding umber. Cormorant Garamond serif + DM Mono. Pilcrows ¶ as per-entry anchors, manicule ☞ on action buttons, asterism ⁂ as the empty-state mark. Wordmark mirrors konzert.haus (lehr ink + rubric dot + italic-rubric salon).
bun install # from repo root
bun run -F @museumsufer/lehrhaus dev
bun run -F @museumsufer/lehrhaus scrape # regenerate src/scrape-data.ts
curl http://localhost:8787/
curl http://localhost:8787/api/day
curl 'http://localhost:8787/api/events?format=Lesung'
curl http://localhost:8787/feed.icsLehrhaus reads its events from the central event hub at
@museumsufer/event-hub. The actual scraping happens in
packages/scrapers/src/venues/; scripts/scrape.ts here just filters
EVENTS to entries with a talk:* label and maps them onto
LehrhausEvent.
- Add a canonical scraper under
packages/scrapers/src/venues/<slug>.tsthat emits atalk:vortrag/talk:diskussion/talk:lesunglabel for the talk-shaped events. Register it inpackages/scrapers/src/index.ts. - Add a
LehrhausSourceentry insrc/source-config.tswhose slug matches the hubsource_slug. Lehrhaus picks it up automatically on the nextbun scraperun. - Optionally curate a display name in
packages/event-hub/src/venue-names.ts.
Talks held at museums, theatres, and other venues surface here under
their own hub source_slug (e.g. senckenberg-naturmuseum,
schauspiel-frankfurt) — whatever scraper emitted the talk:* label.
scripts/scrape.ts keeps every hub event with a talk:* label inside
the Frankfurt bbox, regardless of which scraper produced it. Sources not
curated in src/source-config.ts are auto-synthesized into the bundle's
source list (display name via the hub's venue-names.ts), so
/quelle/<slug> works for them too.