seepuls/STATUS.md
Till JS bb81c9b9ea
Some checks are pending
CI / validate (push) Waiting to run
docs(status): UI/UX-Feinschliff (Filter/Karten/Wärme/Flaggen) dokumentiert
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:05:17 +02:00

14 KiB
Raw Permalink Blame History

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", da inline-flex das 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 (~2835), 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/search war ein Stub (results: []), ist jetzt eine echte unified Suche (kommende Events + Orte, ILIKE über Titel/Beschreibung/Venue-Name bzw. Name/Beschreibung/Stadt). Event-q trifft 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) + q in 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 (Migration 0005), Crawler-Auslöser (nur NEUE Events, best-effort), notification-worker (Drain + Ruhezeiten 228h + 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=&region=&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 pro CF-Connecting-IP-Hash, 429 + Retry-After/RateLimit-*. Nur auf browser-direkte Vektoren: /takedown 10/10min, /image-proxy 100/min. NICHT auf SSR-gespiegelte Read-Routes (sonst Ein-IP-Drosselung). 5 Tests.
  • Kategorie-Filter-Fixcategory war auf der Startseite ein No-Op (Param gelesen, nie an die API gereicht; events-API kannte ihn nicht). Jetzt in listQuery + Venue-Filter, durch Web durchgereicht, greift auch für feed.ics.
  • Bucket-Bug-Fix — Events an Wochenmitte-Tagen (MiFr 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/karte lädt die ~1 MB maplibre-gl nicht mehr beim Initial-Load, sondern per dynamischem import() (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ür time-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 B
  • image_probes populated: 1× ok + 2× blocked persisted
  • Web /venue/apollo-kreuzlingen rendert 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 Liste
  • GET 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.ts Tick 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-After statt 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/details
  • GET /.well-known/mana-app.json — Manifest
  • GET /api/v1/events?from&to&country&region&q&limit + /events/:slug
  • GET /api/v1/venues?country&region&limit + /venues/:slug
  • GET /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-sourcesStadt-Quelle anlegen (scope='city'; body: url, city, regionSlug, countryCode)
  • GET/PATCH /api/v1/admin/event-sources/:id
  • POST /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:
    1. Stack lokal hochfahren (Mac Mini-Compose oder mana/ docker-compose)
    2. Prod-Stack ansprechen (research.mana.how, llm.mana.how, geocoding.mana.how) — braucht Service-Key für seepuls
  • 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 in managarten/cloudflared-config.yml committed 21e8bcaa5 und 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.