From 264c4c3087269dafdfb6f9443ea923b88c74c1a9 Mon Sep 17 00:00:00 2001 From: Till JS Date: Mon, 20 Apr 2026 20:41:09 +0200 Subject: [PATCH] feat(broadcast): M2 audience + editor + compose wizard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Core authoring loop works end-to-end: create a draft, filter an audience from contacts, write content in a rich-text editor, save. Send is still stubbed (M4 gets mana-mail's bulk endpoint). Dependencies - @tiptap/core + starter-kit + image + link + placeholder (3.22.4) - shared-auth/tsconfig: allowImportingTsExtensions + rewriteRelativeImportExtensions so tsc accepts shared-types' explicit .ts imports. Was blocking EVERY pnpm install postinstall hook in the repo — fixing it here unblocks everyone, not just broadcast. Module - queries.ts: useAllCampaigns / useAllTemplates with scoped-db + crypto, computeStats (counts + open/click rates per year), formatRate helper - stores/settings.svelte.ts: singleton with ensure/get/update, same pattern as invoices settings - stores/campaigns.svelte.ts: createCampaign (pulls sender defaults from settings), updateCampaign / updateContent / updateAudience (draft-only edit guard), schedule / cancel / duplicate / deleteCampaign, plus an applyServerStatus hook for M4's orchestrator to write back progress Audience - audience/segment-builder.ts: pure matchContact / filterAudience / countAudience / describeAudience. AND semantics across filters. Drops contacts without a usable email so estimatedCount never inflates. - audience/AudienceBuilder.svelte: tag-chip UI with live count, dedup (same tag twice toggles op instead of stacking), greys out already- referenced tags in the picker Editor - editor/Editor.svelte: Tiptap wrapper with onMount / onDestroy, toolbar (bold/italic/H1/H2/lists/link/image), bind on content (Tiptap JSON + derived HTML/plaintext). Image upload reuses invoices' mana-media uploader pragmatically; extract to @mana/shared-uload later. Compose wizard - views/ComposeView.svelte: 4-step stepper (Audience → Content → Preflight → Send). Steps 3+4 stubbed pragmatically. Autosave on step change so content survives navigation. Step 3/4 gated on earlier readiness so the user can't skip. Routes - /broadcasts/new: bootstraps a draft + redirects to edit - /broadcasts/[id]/edit: guarded on status=='draft' - ListView: working "+ Neue Kampagne" button, rows open edit Tests - 17 unit tests for segment-builder covering tag has/not-has/AND, email eq/contains case-insensitivity, no-email filtering, no-mutation, describeAudience resolver + fallback Plan: docs/plans/broadcast-module.md §M2. Next: M3 HTML-render with email-safe inlining + preview. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/mana/apps/web/package.json | 5 + .../src/lib/modules/broadcast/ListView.svelte | 106 ++- .../broadcast/audience/AudienceBuilder.svelte | 298 +++++++ .../audience/segment-builder.test.ts | 170 ++++ .../broadcast/audience/segment-builder.ts | 96 +++ .../modules/broadcast/editor/Editor.svelte | 392 +++++++++ .../web/src/lib/modules/broadcast/index.ts | 22 + .../web/src/lib/modules/broadcast/queries.ts | 181 ++++ .../broadcast/stores/campaigns.svelte.ts | 238 ++++++ .../broadcast/stores/settings.svelte.ts | 83 ++ .../broadcast/views/ComposeView.svelte | 406 +++++++++ .../(app)/broadcasts/[id]/edit/+page.svelte | 54 ++ .../routes/(app)/broadcasts/new/+page.svelte | 55 ++ packages/shared-auth/tsconfig.json | 5 +- pnpm-lock.yaml | 777 +++++++++++++----- 15 files changed, 2635 insertions(+), 253 deletions(-) create mode 100644 apps/mana/apps/web/src/lib/modules/broadcast/audience/AudienceBuilder.svelte create mode 100644 apps/mana/apps/web/src/lib/modules/broadcast/audience/segment-builder.test.ts create mode 100644 apps/mana/apps/web/src/lib/modules/broadcast/audience/segment-builder.ts create mode 100644 apps/mana/apps/web/src/lib/modules/broadcast/editor/Editor.svelte create mode 100644 apps/mana/apps/web/src/lib/modules/broadcast/queries.ts create mode 100644 apps/mana/apps/web/src/lib/modules/broadcast/stores/campaigns.svelte.ts create mode 100644 apps/mana/apps/web/src/lib/modules/broadcast/stores/settings.svelte.ts create mode 100644 apps/mana/apps/web/src/lib/modules/broadcast/views/ComposeView.svelte create mode 100644 apps/mana/apps/web/src/routes/(app)/broadcasts/[id]/edit/+page.svelte create mode 100644 apps/mana/apps/web/src/routes/(app)/broadcasts/new/+page.svelte diff --git a/apps/mana/apps/web/package.json b/apps/mana/apps/web/package.json index a8fd81faa..6a56633a1 100644 --- a/apps/mana/apps/web/package.json +++ b/apps/mana/apps/web/package.json @@ -77,6 +77,11 @@ "@mana/spiral-db": "workspace:*", "@mana/wallpaper-generator": "workspace:*", "@quotes/content": "workspace:*", + "@tiptap/core": "^3.22.4", + "@tiptap/extension-image": "^3.22.4", + "@tiptap/extension-link": "^3.22.4", + "@tiptap/extension-placeholder": "^3.22.4", + "@tiptap/starter-kit": "^3.22.4", "@types/pako": "^2.0.4", "@types/suncalc": "^1.9.2", "date-fns": "^4.1.0", diff --git a/apps/mana/apps/web/src/lib/modules/broadcast/ListView.svelte b/apps/mana/apps/web/src/lib/modules/broadcast/ListView.svelte index db225ddff..de511cc61 100644 --- a/apps/mana/apps/web/src/lib/modules/broadcast/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/broadcast/ListView.svelte @@ -1,24 +1,28 @@
@@ -27,7 +31,7 @@

Broadcasts

Newsletter und Kampagnen an deine Kontakte

- + {#if campaigns.length === 0} @@ -38,20 +42,27 @@ Verschicke deinen ersten Newsletter — mit Rich-Text-Editor, Tracking und DSGVO-konformem Abmelden.

-

M1 Skelett — Compose-Flow folgt in M2.

+ {:else} -