diff --git a/apps/mana/apps/web/src/lib/modules/articles/ListView.svelte b/apps/mana/apps/web/src/lib/modules/articles/ListView.svelte
index effe32cee..bd5cb4cc9 100644
--- a/apps/mana/apps/web/src/lib/modules/articles/ListView.svelte
+++ b/apps/mana/apps/web/src/lib/modules/articles/ListView.svelte
@@ -79,9 +79,20 @@
Später lesen — gespeicherte Web-Artikel, offline verfügbar.
-
@@ -175,6 +186,26 @@
margin: 0 0 0.25rem 0;
font-size: 1.75rem;
}
+ .header-actions {
+ display: flex;
+ gap: 0.4rem;
+ align-items: center;
+ flex-shrink: 0;
+ }
+ .icon-btn {
+ padding: 0.5rem 0.65rem;
+ border-radius: 0.55rem;
+ border: 1px solid var(--color-border, rgba(0, 0, 0, 0.15));
+ background: transparent;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ font-size: 1rem;
+ line-height: 1;
+ }
+ .icon-btn:hover {
+ border-color: var(--color-border-strong, rgba(0, 0, 0, 0.3));
+ }
.add-btn {
padding: 0.5rem 1rem;
border-radius: 0.55rem;
diff --git a/apps/mana/apps/web/src/lib/modules/articles/components/AddUrlForm.svelte b/apps/mana/apps/web/src/lib/modules/articles/components/AddUrlForm.svelte
index 2142d94db..62fe91ee7 100644
--- a/apps/mana/apps/web/src/lib/modules/articles/components/AddUrlForm.svelte
+++ b/apps/mana/apps/web/src/lib/modules/articles/components/AddUrlForm.svelte
@@ -2,7 +2,9 @@
AddUrlForm — paste URL → preview → save.
Flow:
- 1. User pastes (or types) a URL
+ 1. User pastes (or types) a URL, OR the page is opened with a URL
+ pre-filled via query string (?url=… / ?text=… / ?title=…). The
+ Web Share Target + bookmarklet both land here that way.
2. On "Vorschau abrufen": check scope-local dedupe first; if found,
offer "öffnen" instead of re-extracting (saves one round-trip).
Otherwise call /api/v1/articles/extract and render the preview.
@@ -10,9 +12,15 @@
articlesStore.saveFromExtracted — no second server call.
4. Navigate into the new article so the user lands directly in the
reader view.
+
+ Pre-filled URLs auto-trigger the preview on mount so the three-click
+ "share from browser → saved" flow really is three clicks: share →
+ pick Mana → hit "In Leseliste speichern".
-->
+
+
+ Artikel-Einstellungen — Mana
+
+
+
+
+
+
+ Bookmarklet
+
+ Zieh den Button unten in deine Lesezeichen-Leiste. Ein Klick auf einer beliebigen Webseite
+ öffnet
+ /articles/add mit der aktuellen URL vorausgefüllt — du bestätigst nur noch die Vorschau.
+
+
+
+ Quellcode anzeigen
+ {bookmarklet}
+
+
+ Funktioniert in jedem Desktop-Browser. In Safari: Lesezeichen anlegen mit einer beliebigen
+ URL, dann nachträglich die URL durch das Snippet ersetzen.
+
+
+
+
+ Share-Target (Android / Chromium)
+
+ Wenn du Mana als App installierst (Browser-Menü „Zum Startbildschirm hinzufügen"), taucht
+ „Mana" in deinem OS-Share-Sheet auf. Teilen aus dem Browser oder einer anderen App → Mana
+ auswählen → Artikel wird direkt in der Leseliste vorgeschlagen.
+
+
+ iOS-Safari unterstützt die Web-Share-Target-API derzeit nicht — nutze dort das Bookmarklet.
+
+
+
+
+
diff --git a/apps/mana/apps/web/vite.config.ts b/apps/mana/apps/web/vite.config.ts
index 9914b208c..2b2768eb4 100644
--- a/apps/mana/apps/web/vite.config.ts
+++ b/apps/mana/apps/web/vite.config.ts
@@ -49,6 +49,17 @@ export default defineConfig({
},
{ name: 'Chat', short_name: 'Chat', url: '/chat', description: 'Chat öffnen' },
],
+ // Web Share Target — installed PWA shows up in the OS share
+ // sheet as "Mana" and lands on /articles/add with the URL
+ // pre-filled (AddUrlForm reads ?url + ?text + ?title). The
+ // `text` param is listed alongside `url` because some
+ // senders (Chrome Android, WhatsApp) stuff the URL into the
+ // text field.
+ shareTarget: {
+ action: '/articles/add',
+ method: 'GET',
+ params: { title: 'title', text: 'text', url: 'url' },
+ },
})
),
],
diff --git a/docs/plans/articles-module.md b/docs/plans/articles-module.md
index bdc1e4ac0..20a175636 100644
--- a/docs/plans/articles-module.md
+++ b/docs/plans/articles-module.md
@@ -12,11 +12,15 @@
**M5 Migration von news:type='saved': DONE** (commit `04293ed5e`) — Boot-gated Migration in `modules/articles/migrations/from-news.ts` (localStorage-Sentinel `mana:articles:from-news-migration:v1`), decrypt→re-encrypt zwischen den beiden Field-Allowlists, Status-Mapping `isArchived→archived` / `isRead→finished` / sonst `unread`, Source-Rows werden soft-deletet. News-Code deprecated: `saveFromUrl` + `extractFromUrl` entfernt, `save_news_article` AI-Tool behält seinen Namen (wegen Mission-History) und leitet intern aufs `articles`-Modul um. `/news/add` + `/news/saved` sind Redirects. `news-research` „Speichern"-Buttons routen auf `/articles/[id]`.
-**M6 AI-Tools: DONE** (commit pending) — 5 neue Einträge im `AI_TOOL_CATALOG` (`shared-ai/src/tools/schemas.ts`): `list_articles` (auto), `save_article` / `archive_article` / `tag_article` / `add_article_highlight` (alle propose). `modules/articles/tools.ts` enthält die `execute`-Funktionen, registriert in `data/tools/init.ts`. `tag_article` dedupliziert case-insensitive über den globalen Pool und legt Tags via `tagMutations.createTag` an falls nötig. `add_article_highlight` snappt auf die erste wörtliche Fundstelle in `article.content` und lehnt den Call ab wenn der Text nicht exakt vorkommt (kein Orphan-Highlight). Policy/Executor/Server-Planner leiten sich automatisch aus dem Katalog ab.
+**M6 AI-Tools: DONE** (commit `5924f4fac`) — 5 neue Einträge im `AI_TOOL_CATALOG` (`shared-ai/src/tools/schemas.ts`): `list_articles` (auto), `save_article` / `archive_article` / `tag_article` / `add_article_highlight` (alle propose). `modules/articles/tools.ts` enthält die `execute`-Funktionen, registriert in `data/tools/init.ts`. `tag_article` dedupliziert case-insensitive über den globalen Pool und legt Tags via `tagMutations.createTag` an falls nötig. `add_article_highlight` snappt auf die erste wörtliche Fundstelle in `article.content` und lehnt den Call ab wenn der Text nicht exakt vorkommt (kein Orphan-Highlight). Policy/Executor/Server-Planner leiten sich automatisch aus dem Katalog ab.
**Hinweis AiProposalInbox:** Der apps/mana/CLAUDE.md-Abschnitt erwähnt `
` als Inline-Mount, aber die Komponente existiert im aktuellen Codebase nicht — nach dem `pendingProposals`-Table-Drop in Dexie v29 wurde die Proposal-Darstellung auf `server-iteration-staging` + den Cross-Module-Inbox im Mission-Detail-View umgestellt. Articles-Proposals tauchen dort automatisch auf. Falls die Inline-Komponente wieder reaktiviert wird, muss nur der Mount in `ListView.svelte` ergänzt werden.
-Nächster Schritt: M7 (Share-Target + Bookmarklet) oder M8 (HighlightsView + Stats + Dashboard-Widget).
+**M7 Share-Target + Bookmarklet: DONE** (commit pending) — `@mana/shared-pwa` bekommt neue Types (`PWAShareTarget`, `PWAShareTargetParams`), `createPWAConfig` threadet `shareTarget` in den Manifest, `ManifestConfig.share_target?` ergänzt. Web-App: `vite.config.ts` setzt `shareTarget: { action: '/articles/add', method: 'GET', params: { title, text, url } }`; `AddUrlForm` liest Query-Params in `onMount` (inkl. URL-Regex-Fallback auf `text` weil Chrome Android / WhatsApp den Link dort reinstecken), triggert auto-Vorschau. Neue Route `/articles/settings` rendert Bookmarklet-Karte (Drag-to-Bookmark + Copy-Snippet + expandable Quellcode) und Share-Target-Erklärung. `ListView` bekommt Zahnrad-Button zum Settings-Aufruf.
+
+Nicht im Scope (bewusst ausgelassen): die „optional" im Plan markierte `_pendingUrls`-Offline-Queue. Kann als M7b nachgereicht werden wenn das Problem auftaucht.
+
+Nächster Schritt: M8 (HighlightsView + Stats + Dashboard-Widget).
## Ziel
diff --git a/packages/shared-pwa/src/config.ts b/packages/shared-pwa/src/config.ts
index 7392739b4..135b837f5 100644
--- a/packages/shared-pwa/src/config.ts
+++ b/packages/shared-pwa/src/config.ts
@@ -48,6 +48,7 @@ export function createPWAConfig(options: PWAConfigOptions): PWAConfig {
backgroundColor = DEFAULT_BACKGROUND_COLOR,
preset = 'standard',
shortcuts = [],
+ shareTarget,
categories = DEFAULT_CATEGORIES,
includeAssets = [],
globIgnores = [],
@@ -91,6 +92,17 @@ export function createPWAConfig(options: PWAConfigOptions): PWAConfig {
}));
}
+ // Web Share Target — lets installed PWAs appear in the OS share
+ // sheet. Browsers that don't support the spec ignore the field.
+ if (shareTarget) {
+ manifest.share_target = {
+ action: shareTarget.action,
+ method: shareTarget.method ?? 'GET',
+ enctype: shareTarget.enctype,
+ params: shareTarget.params,
+ };
+ }
+
// Build workbox config
const workbox: WorkboxConfig = {
globPatterns: DEFAULT_GLOB_PATTERNS,
diff --git a/packages/shared-pwa/src/index.ts b/packages/shared-pwa/src/index.ts
index 8f15be8c2..28c83e7d2 100644
--- a/packages/shared-pwa/src/index.ts
+++ b/packages/shared-pwa/src/index.ts
@@ -46,6 +46,8 @@ export type {
PWAConfigOptions,
PWAConfig,
PWAShortcut,
+ PWAShareTarget,
+ PWAShareTargetParams,
WorkboxPreset,
ManifestConfig,
ManifestIcon,
diff --git a/packages/shared-pwa/src/types.ts b/packages/shared-pwa/src/types.ts
index a83b1a038..5a7d7ebca 100644
--- a/packages/shared-pwa/src/types.ts
+++ b/packages/shared-pwa/src/types.ts
@@ -20,6 +20,36 @@ export interface PWAShortcut {
url: string;
}
+/**
+ * Web Share Target API configuration. When an installed PWA declares
+ * a share target, the OS share sheet offers the app as a destination
+ * for URLs / text / titles. The browser invokes `action` with the
+ * selected data mapped to the `params` field names.
+ *
+ * Reference: https://www.w3.org/TR/web-share-target/
+ * Browser support: Chromium (Android + desktop installed PWAs).
+ * Safari / Firefox ignore the field gracefully.
+ */
+export interface PWAShareTargetParams {
+ /** Query/form param that carries the shared page title. */
+ title?: string;
+ /** Query/form param for free-form shared text. */
+ text?: string;
+ /** Query/form param for the shared URL. */
+ url?: string;
+}
+
+export interface PWAShareTarget {
+ /** In-app URL the browser should navigate to with the shared payload. */
+ action: string;
+ /** HTTP method. GET maps data to query params, POST to form data. */
+ method?: 'GET' | 'POST';
+ /** Required for method=POST — typical value: 'multipart/form-data'. */
+ enctype?: string;
+ /** Maps the spec's title/text/url slots onto your own param names. */
+ params: PWAShareTargetParams;
+}
+
/**
* Configuration options for createPWAConfig
*/
@@ -67,6 +97,13 @@ export interface PWAConfigOptions {
*/
shortcuts?: PWAShortcut[];
+ /**
+ * Web Share Target config. Installed PWA becomes a destination in
+ * the OS share sheet for URLs / text / titles. Ignored by browsers
+ * that don't support the spec (Safari / Firefox).
+ */
+ shareTarget?: PWAShareTarget;
+
/**
* App categories for store listings
* @default ["productivity", "utilities"]
@@ -165,6 +202,12 @@ export interface ManifestConfig {
url: string;
icons?: Array<{ src: string; sizes: string }>;
}>;
+ share_target?: {
+ action: string;
+ method: 'GET' | 'POST';
+ enctype?: string;
+ params: PWAShareTargetParams;
+ };
}
/**