pageta/STATUS.md
Till JS 28ca5a5792 Listen-Import gebaut — die Landing versprach ihn seit Monaten
Die Pocket-Seite beschrieb „Lese-Liste → URLs hinzufügen → Pocket-Export
(.zip)". Diese Schaltflaeche gab es nie: kein Treffer fuer .zip, jszip
oder eine Datei-Annahme im ganzen Web- und API-Code. Gleichzeitig lag der
Bulk-Weg der API (POST /api/v1/imports, 200 je Auftrag) seit Lambda-5
fertig da — api.startBulkImport() war definiert und wurde von keiner
Zeile aufgerufen.

Also gebaut statt gestrichen:

- $lib/url-list.ts zieht Adressen aus beliebigem Text (CSV, HTML-Ausfuhr,
  eingefuegte Liste). Bewusst kein Format-Erkenner — Spaltennamen
  unterscheiden sich je Dienst und je Jahr, Adressen nicht. Entdoppelt
  ueber Schluss-Schraegstrich, Fragment und Wirt-Schreibweise, wirft
  Bilder und Skripte raus. 13 Tests.
- Neuer erster Abschnitt in /lese-liste/settings: Datei waehlen oder
  einfuegen, zaehlen, importieren. Ueber 200 Adressen laufen als mehrere
  Auftraege NACHEINANDER — parallel staenden bei 5000 Adressen 25
  Auftraege in derselben Extraktions-Kette. Fortschritt, Abbruch, und
  beim Verlassen der Seite haelt die Schleife an.
- ZIP wird nicht ausgepackt, und die Oberflaeche sagt das.

Ein Test fand einen Fehler, den der Code nicht zeigte: der Adress-Sucher
endet am Semikolon (es trennt in CSV und HTML die Felder), damit wurde
"...?a=1&b=2" zu "...?a=1&amp". Entitaeten werden jetzt vor dem
Suchen aufgeloest.

Dazu drei Stellen, an denen Text und Stand auseinanderliefen:

- getpocket.com/export ist zu (gemessen: 302 auf /home; Mozilla hat die
  Daten am 2025-11-12 geloescht). Die Seite schickte Leute trotzdem hin.
  Jetzt sagt sie, was heute noch geht.
- Das PWA-Manifest bewarb "Feed, Lese-Liste, Recherche" und legte
  Verknuepfungen auf /feed und /recherche — beide ruhen seit 2026-06-01
  und leiten weiter. Ein installiertes Symbol fuehrte ins Leere.
- /konto sagte Gaesten, die Lese-Liste brauche ein Konto, und nannte die
  ruhende Recherche. Jetzt steht dort der Satz aus der i18n-Quelle, den
  auch die Native-App zeigt.
- llms.txt versprach Maschinen ".zip inkl. Tags und Highlights" und
  "Export aller Daten als JSON jederzeit". Beides unwahr.

Offen und nicht selbst entschieden: eine Ausfuhr der ganzen Liste auf
Knopfdruck gibt es nirgends — der Text ist jetzt ehrlich, der Knopf
fehlt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MhVjQpDUkFZGtFWsLid4PT
2026-09-05 12:53:25 +02:00

