diff --git a/apps/api/src/modules/articles/import-projection.ts b/apps/api/src/modules/articles/import-projection.ts index e832f83b0..51a005afa 100644 --- a/apps/api/src/modules/articles/import-projection.ts +++ b/apps/api/src/modules/articles/import-projection.ts @@ -39,8 +39,6 @@ export interface ImportJobRow { spaceId: string | null; totalUrls: number; status: 'queued' | 'running' | 'paused' | 'done' | 'cancelled'; - leasedBy: string | null; - leasedUntil: string | null; startedAt: string | null; finishedAt: string | null; savedCount: number; @@ -192,8 +190,6 @@ function projectJob(userId: string, recordId: string, merged: Row | null): Impor spaceId: optStr(merged.spaceId), totalUrls, status, - leasedBy: optStr(merged.leasedBy), - leasedUntil: optStr(merged.leasedUntil), startedAt: optStr(merged.startedAt), finishedAt: optStr(merged.finishedAt), savedCount: num(merged.savedCount) ?? 0, diff --git a/apps/mana/CLAUDE.md b/apps/mana/CLAUDE.md index 003ea5be5..c5804bdfb 100644 --- a/apps/mana/CLAUDE.md +++ b/apps/mana/CLAUDE.md @@ -275,6 +275,7 @@ Agents interact with the app through tools — each one either auto (executes si | food | — | `nutrition_summary`, `log_meal` | | news | `save_news_article` | — | | news-research | `research_news` | — | +| articles | `save_article`, `archive_article`, `tag_article`, `add_article_highlight`, `import_articles_from_urls` (auto) | `list_articles` | | journal | `create_journal_entry` | — | | habits | `create_habit`, `log_habit` | `get_habits` | | contacts | `create_contact` | `get_contacts` | @@ -304,6 +305,36 @@ Each template bundles: optional agent + optional scene layout + optional starter Full architecture (Planner prompt + parser in `@mana/shared-ai`, server-side runner, Postgres actor column, materialized snapshots, Multi-Agent gating, server-side web-research, Prometheus metrics + status.mana.how integration): [`docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md`](../../docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md) §20 (AI Workbench) + §21 (Mission Grants) + §22 (Multi-Agent Workbench). +## Articles bulk-import + +Background pipeline that ingests N URLs into a user's reading list as +one Job, with the same encryption + scope semantics as a single-URL +save. Same shape as the AI mission runner: state lives in +`sync_changes`, a server-side worker projects + writes back, the +client encrypts the final article. + +``` +client createJob(urls) + → bulkAdd articleImportItems(state='pending') + articleImportJobs(queued) + → sync push → mana_sync.sync_changes + → apps/api worker tick (every 2s, advisory-lock-gated) + → extractFromUrl (shared-rss / Readability) + → write articleExtractPickup row + flip item → 'extracted' + → sync pull → liveQuery + → consume-pickup encryptRecord + articleTable.add + → flip item → 'saved' (or 'duplicate' / 'consent-wall') + → delete pickup row + → server flips job → 'done', emits ArticleImportFinished +``` + +Tables: `articleImportJobs`, `articleImportItems`, `articleExtractPickup` +(all plaintext-allowlisted — see `data/crypto/plaintext-allowlist.ts`). +Actor on every server-write: `system:articles-import-worker`. Worker +metrics under `mana_api_articles_import_*`. Hard cap of 200 URLs per +job (`MAX_URLS_PER_JOB` in `modules/articles/stores/imports.svelte`). + +Plan: [`docs/plans/articles-bulk-import.md`](../../docs/plans/articles-bulk-import.md). + ## Reference Documents | Path | Purpose | diff --git a/apps/mana/apps/web/src/lib/data/database.ts b/apps/mana/apps/web/src/lib/data/database.ts index 89fce5dea..95f89dae5 100644 --- a/apps/mana/apps/web/src/lib/data/database.ts +++ b/apps/mana/apps/web/src/lib/data/database.ts @@ -1465,6 +1465,34 @@ db.version(59).stores({ documentTags: null, }); +// v60 — Articles bulk-import schema cleanup. +// Two changes, both lossless: +// +// 1. articleImportJobs: drop the unused `leasedBy`/`leasedUntil` +// columns. They were on the original v57 schema as a soft-lease +// handshake, but the worker uses pg_try_advisory_xact_lock +// instead and never wrote them. Dexie's index list shrinks but +// no data is migrated — the columns simply disappear from +// future writes; existing rows still carry them as zombies (a +// one-shot row-rewrite to delete the field would be a hard- +// migration; not worth it for two never-written nulls). +// 2. articleImportItems: drop the standalone `state` index. +// `[jobId+state]` covers the only hot query (worker's per-job +// pending scan). The state-solo index had no call site — +// retryFailed uses [jobId+state]. Trimming the index list saves +// a bit of write amplification. +// +// Kept on the schema (not dropped here): `idx` standalone index on +// articleImportItems. It's also unused right now, but the +// JobDetailView currently sorts items in JS via .sort((a,b)=>a.idx-b.idx); +// if that view ever switches to a server-side ordered scan we'd want +// the index back, and re-adding indexes after the fact is more +// painful than keeping a small one around. +db.version(60).stores({ + articleImportJobs: 'id, status, [spaceId+status], _updatedAtIndex', + articleImportItems: 'id, jobId, [jobId+state], idx', +}); + // ─── Sync Routing ────────────────────────────────────────── // SYNC_APP_MAP, TABLE_TO_SYNC_NAME, TABLE_TO_APP, SYNC_NAME_TO_TABLE, // toSyncName() and fromSyncName() are now derived from per-module diff --git a/apps/mana/apps/web/src/lib/modules/articles/components/BulkImportForm.svelte b/apps/mana/apps/web/src/lib/modules/articles/components/BulkImportForm.svelte index ea7c72065..848b33d94 100644 --- a/apps/mana/apps/web/src/lib/modules/articles/components/BulkImportForm.svelte +++ b/apps/mana/apps/web/src/lib/modules/articles/components/BulkImportForm.svelte @@ -7,13 +7,14 @@ --> -{#if jobs.length > 0} +{#if allJobs.length > 0}
-

Bisherige Imports

+
+

Bisherige Imports

+ +
+ {#if visibleJobs.length === 0} +

Keine Jobs in dieser Ansicht.

+ {/if}