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&". 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
432 lines
27 KiB
Markdown
432 lines
27 KiB
Markdown
# 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&b=2` wurde damit zu `…?a=1&`. 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.
|