432 lines
27 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Pageta — Status
Letzter Stand: **2026-09-05**. **LIVE 🚀 + voll-featured**
## 2026-09-05 — Der Umzugs-Weg gab es nur auf dem Papier
Ein Durchgang durch App, Landing und Doku hat eine Lücke gefunden, die
niemand meldet, weil beide Seiten für sich stimmig aussahen: **die
Landing beschrieb einen Import, den die App nicht hatte.**
- `/pocket-import` sagte „Lese-Liste → ‚URLs hinzufügen' → ‚Pocket-Export
(.zip)'". Diese Schaltfläche gab es nie. Im ganzen Web- und API-Code
steht kein einziger Treffer für `.zip`, `jszip` oder eine Datei-Annahme.
- Sie schickte Leute auf `getpocket.com/export`. Gemessen am 2026-09-05:
**302 auf `/home`**. Mozilla hat den Export geschlossen und die
Konten-Daten am 2025-11-12 gelöscht. Der Weg war also nicht nur
unbeschrieben, er war zu.
- Der Bulk-Weg der API (`POST /api/v1/imports`, 200 je Auftrag) lag seit
λ-5 fertig da — **ohne jede Oberfläche**. `api.startBulkImport()` war im
Client definiert und wurde von keiner Zeile aufgerufen.
**Gebaut statt gestrichen.** Die Zusage war die richtige, nur ohne
Umsetzung — also hat die App jetzt den Import, den die Landing versprach:
- `$lib/url-list.ts` — zieht Adressen aus beliebigem Text (CSV, HTML-
Ausfuhr, eingefügte Liste). **Bewusst kein Format-Erkenner**: die
Spaltennamen unterscheiden sich je Dienst und je Jahr, Adressen nicht.
Entdoppelt über Schluss-Schrägstrich, Fragment und Wirt-Schreibweise,
wirft Bilder/Skripte raus. 13 Tests.
- Neue Fläche in `/lese-liste/settings` (erster Abschnitt): Datei wählen
oder einfügen → zählen → importieren. Listen über 200 laufen als
mehrere Aufträge **nacheinander** (nicht parallel — sonst stehen bei
5 000 Adressen 25 Aufträge gleichzeitig in derselben Extraktions-Kette).
Fortschritt, Abbruch, und beim Verlassen der Seite hält die Schleife an.
- ZIP wird **nicht** ausgepackt und die Oberfläche sagt das, statt eine
Binärdatei zu durchsuchen und „nichts gefunden" zu melden.
🛑 **Der Test hat einen Fehler gefunden, den der Code nicht zeigte:** der
Adress-Sucher endet am Semikolon (es trennt in CSV und HTML die Felder) —
`…?a=1&amp;b=2` wurde damit zu `…?a=1&amp`. Entitäten werden jetzt VOR
dem Suchen aufgelöst. Ohne den einen Test wäre jede Adresse mit zwei
Abfrage-Feldern aus einer HTML-Ausfuhr still kaputt importiert worden.
**Drei weitere Stellen, an denen Text und Stand auseinanderliefen:**
- Das **PWA-Manifest** bewarb „Feed, Lese-Liste, Recherche" und legte
Verknüpfungen auf `/feed` und `/recherche`. Beide ruhen seit
2026-06-01 und leiten weiter — ein installiertes Symbol führte ins
Leere. Jetzt: Lese-Liste, Markierungen, Artikel speichern.
- `/konto` sagte Gästen „Lese-Liste, Markierungen und Recherche brauchen
ein mana-Konto" — nannte die ruhende Recherche und widersprach der
local-first-Zusage der eigenen i18n-Quelle. Jetzt steht dort der Satz
aus `mana/i18n/pageta.de.json`, den auch die Native-App zeigt.
- `llms.txt` versprach Maschinen „Pocket-Import (.zip) inkl. Tags und
Highlights" und „Export aller Daten als JSON jederzeit". Beides
unwahr; korrigiert.
🔴 **Offen und bewusst nicht selbst entschieden:** eine **Ausfuhr der
ganzen Liste auf Knopfdruck** gibt es nicht. Weder pageta noch das
mana-Konto haben eine Schaltfläche dafür; die DSGVO-Route ist
Service-Key-gesichert, ein Mensch muss sie auslösen. Die Landing hat das
bis heute als „jederzeit verfügbar" behauptet — der Satz ist jetzt
ehrlich („auf Anfrage"), aber der Wert `datensouveraenitaet` verlangt den
Knopf. Local-first heisst: die Daten liegen im Gerät, eine Ausfuhr wäre
clientseitig und klein.
🛑 **Und ein Betriebsfund:** `pnpm --filter @pageta/web check` scheiterte
mit einem unverständlichen vite-Typfehler — zwei vite-Instanzen im Baum
(terser 5.49 gegen 5.51), obwohl das Lockfile nur eine kennt. `pnpm
install` räumt es weg, danach 0 Fehler. Wer den Fehler liest, sucht
sonst im eigenen Code.
## 2026-07-30 — Tagging am echten Bestand: drei Funde
40 Artikel über die Mana-Cloud getaggt (`claude-haiku-4.5`, ~1,5 s je Artikel).
Die Tag-Qualität stimmt (_Handelspolitik, Justizirrtum, Kinderschutz,
Batterietechnik_), aber der erste Blick auf die Karte hat drei Dinge
aufgedeckt:
1. **Nabenknoten.** `tech` (der einzige Alt-Tag) wurde an 39 von 40 Artikeln
vorgeschlagen und zog die Karte zu einem Stern zusammen. Tags ab 80 %
Anteil werden jetzt aus der Zeichnung genommen — benannt statt
verschwiegen, abschaltbar, am Artikel bleiben sie.
2. **Beschriftungs-Salat.** Das Kräfte-Layout trennt Kreise, nicht Wörter.
Neu: Beschriftungslängen fließen ins Layout ein, und `label-placement.ts`
setzt jedes Wort deterministisch unten / oben / gar nicht.
3. **Vokabular wuchs nicht mit.** Der Stapel lud die vergebenen Tags einmal
beim Öffnen — Artikel 40 kannte nur den Stand vor Artikel 1. Folge: 60 Tags
auf 40 Artikel (fast jeder genau einmal, also kaum Verbindungen) plus
Dubletten wie „Kryptografie" neben „Kryptographie". `GrowingVocabulary`
reicht jetzt das mitwachsende Vokabular in jeden Prompt.
**Zweiter Durchlauf nachgeholt (nach Neu-Login).** Mit mitwachsendem Vokabular
kamen 84 Tags für 38 Artikel — und diesmal überwiegend **vorhandene**
(Recht, Kinderschutz, Prompting, Nachhaltigkeit, Energieeffizienz). Genau die
Querverbindungen, die vorher fehlten: aus einem Feld von Zweier-Inseln wurde
ein zusammenhängendes Netz.
**Dritter Fund dabei:** das dichte Netz war unlesbar — 43 von 75
Beschriftungen fielen weg. Mehr Fläche hilft nicht (ein größerer viewBox
skaliert die Schrift proportional mit herunter; gemessen: 2000×1350 = 0
Kollisionen bei ~8 px Schrift, höher-statt-breiter bringt 12→7). Bei
deutschen Komposita passen ~40 Wörter auf einen Bildschirm, nicht 75. Also
filtert die Karte auf **wiederkehrende Tags** (ab 2 Artikeln) — selbst-
entscheidend ab 45 Knoten und nur, wenn danach ≥8 übrig bleiben, sonst wäre
eine kleine Lese-Liste leer. Ergebnis: 30 Knoten, 2 ausgeblendete Wörter.
**Stand jetzt: 32 Tags aus 46 getaggten Artikeln, keiner mehr ohne Tag** (die
letzten 6 lagen nur an der 40er-Obergrenze je Lauf und wurden nachgezogen).
**Dubletten-Werkzeug** (`packages/pageta-domain/src/tag-merge.ts`):
`findTagVariants` gruppiert Schreibvarianten (ß→ss, ph→f, Bindestriche weg)
und Tippfehler (Editierdistanz 1, erst ab 8 Zeichen + gleicher Wortanfang).
Am Bestand gefunden und zusammengeführt: Kryptografie/Kryptographie,
Music-/Musik-Streaming, Webstandards/Web-Standards. Die Zielform wählt der
Mensch — bei Gleichstand entscheidet die Heuristik alphabetisch und schlug
prompt „Music-Streaming" für eine deutsche Liste vor.
## 2026-07-29 — Themenkarte + Tag-Vorschläge
Zwei Stufen, live unter `/rueckblick/themen` und im Werkzeug-Menü der
Lese-Liste.
- **Themenkarte** (`packages/pageta-domain/src/graph.ts`,
`apps/web/src/lib/graph-layout.ts`): Tags als Ko-Vorkommens-Graph —
Knoten sind Tags (auf Wunsch auch Quellen), eine Kante heißt „kommt am
selben Artikel vor". Kein LLM, kein Server: Zählarbeit über den lokalen
Event-State, Layout als eigenes Fruchterman-Reingold, deterministisch
(Goldwinkel-Spirale statt `Math.random`) und einmal fertig gerechnet
statt laufender Simulation. Die Lese-Liste versteht jetzt `?tag=`.
- **Tag-Vorschläge** (`packages/pageta-domain/src/autotag.ts`): Vokabular-
Weg über das Modell (die bereits vergebenen Tags gehen in den Prompt,
höchstens EIN neuer Begriff — sonst Wildwuchs) mit KI-freiem Rückfall
über TF-IDF gegen die eigene Liste. Einzeln im Reader, im Stapel aus der
Lese-Liste (max. 40 je Lauf). Geschrieben wird erst auf Bestätigung,
über das bestehende `ArticleTagsSet`-Event — bewusst kein eigener
Event-Typ, das hätte `@mana/shared-schemas` mitgezogen.
- Neue Route `POST /api/v1/tags/suggest` (JWT, 60/min), greift nur bei
erlaubtem Cloud-Pfad.
**Am Live-Bestand geprüft, zwei Funde:** die ganze Lese-Liste trug genau
1 Tag (die Karte blieb also leer — Auto-Tagging war die Voraussetzung,
nicht die Kür), und der statistische Weg schlug anfangs Wortformen statt
Begriffe vor („Ausländischer", „Führte", „Kilometern"). Behoben über die
deutsche Großschreibung als Substantiv-Signal plus vorsichtige
Grundform-Zusammenführung.
Offen: ohne lokales Modell **und** ohne erlaubte Mana-Cloud bleibt nur der
statistische Weg; dessen Labels sind Wörter, keine Begriffe. Der
Vokabular-Weg ist ungetestet gegen ein echtes Modell, weil im Testkonto
beides aus ist.
## 2026-07-27 — Mehrseitige Artikel kommen vollständig an
Bookmarklet und Erweiterung sammeln Folgeseiten jetzt **im Tab des
Nutzers** und schicken sie als Liste; der Server extrahiert jede Seite
einzeln und fügt sie in Lese-Reihenfolge zusammen.
Anlass: `golem.de/news/…-2607-211258.html` hat drei Seiten, gespeichert
wurde bisher nur Seite 1 (911 von rund 2400 Wörtern). Serverseitig ist da
nichts zu holen — Golem beantwortet auch Seite 2 ohne Zustimmungs-Cookie
mit der Abfrage. Ein `fetch` aus dem Tab bekommt sie (gemessen: 200, kein
Redirect).
- **Folgeseiten-Erkennung ohne quellenspezifische Selektoren**
(`apps/web/src/lib/page-collector.js`): ein Link zählt, wenn seine
Adresse sich von der aktuellen nur in einem Seitenzähler unterscheidet
(`-2.html`, `?page=2`, `/seite/2/`); ohne Seitenliste folgt es
`rel="next"`. Deckel: 20 Seiten, 6 MB, 300 ms Pause je Abruf.
- **Gestrippt vor dem Senden** — `<style>/<noscript>/<template>` raus,
Skript-_Inhalte_ geleert, Tags/Attribute und JSON-LD bleiben (sonst
verlören `siteNameFromJsonLd` und die CMP-Signaturen in
`consent-wall.ts` ihre Grundlage). Gemessen: Golem 401 → 145 KB,
Extraktions-Ergebnis nachweislich unverändert (Titel, Wortzahl,
siteName, Bild, Datum identisch an the-decoder.de und winfuture.de).
- **API**: `POST /articles/extract/preview` nimmt zusätzlich `pages`
(max 20), antwortet mit `pageCount`. `html` bleibt gültig — Bookmarklets
liegen im Lesezeichen des Nutzers und werden nicht mitdeployt.
- **Ehrlich bei Lücken**: kam eine erkannte Folgeseite nicht durch,
springt die add-Seite nicht weiter, sondern sagt „x von y Seiten".
- Am Golem-Artikel verifiziert: 3 Seiten, 0 fehlend, 298 KB statt ~1,2 MB.
**Wer schon ein Bookmarklet installiert hat, muss es einmal neu ablegen** —
ein altes speichert weiterhin nur Seite 1. Der Hinweis steht in den
Einstellungen.
Offen: `pageCount` wird nicht im Event gespeichert (bräuchte ein Feld in
`evt_article_saved_v1`/`evt_article_enriched_v1`, also `@mana/shared-schemas`
mit 14 Konsumenten). Server-seitiges Nachladen für die Wege ohne Browser
(Bulk-Import, Feed-Archiv, „Neu extrahieren") steht ebenfalls noch aus —
derselbe Detektor, aber im API-Prozess.
## 2026-07-23 — Quellen / Blog-Hub
Artikel können einer Quelle (`source:<hostname>`) zugeordnet werden.
Hub unter `/quelle/howtomarketagame.com`. Feed-Import legt Quelle an und
verknüpft Artikel; ältere Imports nachträglich unter Settings. Lese-Liste
zeigt Quellen-Chips + Filter `?quelle=`. Schema `@mana/shared-schemas`
0.1.23 (SourceUpserted / ArticleSourceLinked) — vorerst Vendor-Tarball,
bis Verdaccio-Publish wieder greift.
## 2026-07-23 — Blog/Feed einmalig importieren
Unter `/lese-liste/settings`: Blog- oder Feed-URL → Vorschau → Batch-Save
in die Lese-Liste. Backend: `POST /api/v1/imports/from-feed[/preview]`
(RSS-Discovery, WP-`?paged=N`, optional Sitemap-Fallback). Bulk-Worker
schreibt jetzt nach **sync2** (`emitArticleSaved`), nicht mehr in die
legacy Postgres-`saved_articles`. Cap Feed-Archiv: 1000.
## 2026-06-03 — Rechtsklick-Kontextmenü auf der Lese-Liste
Geteiltes `@mana/shared-ui/organisms` ContextMenu auf jedem Artikel-`<li>`:
Lesen / Original öffnen / Status (gelesen·ungelesen) / Archivieren bzw.
Aus-Archiv / Taggen / Löschen — dieselben `api.batch*`-Handler wie die
Batch-Toolbar, aber direkt pro Artikel ohne Auswahl-Modus. Ergänzt die
sichtbaren Aktionen (Touch/Mobile bleibt bedienbar). svelte-check 0 neue
Fehler, build grün, Screenshot verifiziert, live auf `app.pageta.com`.
(`683ff3f`)
## Folge-Sprint 2026-05-22 — Web-/Native-Parität + Cross-Device
### Landing-Cutover 2026-05-22 19:30
**Subdomain-Migration ausgeführt** ([`LANDING_CUTOVER.md`](../mana/docs/playbooks/LANDING_CUTOVER.md)):
`pageta.com` zeigt jetzt auf die Astro-Marketing-Landing
(`pageta-landing:3201`), die App lebt unter `app.pageta.com`
(`pageta-web:3100`, war vorher `pageta.com`).
- `GET https://pageta.com/` → **200** Marketing-Landing
(Title: „pageta — Pocket ist tot. Lies in Ruhe weiter.")
- `GET https://pageta.com/{ueber,mitwirken,pocket-import}` → **200**
- `GET https://pageta.com/sitemap.xml` → **200** (4 Marketing-URLs)
- `GET https://pageta.com/.well-known/apple-app-site-association` →
**200 application/json** (Universal Links bleiben funktional)
- `GET https://pageta.com/{lese-liste,article/*,feed,konto,markierungen,recherche,auth/*}` →
**301 → app.pageta.com/...**
- `GET https://app.pageta.com/` → **307 → /lese-liste** (App-Default)
- `GET https://app.pageta.com/{lese-liste,feed,konto,markierungen}` → **200**
- `GET https://pageta-api.mana.how/healthz` → `{"status":"ok"}`
- `POST https://pageta-api.mana.how/api/v1/summarize` (kein JWT) → **401** ✓
**Offene Punkte aus dem Cutover:**
- Native iOS-App-AASA hardcoded auf `pageta.com` — Universal-Links auf
`/article/*` und `/lese-liste/*` werden weiterhin vom Landing-nginx
korrekt ausgeliefert (gleicher JSON-Payload, gleiche App-IDs). Im
nächsten TestFlight-Build verifizieren.
- Erst-Login auf `app.pageta.com` bei einem User, der vorher auf
`pageta.com` eingeloggt war: ggf. erneutes Login nötig, weil App-
spezifische Cookies (falls vorhanden) auf der alten Apex-Domain
verwaisen. mana-auth-Cookie auf `.mana.how` ist nicht betroffen.
### Cross-Device Cookie-Wall via @mana/shared-schemas@0.1.11
`warning: string | null` lebt jetzt im event-sourced `ArticleState`,
nicht mehr in localStorage. Zwei neue Events:
- `ArticleWarningFlagged` setzt `state.warning` (heute nur
`"probable_consent_wall"`)
- `ArticleWarningCleared` löscht ihn (UI „Hinweis ausblenden")
Bewusst eigene Events statt ArticleSaved-Payload-Erweiterung —
`evt_article_saved_v1` bleibt frozen. Native + Web schreiben + lesen
beide via `ev/article:<id>`-Aggregat; Banner zeigt sich auf jedem
Gerät, das den Event-Stream replayed hat.
**Schaden + Recovery 2026-05-22 13:43:**
Beim ersten Publish-Versuch wurde `@mana/shared-schemas` versehentlich
komplett unpublished (DELETE auf `/-rev/latest`). 0.1.1/0.1.6/0.1.7/
0.1.8/0.1.10 sind weg, nur 0.1.11 ist live. Recovery via Lockfile-Bump
- `pnpm install` in allen 8 Schwester-Apps (uload/moodlit/werdrobe/
comicello/nutriphi/wordeck/mukke/herbatrium) auf `^0.1.11`. Funktional
unkritisch — Schema-Änderungen nur in pageta-Sub-Schema, andere
Sub-Schemas byte-gleich.
Konkret eingefügt, alles im Web:
- **Routing-Restructure**: `/` redirected auf `/lese-liste` (= Standardseite,
wie Native). Feed-Tab lebt jetzt auf `/feed`. Tab-Reihenfolge im Layout
spiegelt Native: Lese-Liste · Markierungen · Feed · Recherche · **Konto**.
- **`/konto` neue Seite**: E-Mail/Tier/Konto-ID, Abmelden + „Konto verwalten ↗",
`ThemeToggle` + `VariantPicker` (alle 8 mana-Themes), Bookmarklet-Link,
Über-Sektion (Datenschutz/Verein/pageta.com). ThemeToggle ist aus dem
Header raus (lebt jetzt in /konto), genau wie bei Native.
- **Save-from-Feed**: Feed-Items haben jetzt einen „+ In Lese-Liste"-Button
(zuvor read-only, nur Original-Link).
- **Vor/Nach-Sibling-Navigation** im Reader-Footer (Pfeil + Titel-Karte,
identisch zur Native-Variante).
- **Cookie-Wall-Banner** im Web-Reader. Lokal-persistiert via
`localStorage`-Map keyed by articleId (Schema-Bump in `@mana/shared-schemas`
vermieden). Banner mit „Original im Browser öffnen" + „Hinweis ausblenden".
- **Tag-Read-back**: `Article.tags` durchgereicht aus `ArticleState`. Reader
zeigt Tags als Pills + Tag-Hinzufügen-Input; emittiert `ArticleTagsSet`.
Native kann Tags ab sofort auch sehen, sobald sie das DTO-Feld liest
(eigener Followup, betrifft `pageta-native/Sources/Core/Domain/Article.swift`).
- **Reading-Progress** persistiert: Window-Scroll-Listener mit Debounce
(1.5 s), emit `ArticleProgressUpdated` ab 2 %-Differenz; beim Re-Open
springt der Reader an die gespeicherte Position. `beforeunload`-Hook
flusht Pending-Progress vor Tab-Close.
- **LLM-Zusammenfassung im Reader**: neue Route
`POST /api/v1/summarize` in pageta-api ruft `mana-llm` mit Alias
`mana/fast-text`. Client-Sektion „Zusammenfassen ✨" im Reader, ohne
Persistierung (Volltext bleibt Source-of-Truth). Pendant zum Native
`ArticleSummarySection` (Foundation Models / Gemma).
## Phase
**λ-0 ✅ + λ-1 ✅ + λ-2 ✅ + λ-3 ✅ + λ-4 ✅ + λ-5 ✅ + λ-6 ✅** +
**Lift-A ✅ + Lift-B ✅** + **CI ✅ + Deploy ✅ + Login-Flow ✅ +
Verein-Landing ✅** — `pageta.mana.how` ist erreichbar, alle
ursprünglich geplanten λ-Phasen sind live.
## Folge-Sprint 2026-05-17
- **Echter Login-Flow** — Cards-Pattern: SSO-Cookie → /auth/refresh → JWT
in localStorage. `apps/web/src/lib/auth/session.svelte.ts`,
`apps/web/src/routes/auth/callback/+page.svelte`, AuthGate im Layout
mit silent-refresh + Redirect zum Auth-Portal. mana-auth-web hat
`lesen` in der App-Allowlist (Forest-Branding, pageta.mana.how +
localhost:3100 als erlaubte Redirect-Origins).
- **Verein-Landing** — mana-ev.ch zeigt Lesen als 12. App-Card +
Intro „elf eigenständige Produkte". Eigenes `lesen.svg`.
- **λ-5 Bulk-Import** — POST /api/v1/imports (cap 200/Job), Tick-Worker
alle 2s mit Postgres `pg_try_advisory_lock`, State-Machine pending →
extracting → saved/duplicate/error. Worker bootet im Container.
- **λ-6 Tool-Handler** — 5 Tools live unter /api/v1/tools/:name
(save_article, list_articles, archive_article, add_article_tag,
add_article_highlight). Alle JWT-gated + zod-validated. JSON-Schemas
pro Tool im `app-manifest.json#tools[]` für mana-mcp-Discovery.
## Live-Smoke (2026-05-16 19:13)
- `GET https://pageta.mana.how/` → 200 (SvelteKit-SSR)
- `GET https://pageta-api.mana.how/healthz` → `{"status":"ok","app":"pageta"}`
- `GET https://pageta-api.mana.how/.well-known/mana-app.json` → Manifest
- `GET https://pageta-api.mana.how/api/v1/feed` (kein JWT) → 401
- `POST https://pageta-api.mana.how/api/v1/dsgvo/export` (kein Key) → 401
- `POST .../dsgvo/export` (Service-Key) → 200, leerer User-Export
- `mana-news-pool` Tick: **1133 Artikel** im Pool
## Container (alle healthy)
| Container | Port | Wo |
| ----------------- | -------------- | ---------------------------------------------------------------------------------- |
| `pageta-postgres` | 127.0.0.1:5447 | `pageta-postgres`-Volume `/Volumes/ManaData/pageta/postgres/` |
| `pageta-api` | 127.0.0.1:3099 | im Netz `pageta_default` + `manacore-monorepo_default` |
| `pageta-web` | 127.0.0.1:3100 | im Netz `pageta_default` |
| `mana-news-pool` | 127.0.0.1:3079 | im Netz `manacore-monorepo_default`, DB `mana_news_pool` auf `mana-infra-postgres` |
## Was steht
| Phase | Inhalt | Status |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ |
| λ-0 | Repo-Skelett: pnpm-workspace, turbo, tsconfig, prettier, docker-compose | ✅ |
| λ-0 | `apps/api` Hono-Skeleton (`/healthz`, `/.well-known/mana-app.json`) | ✅ |
| λ-0 | `apps/web` SvelteKit-Skeleton mit drei Tab-Stubs | ✅ |
| λ-0 | `packages/pageta-domain` Pure-TS Feed-Engine + Types + Sources, **9/9 Tests grün** | ✅ |
| λ-0 | PORTS.md-Eintrag in mana/ (`pageta-api: 3099`, `pageta-web: 3100`) | ✅ |
| λ-1 | DB-Schema (Drizzle, 8 Tabellen in Schema `reader`), `db:push` grün | ✅ |
| λ-2 | Auth-Middleware (JWKS + serviceKey), `/dsgvo/export+delete`, `/search`, `/share/receive`, `/tools/:name` | ✅ |
| λ-3 | Core-API: `/feed` (Pool-Proxy + Server-side-Ranking), `/articles` CRUD, `/highlights`, `/preferences`, `/reactions` mit Auto-Weight-Diff, `/extract/preview` | ✅ |
| λ-4 | Web-UI: 3 Tabs, API-Client, Feed + Lese-Liste, **Reader-Detail `/article/[id]` mit Highlight-Selection (4 Farben, Selection-Listener, Plain-Text-Offsets, Highlight-Liste mit Delete), Status-Toggles, Favorit, Delete** | ✅ |
| CI | `.github/workflows/ci.yml` — install, type-check, db:push, build, web-check, domain-tests | ✅ |
| Compose | `infrastructure/docker-compose.production.yml` + Dockerfiles für api/web; `.env.production.example` | ✅ |
| λ-5 | Bulk-Import-Worker (Job/Items/Pickup-Pipeline gegen 200 URLs/Job) | offen |
| λ-6 | Föderation: `accepts: [url]` Handler vollständig, Tools-Handler-Implementierungen | offen |
| λ-7 | Deployment auf mana-server (pageta.mana.how, pageta-api.mana.how) | bereit, ausstehend |
## Plattform-Lifts (parallel, vor λ-7)
| Phase | Inhalt | Status |
| ------ | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Lift-A | `@mana/shared-rss` aus managarten nach `mana/packages/` + Verdaccio | ✅ (Code in `mana/packages/shared-rss/`, npm publish pending) |
| Lift-B | `news-ingester` aus managarten → `mana-news-pool` Plattform-Service (Port 3079) | ✅ (Code in `mana/services/mana-news-pool/`, eigene DB `mana_news_pool`, Schema `pool`, Dockerfile, Compose-Snippet im CLAUDE.md) |
| Lift-C | News-Research-Endpoints in `mana-research` integrieren | deferred |
## Verifizierter Stand (lokal, 2026-05-16)
- `pnpm install` grün
- `pnpm docker:up` → Postgres :5443
- `pnpm --filter @pageta/api db:push` → 8 Tabellen + 11 Indizes in Schema `reader`
- `pnpm --filter @pageta/api type-check` → grün
- `pnpm --filter @pageta/api dev` → bootet auf :3099
- `GET /healthz` → 200
- `GET /.well-known/mana-app.json` → Manifest 200
- `GET /api/v1/feed` (ohne Auth) → 401
- `GET /api/v1/articles` (ohne Auth) → 401
- `POST /api/v1/articles/extract/preview` (ohne Auth) → 401
- `POST /api/v1/dsgvo/export` (ohne Service-Key) → 401
- `POST /api/v1/dsgvo/export` (mit Service-Key + userId) → 200 leerer Export
- `pnpm --filter @pageta/domain test` → 9/9 grün
- `pnpm --filter @pageta/web check` → 0 errors / 0 warnings (252 files)
- `pnpm --filter @pageta/web build` → grün, adapter-node Bundle
## Was noch fehlt für „real benutzbar"
> **Historisch — Stand λ-6, Mai 2026.** Alles in dieser Liste ist seither
> erledigt (Auth, Reader, Recherche-Anbindung, CI, Deploy). Sie bleibt als
> Protokoll stehen; wer den heutigen Stand sucht, liest die Einträge oben.
- **Echte Auth-Integration**: heute `localStorage['pageta.dev_token']`-Stub. Migration auf
`@mana/shared-auth` mit Auth-Portal-Redirect kommt mit λ-6 (oder mit dem
Auth-Portal-Plan im Memory `project_auth_portal_migration`).
- **Echter Pool-Endpoint**: `/api/v1/feed` ruft `MANA_NEWS_POOL_URL/feed` —
zeigt heute auf `localhost:3066` (managartens news-ingester). Nach Lift-B
wechseln auf `mana-news-pool:3079`.
- **Reader-Detail-View** `/article/[id]` mit Highlight-Selection
- **Recherche-Tab** (heute Stub) braucht mana-research-Endpoints
- **CI** (.github/workflows/ci.yml)
- **Deploy** (docker-compose.macmini.yml + Cloudflared)
## Offene Punkte
- **Port-Range knapp**: 3099+3100 sind die letzten freien. Memoro/Who-Range
(3100+) wird damit bewusst angekratzt — beide haben eigene Repos.
- ~~**Encryption AUS**~~ — **überholt, seit 2026-08-02 falsch.** Verschlüsselt
wird auf JEDEM Schreibpfad, Client wie Server (`enc:1:…`, fail-closed).
Die geltende Fassung steht als Invariante 4 in
[`CLAUDE.md`](CLAUDE.md) — dort nachlesen, nicht hier.
- **`shared-rss` vendored**: Code-Copy in `apps/api/src/lib/shared-rss/`.
Wechsel auf Verdaccio-Paket nach Lift-A.
- **Tier-Gate**: Manifest sagt `tier_required: "public"`. Wenn Lesen Beta
bleiben soll bis Mature, hier umstellen + `auth.apps`-Eintrag bei mana-auth.
- **Source-Liste**: SOURCES_META in `packages/pageta-domain/src/sources.ts`
muss synchron mit `mana-news-pool` bleiben. Heute Source-of-Truth ist
`managarten/services/news-ingester/src/sources.ts`. Drift-Risiko bis
Lift-B durch.
- **DEV-Token-Setup**: kein lokaler mana-auth gestartet → man kann lokal
keine Live-Calls testen. Quick-Fix: `pageta.dev_token` im Browser-
LocalStorage von einem `mana-auth`-Token aus einer anderen App
übernehmen. Sauber: Login-Flow gegen `auth.mana.how` in λ-6.