Articles ist jetzt als Workbench-App in apps.ts registriert
(icon BookOpen, collection 'articles', paramKey 'articleId') und
landet damit im Scene-App-Picker. HomeView/ListView/HighlightsView/
StatsView teilen sich eine neue ArticlesTabShell, die sowohl als
SvelteKit-Route als auch als Workbench-Karte rendert.
Shell (ArticlesTabShell.svelte):
- Top-Bar mit QuickAddInput (URL einfügen + Enter = Save + goto
Reader; kein Preview-Schritt) und Settings-Gear.
- Tab-Leiste darunter: Leseliste | Highlights | Favoriten | Stats.
Leseliste ist Default (initialTab='list').
- Tab-Wechsel läuft intern via $state + Svelte-Context — kritisch
für die Workbench-Karte, wo goto() den User aus der Karte kicken
würde. getArticlesTabContext() aus tab-context.ts gibt tief
verschachtelten Sektionen eine switchTo(tab)-API.
- Padding 1rem 1.25rem auf der Shell selbst — PageShell.page-body
hat null padding, sonst klebt QuickAdd am Card-Rand. Im Route-
Kontext addiert's sich zum (app)-Layout-Padding ohne zu viel.
Tabs:
- Leseliste (list): bestehende ListView mit optionalem
initialFilter-Prop. Continue-Reading-Strip (HomeSectionWeiterlesen
horizontal carousel) erscheint über den Filter-Chips wenn
status='reading'-Artikel existieren und filter ∈ {all, reading}.
Filter-Chips sind einzeilig + horizontal scrollbar mit
scroll-snap-Einrast; inaktive Chips haben jetzt sichtbare
Background-Füllung + Border via color-mix(currentColor) — adaptiv
fürs Theme.
- Highlights (highlights): HighlightsView unverändert (nur der
eigene Header + Zurück-Button raus, liegt jetzt in der Shell).
- Favoriten (favorites): ListView mit initialFilter='favorites' —
Shell-Shortcut auf den Filter.
- Stats (stats): neue StatsView mit Stats-Strip (savedThisWeek,
finishedThisWeek, avg reading time), Highlight-Counter, Top-
Sources und Archiv-Link.
Routes (unter (tabs)-Gruppe):
- /articles → initialTab="list" (Default)
- /articles/list → initialTab="list" (alias)
- /articles/highlights → initialTab="highlights"
- /articles/favorites → initialTab="favorites"
- /articles/stats → initialTab="stats"
Detail/Add/Settings bleiben bewusst ausserhalb — die haben ihren
eigenen Reader/Form-Chrome und sollen die Tab-Leiste nicht zeigen.
Neue Files:
- ArticlesTabShell.svelte (Tab-Host)
- tab-context.ts (Cross-Tab-Switch-Context)
- components/ArticleCard.svelte (shared Card aus ListView extrahiert,
row + compact Varianten)
- components/QuickAddInput.svelte (URL-Input aus HomeView extrahiert)
- components/HomeSectionSources.svelte
- components/HomeSectionStats.svelte
- components/HomeSectionWeiterlesen.svelte
- views/StatsView.svelte
- routes/(app)/articles/(tabs)/{+page,list,highlights,favorites,stats}
Gelöscht:
- HomeView.svelte (Overview-Tab wurde rausgenommen auf User-Feedback)
- HomeSectionFrisch/Highlights/Favorites (durch eigene Tabs ersetzt)
docs/plans/articles-homepage.md dokumentiert den Architektur-Plan,
inklusive der Entscheidung für "eine Card pro Domain, interne Tabs"
statt zwei separater App-Registrierungen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
14 KiB
Articles — Workbench Homepage
Status (2026-04-22)
Proposed. Follow-up auf die M1–M8-Umsetzung (siehe articles-module.md).
Ziel
Die /articles-Landing ist heute eine flache Filter-Chips + Karten-Liste. Für einen Pocket-Klon ist das funktional aber „langweilig" — der User sieht keinen kuratierten Einstieg, keinen Fortschritt, keine Wiederaufnahme-Empfehlung. Bei Readwise Reader / Matter / Omnivore ist die Landing eine reiche Übersicht mit mehreren Sektionen; die flache Liste steht als Fallback daneben.
Dieses Plan-Dokument beschreibt eine neue HomeView als Default-Einstieg — multi-sektional, reaktiv, leanes Data-Layer (alle Queries existieren schon). Die bestehende ListView bleibt 1:1 erhalten und ist via Tab oder direkter Route (/articles/list) weiter erreichbar.
Abgrenzung
- Kein Workbench-Cards-Rewrite. Articles registriert sich als eine App mit einer
list-View. Die HomeView ist der neue Body dieser View — intern mehrere Sektionen, extern ein einziger Einstieg. Siehe workbench-cards-migration.md — das Prinzip „eine Karte pro Domain, interne Tabs wenn nötig" passt hier. - Kein Scene-Template. Wir schaffen keinen neuen Template-Handler („Lese-Scene"); Articles landet als ganz normale Workbench-App.
- Kein Ersatz für das Dashboard-Widget.
ArticlesUnreadWidgetbleibt auf dem globalen/dashboard— das ist ein anderer Surface (Dashboard-Tile) mit anderem Zweck (3-Zeilen-Schnappschuss im Widget-Grid). - Keine neuen Queries/Stores. Alles was die Sections brauchen ist bereits in
queries.tsvorhanden:useAllArticles,useStats,useAllHighlights. Falls wir was fehlt, wird's dort ergänzt — nicht im HomeView.
Warum jetzt
- M1–M8 sind durch, das Modul funktioniert end-to-end — Polish-Phase.
- Nutzer kommt jetzt öfter vorbei (Bookmarklet + Share-Target → mehr Saves pro Woche). Eine Landing die sagt „hier ist was Neues, hier warst du stehengeblieben" lohnt sich plötzlich.
- Articles-as-Workbench-App-Registrierung fehlt noch komplett — das Modul lebt heute nur als Route, nicht als draggable Scene-App. Diese Homepage-Iteration ist der natürliche Moment das mitzunehmen.
Entscheidungen vorab
- Eine HomeView, keine Tabs. Keine „Home vs. List"-Umschaltung in der UI. Die Liste-Sektionen DER Homepage ersetzen die alte ListView-Funktionalität. Wenn User wirklich nur die Chip-Filter-Liste will, geht er auf
/articles/list(separate Route, rendert weiterhinListView.svelte). - Scroll-Layout, kein Bento-Grid. Sections stacken vertikal (wie
myday/ListView.svelte). Kein CSS-Grid-Layout mit Bento-Kacheln — das skaliert auf Mobile schlecht und ist optisch lauter. Scroll-Landing bleibt ruhig und funktional. - Gleiche Theme-Tokens wie das restliche Mana-UI (
bg-surface,text-muted-foregroundetc.). Kein Reader-Theme hier — die Homepage ist UI, nicht Content. Das unterscheidet sie von DetailView (Reader). - Keine Persistenz der Sektions-Reihenfolge. User kann Sektionen nicht umordnen/ausblenden. Wenn das Bedürfnis entsteht → separates Ticket. Startpunkt ist ein gut kuratiertes fixes Layout.
View-Struktur
┌──────────────────────────────────────────────────┐
│ HEADER │
│ "Artikel" [+ Neu] [✎ Highlights] [⚙ Settings]
│ │
│ Weiterlesen (wenn status='reading' existiert) │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ │ │ │ │ │ horizontal scroll │
│ │card │ │card │ │card │ mit Progress-Bar │
│ └─────┘ └─────┘ └─────┘ │
│ │
│ Frisch in der Leseliste (status='unread') │
│ ┌─────────────────────┐ │
│ │ Artikel-Karte │ vertikal, max 8 │
│ └─────────────────────┘ │
│ ┌─────────────────────┐ │
│ │ Artikel-Karte │ │
│ └─────────────────────┘ │
│ [ Alle ungelesenen →] │
│ │
│ Letzte Highlights (wenn vorhanden) │
│ ┌─────────────────────┐ │
│ │ "…quote text…" │ max 5 │
│ │ Artikel-Titel │ │
│ └─────────────────────┘ │
│ [ Alle Highlights → ] │
│ │
│ Diese Woche │
│ 12 gespeichert · 4 gelesen · 7 min pro Artikel │
│ │
│ Deine Quellen (top 5 siteName counts) │
│ • spiegel.de 14 Artikel │
│ • theverge.com 8 │
│ • … │
│ │
│ Favoriten (wenn welche markiert) │
│ ┌─────────────────────┐ │
│ │ ★ Artikel-Karte │ │
│ └─────────────────────┘ │
│ │
│ Archiv-Link │
│ [ 37 archivierte Artikel → ] │
└──────────────────────────────────────────────────┘
Sektionen im Detail
-
Weiterlesen (Continue Reading) — nur wenn mindestens ein Artikel mit
status='reading'existiert. Horizontaler Karussell-Scroll, 3–5 sichtbar auf Desktop. Jede Karte zeigt Titel + Site +readingProgressals dünnen Progress-Bar unten. Click → Reader. -
Frisch in der Leseliste — unread Artikel, sortiert
savedAt DESC, max 8. Wenn mehr → Link „Alle ungelesenen →" der nach/articles/list?filter=unreadgeht. -
Letzte Highlights — max 5 Highlights sortiert
createdAt DESC, gruppiert per Artikel (ein Eintrag pro Artikel, mit erstem Highlight als Preview und Count falls mehrere). Click → Artikel im Reader. Link „Alle Highlights →" nach/articles/highlights. -
Diese Woche (Stats-Strip) — eine Zeile, drei Zahlen:
savedThisWeek,finishedThisWeek, durchschnittliche Lesezeit (ø readingTimeMinutesaller unread/reading Artikel). Keine Grafik — eine Text-Zeile. -
Deine Quellen — top 5
siteNamenach Count, mit Total-Count pro Site. Click auf eine Site →/articles/list?filter=all&site=<siteName>(Site-Filter in der ListView neu, siehe „Offene Fragen"). -
Favoriten — nur wenn
favorites > 0. Max 4 Karten. Gleicher Card-Style wie „Frisch". -
Archiv-Link — schlichte Zeile am Ende: „37 archivierte Artikel →". Kein Embedded-Inhalt, reiner Link in die gefilterte ListView.
Empty-State: wenn der User 0 Artikel hat → identisch zur aktuellen ListView-Empty-State („Noch nichts gespeichert" + CTA + Hinweis auf Bookmarklet).
Daten-Queries (alle existieren bereits)
| Sektion | Query | Datei |
|---|---|---|
| Weiterlesen | useAllArticles() filtered status==='reading' |
queries.ts |
| Frisch | useAllArticles() filtered status==='unread', slice(0,8) |
queries.ts |
| Highlights | useAllHighlights(), slice(0,5) |
queries.ts |
| Stats-Strip | useStats() — direkt savedThisWeek, finishedThisWeek, topSites |
queries.ts |
| Quellen | useStats().topSites |
queries.ts |
| Favoriten | useAllArticles() filtered isFavorite, slice(0,4) |
queries.ts |
| Archiv-Count | useStats().archived |
queries.ts |
Keine neue Query notwendig, nur Views die $derived darüberlegen.
Struktur
apps/mana/apps/web/src/lib/modules/articles/
├── HomeView.svelte ← NEU — Default-Einstieg
├── ListView.svelte ← bleibt, Fallback für Power-User
├── views/
│ ├── DetailView.svelte (unverändert)
│ └── HighlightsView.svelte (unverändert)
└── components/
├── HomeSectionWeiterlesen.svelte ← NEU
├── HomeSectionFrisch.svelte ← NEU
├── HomeSectionHighlights.svelte ← NEU
├── HomeSectionStats.svelte ← NEU
├── HomeSectionSources.svelte ← NEU
├── HomeSectionFavorites.svelte ← NEU
└── ArticleCard.svelte ← NEU — geteilte Karte, aus ListView extrahiert
Die einzelnen Section-Components sind bewusst klein und isoliert. HomeView ist nur Layout + Section-Orchestration.
Shared ArticleCard.svelte wird aus der ListView extrahiert (aktuell inline), damit beide Views identisch aussehen.
Routing
/articles→ mountetHomeView(war bisherListView)/articles/list(neu) → mountetListView(Fallback-Route für User die die flache Liste wollen)/articles/[id](unverändert)/articles/add(unverändert)/articles/highlights(unverändert)/articles/settings(unverändert)
/articles/list verlinkt man aus der HomeView via „Alle ungelesenen →" Buttons; direkte URL-Navigation ist der Backup.
App-Registry-Integration
Neu in apps/mana/apps/web/src/lib/app-registry/apps.ts — Articles als draggable Scene-App registrieren:
registerApp({
id: 'articles',
name: 'Artikel',
color: '#f97316',
icon: BookOpen, // oder BookmarkSimple aus @mana/shared-icons
views: {
list: { load: () => import('$lib/modules/articles/HomeView.svelte') },
detail: { load: () => import('$lib/modules/articles/views/DetailView.svelte') },
},
contextMenuActions: [
{
id: 'new-article',
label: 'URL speichern',
icon: Plus,
action: () => goto('/articles/add'),
},
],
collection: 'articles',
paramKey: 'articleId',
dragType: 'article',
getDisplayData: (item) => ({
title: (item.title as string) || 'Artikel',
subtitle: item.siteName as string | undefined,
}),
});
Danach ist Articles im Scene-App-Picker verfügbar und User kann „Articles" in jede Workbench-Scene ziehen — dort rendert dann die HomeView statt des Dashboard-Widgets. Das Dashboard-Widget bleibt für den separaten /dashboard-Surface.
UI-Tokens
Alle Sektions-Chrome (Überschriften, Buttons, Cards) nutzt die globalen Theme-Tokens aus @mana/shared-tailwind:
- Überschriften:
text-foreground font-semibold text-sm uppercase tracking-wide - Cards:
bg-card border-border hover:bg-surface-hover - Muted text:
text-muted-foreground - Primary-Akzente (Favoriten-Stern, Weiterlesen-Progress): weiterhin Articles-Orange
#f97316— konsistent mit dem restlichen Modul-Branding
Keine Tailwind-Raw-Farben (bg-gray-*, bg-white/*). Der pre-existing validate-theme-tokens.mjs-Audit darf nicht neu anschlagen.
Reihenfolge (Milestones)
- M9.1 — ArticleCard extrahieren — bestehende ListView-Card-Logik in
components/ArticleCard.svelteauslagern, ListView angepasst, keine Verhaltensänderung. Ziel: keine Regression, nur Refactor. - M9.2 — HomeView-Skelett + Sektionen — alle 6 Section-Components neu, HomeView orchestriert. Leere Sektionen werden ausgeblendet (
{#if ...}). Route/articlesmountet neu die HomeView. - M9.3 — ListView-Route —
/articles/listangelegt, rendert weiterhin die bestehendeListView.svelte. „Alle ungelesenen →"-Links aus HomeView linken dorthin. - M9.4 — App-Registry-Eintrag — Articles in
apps.tsregistriert, Icon ausgewählt, drag-type + getDisplayData. Articles erscheint im Scene-App-Picker. - M9.5 — Site-Filter in ListView (klein) — ListView bekommt
?site=<siteName>-Query-Param-Support damit „Deine Quellen"-Links funktionieren. Tag-Filter analog via?tag=<tagId>falls nicht schon da.
Offene Fragen
- Continue-Reading-Karten Horizontal vs. Vertical: horizontal-scroll wirkt iOS-Reader-haft, vertical konsistent mit Rest. Ich neige zu horizontal für die Continue-Reading (weil's eine „Carousel of things you started"-Affordanz hat) und vertikal für alles andere. Wenn's zu bunt wird, alles vertikal — wenig Aufwand umzubauen.
- Sektions-Reihenfolge — obige Liste ist mein Vorschlag. „Weiterlesen" zuerst fühlt sich richtig an (wie bei Netflix „Continue Watching"). „Frisch" danach. Highlights-Preview als drittes. Alternativ wäre „Frisch" primär wenn kein Weiterlesen da ist — das passiert aber schon durch
{#if}-Gating. - Max-Counts pro Sektion (8 / 5 / 4) — Bauchgefühl. Bei User-Feedback nachjustieren. Dokumentiert als CONSTANTS-File falls's mal konfigurierbar werden soll.
- Icon für die App-Registry — ich würde
BookOpenoderBookmarkSimpleaus@mana/shared-iconsnehmen. Passt zu „Lesen / Später lesen". Abstimmung mit dem aktuellenAPP_ICONS.articles-SVG (Lesezeichen-Dokument) — konsistent halten. - „Site-Filter" URL-Schema: ListView akzeptiert aktuell nur
?filter=<status>. Neu brauche ich?site=<name>+?tag=<id>. Pure Query-Param-Erweiterung in der ListView. Kein neues Routing. - Mobile-Layout: max-width 700px wie DetailView-Meta-Bar? Oder volle Breite mit ggf. 2-Spalten-Grid auf Desktop? Erste Iteration: single-column, 800px max, vertikal. 2-Spalten nur wenn später Bedarf.