Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
14 KiB
Seepuls — Status
Letzter Stand: 2026-06-03. LIVE 🚀
2026-06-03 — UI/UX-Feinschliff (Filter, Karten, Wärme, Flaggen)
Review-Punkte umgesetzt, alle live auf seepuls.com (build + astro-check
grün, je per Headless-Screenshot verifiziert):
- Filter nach Dimension getrennt — statt einer Pillen-Wolke jetzt beschriftete Achsen „Art"/„Wo" (Orte-Seite zusätzlich „Ausstattung"), eigener Reset je Achse, Orts-Chips eckiger zur visuellen Trennung.
- Event-Karten — Tickets als rechtsbündiger Akzent-Button (statt
grauem Fußnoten-Link); Kategorie-Tag nach links zur Datums-Zeile,
Merk-Herz allein rechts; „zuletzt geprüft" → dezentes ✓ mit Tooltip;
Datum durchgehend ausgeschrieben (
formatDateLong). - Merk-Badge — dunkler Text statt
#fff(Kontrast auf hellem Cyan);[hidden]greift jetzt (zeigte sonst „0", dainline-flexdas UA-[hidden]überschrieb). - Kontrast — Dark-Meta-/Sekundärtext über WCAG-AA gehoben (muted-foreground 62→70 %, text-muted-Alpha 0.7→0.85).
- Wärmeres Dark-Theme — Grund von kühlem Teal (Hue 205) auf warmes Anthrazit (~28–35), Cyan bleibt komplementärer Akzent.
- Sticky-Section-Header — Zähler-Zahl jetzt in Serife wie das Label;
Hintergrund 85→96 % opak (kein Durchschein);
scroll-margin-top. - Länder-Flaggen als robuste Inline-SVG (DE/CH/AT/LI) statt Emoji
(neue
Flag.astro, in Karten + Filter + Event-Detail).
2026-06-02 — Suche real, Amenities/Region ausgespielt, Web-Push-Folgen
Vier Verbesserungen aus dem Review umgesetzt (alle type-check grün, 112 API- + 34 Web-Tests grün; Deploy steht noch aus):
- Suche real gemacht —
/api/v1/searchwar ein Stub (results: []), ist jetzt eine echte unified Suche (kommende Events + Orte, ILIKE über Titel/Beschreibung/Venue-Name bzw. Name/Beschreibung/Stadt). Event-qtrifft jetzt auch den Venue-Namen; die Startseite zeigt bei aktiver Suche eine „Orte zu …"-Sektion. (Relevanz-Ranking/pg_trgm bewusst als Folge-Schritt — bei der Datenmenge unnötig, vermeidet Migrations-Footgun.) - Amenities/Öffnungszeiten/Region ausgespielt — Venue-Liste liefert
jetzt openingHours/amenities/smoking/heroUrl; neuer
amenity-Filter (jsonb@>, AND) +qin der Venues-API. Orte-Seite: Ausstattungs-Chips, Region-Chips, „Jetzt geöffnet"-Badge (isOpenNow, Über-Mitternacht-fest)- Amenity-Badges pro Zeile. Region-Chips auch auf der Startseite.
- Folgen → Web Push (login-frei) — siehe
docs/NOTIFICATIONS.md. Neuer Kanal-agnostischer Kern:push_endpoints+venue_follows+notification_outbox(Migration0005), Crawler-Auslöser (nur NEUE Events, best-effort),notification-worker(Drain + Ruhezeiten 22–8h + Pruning toter Endpoints), öffentliche/api/v1/push/*-Routen, Service Worker +lib/push.ts+ Schalter auf/gemerkt. iOS-Hinweis (PWA).web-push-Lib (lädt unter Bun). VAPID-Env in.env.example. Offen vor Live: VAPID-Keys erzeugen+SOPS, VVT-Eintrag, Migration 0005 + echter Zustell-Smoke nach Deploy. APNs/E-Mail/iCal-Folge-Feed als weitere Kanäle vorgesehen.
2026-05-27 — iCal-Export, Rate-Limit, Lazy-Map, Web-Tests
2026-05-27 — iCal-Export, Rate-Limit, Lazy-Map, Web-Tests
Alles live auf seepuls.com / api.seepuls.com + verifiziert:
- iCal (RFC 5545) — löst das Marketing-Versprechen „JSON + iCal" ein.
GET /api/v1/events/:slug.ics— Einzel-Event-Download (Content-Disposition attachment).GET /api/v1/events/feed.ics?country=®ion=&category=&q=&from=&to=— abonnierbarer, gefilterter Feed (Cache-Control: max-age=900).- Generator
apps/api/src/services/ical.ts(Escaping, UTC-Dates, 75-Oktett-Folding, Pflicht-Quellen-Attribution in DESCRIPTION). 14 Vitest-Tests. - Web: „📅 Zum Kalender"-Button auf Event-Detail +
<link rel="alternate" type="text/calendar">-Discovery + sichtbarer Abo-Link auf der Startseite (spiegelt aktive Filter).
- Rate-Limit (
apps/api/src/middleware/rate-limit.ts) — In-Memory Fixed-Window proCF-Connecting-IP-Hash,429+Retry-After/RateLimit-*. Nur auf browser-direkte Vektoren:/takedown10/10min,/image-proxy100/min. NICHT auf SSR-gespiegelte Read-Routes (sonst Ein-IP-Drosselung). 5 Tests. - Kategorie-Filter-Fix —
categorywar auf der Startseite ein No-Op (Param gelesen, nie an die API gereicht; events-API kannte ihn nicht). Jetzt inlistQuery+ Venue-Filter, durch Web durchgereicht, greift auch fürfeed.ics. - Bucket-Bug-Fix — Events an Wochenmitte-Tagen (Mi–Fr bei Wochenstart Mo) fielen in kein Zeit-Bucket und verschwanden still von der Startseite. Neues „Diese Woche"-Bucket macht die Ranges lückenlos. Regressions-Test über alle Wochentage.
- MapLibre lazy —
/kartelädt die ~1 MBmaplibre-glnicht mehr beim Initial-Load, sondern per dynamischemimport()(Auto-Load nach Idle bei guter Verbindung, Tap-to-load bei Save-Data/2G). Initiales karte-Script: 1,9 kB. - Web-Test-Harness — Vitest in
apps/web(vorher 0 Tests): 26 Tests fürtime-buckets,format,feed.
Deploy-Falle dokumentiert in Memory reference_seepuls_deploy_pattern.md: docker compose -p seepuls ist Pflicht (sonst API auf falschem Netz → DB-ECONNREFUSED).
Offen aus dem Review: RSS-Feeds (FEEDS.md Welle 2, getrennt von iCal), Feed-Discovery auf Region-Pfad-Seiten, /submissions-Rate-Limit (SSR-proxied).
Phase
α-0 ✅ + α-1 ✅ + α-2 ✅ + α-3 ✅ + α-3.5 ✅ + α-4 ✅ + α-4.5 ✅ + α-5 ✅ (Live-Deploy)
+ ι-0..ι-3 ✅ LIVE seit 2026-05-18 ~23:55 auf prod. Schema-Migration
applied (venues.hero_url+gallery_urls+image_probes), Container
rebuilt + restartet, Smokes alle grün:
GET /api/v1/image-proxy?url=http://127.0.0.1/...→ 403 (SSRF)GET /api/v1/image-proxy?url=https://example.com/...→ 403 (off-whitelist)GET /api/v1/image-proxy?url=https://apollokreuzlingen.ch/.../LogoAPOLLORGB6.png→ 200, image/png 3243 Bimage_probespopulated: 1× ok + 2× blocked persisted- Web
/venue/apollo-kreuzlingenrendert SeepulsImage + onerror-Stage-1 - AGGREGATOR_POLICY-Compliance: anwaltliche §5-Review weiter offen
Plan in docs/IMAGES.md. Verbleibend: ι-3.5 (mana-media-
Cache nach 2 Wochen ι-3-Beobachtung) und ι-5 (Spotlight-Thumbs, gebündelt
mit σ-4 in seepuls-native).
Live seit 2026-05-15 auf seepuls.mana.how und seepuls-api.mana.how:
GET https://seepuls.mana.how/→ 200 (Astro-Web mit Empty-State)GET https://seepuls-api.mana.how/healthz→{"status":"ok","app":"seepuls"}GET https://seepuls-api.mana.how/api/v1/events?country=DE→ leere ListeGET https://seepuls-api.mana.how/.well-known/mana-app.json→ Manifest- 3 Container healthy auf mana-server:
seepuls-postgres,seepuls-api,seepuls-web - DB-Schema gepusht, 4 countries + 4 regions geseedet
🚀 LIVE-CRAWL aktiv seit 2026-05-29
Betreiber-Entscheidung (Till): Compliance akzeptiert, Live-Gang freigegeben.
mana-compliance-Veto-Check lief vorher (fand 1 harten Blocker: endpoints.takedown
fehlte im Manifest → behoben + base_url auf api.seepuls.com korrigiert).
Stand: SEEPULS_SCHEDULER=on, 14 aktive Venue-Sources crawlen (1/60s-Tick,
24h-Intervall). Events fließen — Konzert/Theater/Club + programm-führende Museen
liefern echte datierte Events (Apollo 10, bodensee-planetarium 24, alm-konstanz 20…).
Beim Live-Gang aufgedeckt + gefixt:
scheduler.tsTick crashte jeden Tick (orderBy(asc(sql\… nulls first`))→syntax error at or near "asc"); latenter Bug, da Scheduler nie zuvor live war. Fix:sql`… asc nulls first``.- Docker-Build war kaputt:
@mana/shared-share-protocol(devDep) ließ sich ohne Verdaccio-Auth im Build nicht ziehen →--prod(skip devDeps), Build wieder dep-arm. - zebra-kino entfernt: robots.txt
Disallow: /(nur Googlebot) → nicht crawlbar.
Offene Auflagen (kein Live-Stopper, AGGREGATOR_POLICY ✓✓):
- ETag/If-Modified-Since real verdrahten (Schema-Felder sonst tot).
- 429/503 mit
Retry-Afterstatt Pauschal-Backoff behandeln. - Museums-Ausstellungen ohne klares
starts_at(seemuseum, KULA-Wix) werden vom strikten parseDate gedroppt → 0 Events; ggf. Ausstellungs-Date-Range-Handling. - β-1 anwaltliche §5-Review bleibt Tills bewusste menschliche Entscheidung.
Was steht (Code-fertig)
| Phase | Inhalt |
|---|---|
| α-0 | Repo-Skelett (Hono+Bun), Drizzle-Schema (8 Tabellen), docker-compose Postgres :5441, db:push grün |
| α-1a | robots.txt-Parser + 24h-Cache (5xx → Full-Disallow), Crawl-Policy mit max(1.1s, robots.crawl-delay) |
| α-1b | JWT-Auth (jose+JWKS), mana-llm/mana-research/mana-geocoding Clients, Venue-Extraction-Pipeline, POST /admin/venues/from-url mit Reviewer-Stop |
| α-2 | Event-Source-Verwaltung (add/list/patch/probe/run), event-extraction-Pipeline mit strict-first parseDate, external_id_hash für Dedupe |
| α-3 | Re-Crawl-Scheduler (setInterval 60s, max 1 source/tick), DRY-Extraktion crawlAndPersistEventSource(), exp. Backoff bei fail (6h × 2^n, cap 7d) |
| α-3.5 | Seed (DE/CH/AT/LI + 4 Regions inkl. Bodensee), Public Read-API GET /events + /venues mit Filter (country/region/from/to/q) |
| α-3.6 | Stadt-Quellen (scope='city'): stadtweite Sammelkalender (Konstanz/Kreuzlingen) als event_source ohne feste Venue. Jedes Event wird zur Crawl-Zeit auf eine Venue aufgelöst (matchVenues, sonst hidden pending-review-Venue). Dedupe-Hash jetzt stadt-basiert (geoKeyForHash) statt venue-id-basiert → ein Event dedupliziert über Venue-Quelle ↔ Stadt-Kalender ↔ Screenshot-Submit hinweg. |
| α-3.7 | Venue-Event-Sources via YAML kuratierbar: optionaler eventSource-Block im Venue-YAML → Importer legt idempotent eine inaktive scope='venue'-Source an (es_yaml_<slug>). Strategie-Entscheidung: venue-direkt zuerst (eigene Programmseiten), Stadt/Guidle-Discovery als bewusste 2. Phase. Seed: 15 verifizierte Programm-URLs (Theater/Kino/Kulturhaus/Musik/Club/Museum, beide Städte). Bewusst ausgelassen: stadttheater/spiegelhalle/werkstatt (Fremd-Ticketshop jetticket), dreispitz + konstanz.de-Museen (Guidle/§87a), cinestar (Kette), galerie-mensing (Multi-Location, nicht KN-spezifisch), Bars/Clubs mit nur Social/JS (berrys/horstklub/steg4/backstage/newfax …). |
Tests: 107/107 grün. Type-check: grün durchgehend. Commits: 8 lokal in seepuls. mana@94e62b8 mit AGGREGATOR_POLICY gepusht.
Deploy-Schritte α-3.6: Migration 0004_lowly_thor.sql (event_sources: scope
- city/region_slug/country_code, venue_id nullable) wird beim API-Boot
auto-appliziert. Falls schon Events in prod stehen: einmalig
docker exec seepuls-api bun run src/jobs/rehash-events.ts(rechnet bestehende Events auf den stadt-basierten Hash um; no-op bei leer).
Stadt-Discovery (Phase 2) — Entscheidung 2026-05-29: Die stadtweiten
Kalender von Konstanz/Kreuzlingen sind beide Guidle-gespeist (guidle.com)
und JS-gerendert. HTML-Scrapen davon trifft §87a-Datenbankschutz (DE).
Sauberer Weg = Guidle-Export-Schnittstelle (offizielles XML/JSON,
support@guidle.com), braucht aber Channel-Zugang via Guidle oder die
regionalen Tourismus-Orgs — eine Outreach-Aufgabe, kein Code. Der
scope='city'-Mechanismus (α-3.6) ist gebaut und wartet nur auf diesen
Zugang + einen guidle-xml-Parser (parserHint). Bewusst NICHT blind
gebaut. Bis dahin: venue-direkt (Phase 1, α-3.7).
API-Oberfläche
Public (kein Auth)
GET /healthz·/readyz·/healthz/detailsGET /.well-known/mana-app.json— ManifestGET /api/v1/events?from&to&country®ion&q&limit+/events/:slugGET /api/v1/venues?country®ion&limit+/venues/:slugGET /api/v1/search?q(Stub)POST /api/v1/takedown— Take-Down-Antrag
Admin (JWT-gated, mana-auth)
POST /api/v1/admin/venues/from-url— Venue per Crawl anlegen (Reviewer-Stop)GET/POST /api/v1/admin/venues/:venueId/event-sources— Venue-Sources (scope='venue')POST /api/v1/admin/event-sources— Stadt-Quelle anlegen (scope='city'; body: url, city, regionSlug, countryCode)GET/PATCH /api/v1/admin/event-sources/:idPOST /api/v1/admin/event-sources/:id/probe— Vorschau ohne DB-Write (zeigt venue_name pro Event)POST /api/v1/admin/event-sources/:id/run— manueller Crawl-Run
Service-Key-gated (mana-admin)
GET /api/v1/dsgvo/export?email=·DELETE /dsgvo/users/by-email/:email
Was fehlt vor α-1 ✅
- α-1c — Integrations-Smoke: braucht laufenden mana-research +
mana-llm + mana-geocoding. Aktueller Stand lokal: nur mana-auth
online. Optionen:
- Stack lokal hochfahren (Mac Mini-Compose oder
mana/docker-compose) - Prod-Stack ansprechen (
research.mana.how,llm.mana.how,geocoding.mana.how) — braucht Service-Key für seepuls
- Stack lokal hochfahren (Mac Mini-Compose oder
- mana-research-Response-Form angenommen, nicht verifiziert.
Pre-Live-Gates (β-1 + β-2) — nicht-verhandelbar
- β-1: AGGREGATOR_POLICY anwaltliche Review.
- β-2: Take-Down-Postfach besetzt (kontakt@mana.how), Claim-Flow
live,
mana-compliance-Subagent-Veto-Check grün.
Phase α-4 — Astro-Web (nächster autonomer Schritt)
Greenfield Astro-Setup unter apps/web/:
- Liste
/(heutige + kommende Events, Filter-Bar) /heute,/wochenende,/region/:slug,/land/:code- Detail
/event/:slug,/venue/:slug - Karten-Ansicht via Leaflet-Island
- Filter als URL-State (teilbar)
- SSR via Node-Adapter, damit neue Events nach Re-Crawl ohne Rebuild sichtbar werden
- Attribution-Block pro Event (Quell-Domain + Crawl-Datum)
Infrastruktur
- Forgejo-Repo:
git.mana.how/till/seepuls✅ (angelegt 2026-05-15, Repo-ID 21, alle 12+ Commits gepusht) - DNS + Cloudflared-Tunnel:
seepuls.mana.how+seepuls-api.mana.how✅ (CNAMEs live, Tunnel-Config inmanagarten/cloudflared-config.ymlcommitted21e8bcaa5und reloaded). Beide liefern aktuell HTTP 502 — kein Container hört auf :3095/:3096. Erwartet bis Deploy.
Offene Punkte
- mana-llm Port-Drift (PORTS.md 3062 vs CLAUDE.md 3025) → seepuls folgt 3025 (Service-CLAUDE).
.npmrc-Warning bei pnpm install (${NPM_AUTH_TOKEN}ohne env) — kosmetisch, harmlos.- Service-Key bei mana-auth für seepuls anlegen (POST /admin/apps) — nötig für α-1c.
- Deploy auf mana-server (
~/projects/seepuls/-Compose) — sobald ein Container hochkommt, sind seepuls.mana.how + seepuls-api.mana.how sofort live (Cloudflared+DNS warten). - AGGREGATOR_POLICY anwaltliche Review offen.