From fa299e3bf9effa22f4e528b51bdc0a28f425a870 Mon Sep 17 00:00:00 2001 From: Till JS Date: Tue, 28 Apr 2026 22:21:41 +0200 Subject: [PATCH] feat(app-registry): wire up 4 modules + 7 routes + tier-patch validator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves the cross-cutting drift that the app-registry sanity-test was silently catching but BRANDING_ONLY exceptions papered over. App-registry wiring: - Register augur, broadcasts, invoices, timeline as workbench cards. - Resolve agents↔ai-agents naming drift: workbench id is now `agents` (matches MANA_APPS + the /agents route URL); folder stays `ai-agents` for grouping with other ai-* modules. Broadcast→broadcasts unification: - module.config appId, MANA_APPS id, APP_ICONS key, all route appIds, and the redundant APP_URL_OVERRIDES entry — all aligned with the earlier folder rename so nothing diverges anymore. Top-level routes for workbench-only modules: - /goals, /myday, /kontext, /rituals, /automations, /activity — thin RoutePage wrappers around the existing module ListViews. - /timeline becomes a real module (ListView extracted from the route), route shrinks to a 12-line wrapper. Food unarchive: - packages/shared-branding/src/mana-apps.ts: remove `archived: true` from food entry. The module is fully wired (registered, synced, routed, with AI tools); the flag was outdated. i18n cleanup: - Rename ai-agents → agents key in all 5 apps locales. - Drop dead "observatory" key from all 5 nav locales (route folder was removed in 7bca16dfa). New CI guard — scripts/validate-tier-patches.mjs: - Scans for `LOCAL TIER PATCH — revert before release` markers. - Default: informational list (does not fail). - Strict mode (MANA_TIER_PATCH_STRICT=1) for release/RC pipeline. - Wired into validate:all. Spec update: - registry.spec.ts WORKBENCH_ONLY/BRANDING_ONLY: documented Settings family + AI Studio surfaces + intentionally-internal modules so the drift guard fires only on real drift. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../apps/web/src/lib/app-registry/apps.ts | 76 ++- .../web/src/lib/app-registry/categories.ts | 2 +- .../web/src/lib/app-registry/help-content.ts | 2 +- .../web/src/lib/app-registry/registry.spec.ts | 36 +- .../components/dashboard/widget-registry.ts | 2 +- .../apps/web/src/lib/data/crypto/registry.ts | 2 +- .../apps/web/src/lib/data/module-registry.ts | 2 +- apps/mana/apps/web/src/lib/data/tools/init.ts | 2 +- .../web/src/lib/i18n/locales/apps/de.json | 2 +- .../web/src/lib/i18n/locales/apps/en.json | 2 +- .../web/src/lib/i18n/locales/apps/es.json | 2 +- .../web/src/lib/i18n/locales/apps/fr.json | 2 +- .../web/src/lib/i18n/locales/apps/it.json | 2 +- .../apps/web/src/lib/i18n/locales/nav/de.json | 1 - .../apps/web/src/lib/i18n/locales/nav/en.json | 1 - .../apps/web/src/lib/i18n/locales/nav/es.json | 1 - .../apps/web/src/lib/i18n/locales/nav/fr.json | 1 - .../apps/web/src/lib/i18n/locales/nav/it.json | 1 - .../lib/modules/broadcasts/module.config.ts | 2 +- .../web/src/lib/modules/broadcasts/queries.ts | 4 +- .../broadcasts/stores/campaigns.svelte.ts | 8 +- .../broadcasts/stores/settings.svelte.ts | 2 +- .../web/src/lib/modules/broadcasts/tools.ts | 6 +- .../widgets/BroadcastsWidget.svelte | 6 +- .../src/lib/modules/timeline/ListView.svelte | 506 +++++++++++++++++ .../src/routes/(app)/activity/+page.svelte | 12 + .../src/routes/(app)/automations/+page.svelte | 12 + .../src/routes/(app)/broadcasts/+page.svelte | 2 +- .../routes/(app)/broadcasts/[id]/+page.svelte | 4 +- .../(app)/broadcasts/[id]/edit/+page.svelte | 4 +- .../routes/(app)/broadcasts/new/+page.svelte | 2 +- .../(app)/broadcasts/settings/+page.svelte | 2 +- .../web/src/routes/(app)/goals/+page.svelte | 12 + .../web/src/routes/(app)/kontext/+page.svelte | 12 + .../web/src/routes/(app)/myday/+page.svelte | 12 + .../web/src/routes/(app)/rituals/+page.svelte | 12 + .../src/routes/(app)/timeline/+page.svelte | 510 +----------------- package.json | 3 +- packages/shared-branding/src/app-icons.ts | 2 +- packages/shared-branding/src/mana-apps.ts | 8 +- scripts/validate-tier-patches.mjs | 86 +++ 41 files changed, 812 insertions(+), 556 deletions(-) create mode 100644 apps/mana/apps/web/src/lib/modules/timeline/ListView.svelte create mode 100644 apps/mana/apps/web/src/routes/(app)/activity/+page.svelte create mode 100644 apps/mana/apps/web/src/routes/(app)/automations/+page.svelte create mode 100644 apps/mana/apps/web/src/routes/(app)/goals/+page.svelte create mode 100644 apps/mana/apps/web/src/routes/(app)/kontext/+page.svelte create mode 100644 apps/mana/apps/web/src/routes/(app)/myday/+page.svelte create mode 100644 apps/mana/apps/web/src/routes/(app)/rituals/+page.svelte create mode 100644 scripts/validate-tier-patches.mjs diff --git a/apps/mana/apps/web/src/lib/app-registry/apps.ts b/apps/mana/apps/web/src/lib/app-registry/apps.ts index 70ed8118e..8f43ed178 100644 --- a/apps/mana/apps/web/src/lib/app-registry/apps.ts +++ b/apps/mana/apps/web/src/lib/app-registry/apps.ts @@ -84,6 +84,10 @@ import { FilmStrip, Hourglass, HeartHalf, + Eye, + Megaphone, + Receipt, + ClockCounterClockwise, } from '@mana/shared-icons'; // ── Apps with entity capabilities ─────────────────────────── @@ -1113,7 +1117,9 @@ registerApp({ }); registerApp({ - id: 'ai-agents', + // Public id matches MANA_APPS + the route URL `/agents`. The module + // folder is named `ai-agents` for grouping with other ai-* modules. + id: 'agents', name: 'AI Agents', color: '#8B5CF6', icon: Flag, @@ -1492,3 +1498,71 @@ registerApp({ return quiz.id; }, }); + +registerApp({ + id: 'augur', + name: 'Augur', + color: '#7c3aed', + icon: Eye, + views: { + // Witness/Oracle modes live inside the ListView; detail (/augur/entry/[id]) + // and recap (/augur/recap) navigate via goto(). + list: { load: () => import('$lib/modules/augur/ListView.svelte') }, + }, +}); + +registerApp({ + id: 'broadcasts', + name: 'Broadcasts', + color: '#6366f1', + icon: Megaphone, + views: { + // Detail (/broadcasts/[id]), new (/broadcasts/new) and settings live + // as SvelteKit routes; the workbench card hosts the ListView root. + list: { load: () => import('$lib/modules/broadcasts/ListView.svelte') }, + }, + contextMenuActions: [ + { + id: 'new-campaign', + label: 'Neue Kampagne', + icon: Plus, + action: () => { + window.location.href = '/broadcasts/new'; + }, + }, + ], +}); + +registerApp({ + id: 'invoices', + name: 'Rechnungen', + color: '#059669', + icon: Receipt, + views: { + // Detail (/invoices/[id]), new (/invoices/new) and settings live as + // SvelteKit routes; the workbench card hosts the ListView root. + list: { load: () => import('$lib/modules/invoices/ListView.svelte') }, + }, + contextMenuActions: [ + { + id: 'new-invoice', + label: 'Neue Rechnung', + icon: Plus, + action: () => { + window.location.href = '/invoices/new'; + }, + }, + ], +}); + +registerApp({ + id: 'timeline', + name: 'Timeline', + color: '#f59e0b', + icon: ClockCounterClockwise, + views: { + // Cross-module read-only view — reads timeBlocks owned by core. + // /timeline/analytics navigates via goto() from the route page. + list: { load: () => import('$lib/modules/timeline/ListView.svelte') }, + }, +}); diff --git a/apps/mana/apps/web/src/lib/app-registry/categories.ts b/apps/mana/apps/web/src/lib/app-registry/categories.ts index 08f04dbfc..07763f2bb 100644 --- a/apps/mana/apps/web/src/lib/app-registry/categories.ts +++ b/apps/mana/apps/web/src/lib/app-registry/categories.ts @@ -45,7 +45,7 @@ export const APP_CATEGORY_MAP: Record = { // AI Workbench — the 6 new AI feature apps + companion chat companion: 'ai', 'ai-missions': 'ai', - 'ai-agents': 'ai', + agents: 'ai', 'ai-workbench': 'ai', 'ai-policy': 'ai', 'ai-insights': 'ai', diff --git a/apps/mana/apps/web/src/lib/app-registry/help-content.ts b/apps/mana/apps/web/src/lib/app-registry/help-content.ts index fc6e4b0c6..5c876148d 100644 --- a/apps/mana/apps/web/src/lib/app-registry/help-content.ts +++ b/apps/mana/apps/web/src/lib/app-registry/help-content.ts @@ -690,7 +690,7 @@ export const MODULE_HELP: Record = { 'Der Debug-Log hilft zu verstehen warum die AI bestimmte Entscheidungen trifft', ], }, - 'ai-agents': { + agents: { description: 'Benannte AI-Personas mit eigenem System-Prompt, Policy und Gedächtnis. Jeder Agent kann eigene Missionen ausführen.', features: [ diff --git a/apps/mana/apps/web/src/lib/app-registry/registry.spec.ts b/apps/mana/apps/web/src/lib/app-registry/registry.spec.ts index 721991011..d68e62522 100644 --- a/apps/mana/apps/web/src/lib/app-registry/registry.spec.ts +++ b/apps/mana/apps/web/src/lib/app-registry/registry.spec.ts @@ -14,7 +14,36 @@ import { getAllApps } from './registry'; * the kind of mismatch that produced the silent inventar↔inventory bug * before commit 45790ffbb. */ -const WORKBENCH_ONLY = new Set(['automations', 'playground']); +const WORKBENCH_ONLY = new Set([ + // Dev / internal tools — never meant for MANA_APPS. + 'automations', + 'playground', + 'admin', + 'complexity', + 'feedback', + // Settings family — surfaced as workbench cards but conceptually + // part of /settings, not standalone MANA_APPS entries. + 'settings', + 'themes', + 'profile', + 'credits', + 'api-keys', + 'help', + 'spiral', + // User-facing modules that don't yet have MANA_APPS branding. + // Add them to MANA_APPS when they ship a marketing surface. + 'kontext', + 'rituals', + 'wishes', + // AI Studio surfaces. Open question whether to expose each one in + // MANA_APPS or consolidate into a single "AI Studio" branding entry. + // Keeping them workbench-only until that decision lands. + 'ai-missions', + 'ai-workbench', + 'ai-policy', + 'ai-insights', + 'ai-health', +]); /** * Apps that intentionally exist in MANA_APPS but are NOT in the workbench @@ -39,11 +68,6 @@ const BRANDING_ONLY = new Set([ // gallery as "Coming Soon" hints. 'wisekeep', 'mail', - 'events', - // Status 'beta' but the workbench integration is still pending. Move - // out of this list once the apps.ts entry exists. - 'guides', - 'who', ]); describe('app registry ↔ MANA_APPS consistency', () => { diff --git a/apps/mana/apps/web/src/lib/components/dashboard/widget-registry.ts b/apps/mana/apps/web/src/lib/components/dashboard/widget-registry.ts index 535212b38..c5d6f49fe 100644 --- a/apps/mana/apps/web/src/lib/components/dashboard/widget-registry.ts +++ b/apps/mana/apps/web/src/lib/components/dashboard/widget-registry.ts @@ -35,7 +35,7 @@ import NewsUnreadWidget from '$lib/modules/news/widgets/NewsUnreadWidget.svelte' import ArticlesUnreadWidget from '$lib/modules/articles/widgets/ArticlesUnreadWidget.svelte'; import BodyStatsWidget from '$lib/modules/body/widgets/BodyStatsWidget.svelte'; import InvoicesOpenWidget from '$lib/modules/invoices/widgets/InvoicesOpenWidget.svelte'; -import BroadcastsWidget from '$lib/modules/broadcast/widgets/BroadcastsWidget.svelte'; +import BroadcastsWidget from '$lib/modules/broadcasts/widgets/BroadcastsWidget.svelte'; import DayTimelineWidget from './widgets/DayTimelineWidget.svelte'; import ActivityFeedWidget from './widgets/ActivityFeedWidget.svelte'; diff --git a/apps/mana/apps/web/src/lib/data/crypto/registry.ts b/apps/mana/apps/web/src/lib/data/crypto/registry.ts index 188eee51f..747a1d063 100644 --- a/apps/mana/apps/web/src/lib/data/crypto/registry.ts +++ b/apps/mana/apps/web/src/lib/data/crypto/registry.ts @@ -87,7 +87,7 @@ import type { LocalCampaign, LocalBroadcastTemplate, LocalBroadcastSettings, -} from '../../modules/broadcast/types'; +} from '../../modules/broadcasts/types'; import type { LocalArticle, LocalHighlight } from '../../modules/articles/types'; import type { LocalMeImage } from '../../modules/profile/types'; import type { LocalWardrobeGarment, LocalWardrobeOutfit } from '../../modules/wardrobe/types'; diff --git a/apps/mana/apps/web/src/lib/data/module-registry.ts b/apps/mana/apps/web/src/lib/data/module-registry.ts index 652630877..82bfd3521 100644 --- a/apps/mana/apps/web/src/lib/data/module-registry.ts +++ b/apps/mana/apps/web/src/lib/data/module-registry.ts @@ -102,7 +102,7 @@ import { profileModuleConfig } from '$lib/modules/profile/module.config'; import { libraryModuleConfig } from '$lib/modules/library/module.config'; import { articlesModuleConfig } from '$lib/modules/articles/module.config'; import { invoicesModuleConfig } from '$lib/modules/invoices/module.config'; -import { broadcastModuleConfig } from '$lib/modules/broadcast/module.config'; +import { broadcastModuleConfig } from '$lib/modules/broadcasts/module.config'; import { wetterModuleConfig } from '$lib/modules/wetter/module.config'; import { websiteModuleConfig } from '$lib/modules/website/module.config'; import { wardrobeModuleConfig } from '$lib/modules/wardrobe/module.config'; diff --git a/apps/mana/apps/web/src/lib/data/tools/init.ts b/apps/mana/apps/web/src/lib/data/tools/init.ts index 16ae9b20e..e33a12558 100644 --- a/apps/mana/apps/web/src/lib/data/tools/init.ts +++ b/apps/mana/apps/web/src/lib/data/tools/init.ts @@ -45,7 +45,7 @@ import { wetterTools } from '$lib/modules/wetter/tools'; import { quizTools } from '$lib/modules/quiz/tools'; import { invoicesTools } from '$lib/modules/invoices/tools'; import { libraryTools } from '$lib/modules/library/tools'; -import { broadcastTools } from '$lib/modules/broadcast/tools'; +import { broadcastTools } from '$lib/modules/broadcasts/tools'; import { websiteTools } from '$lib/modules/website/tools'; import { writingTools } from '$lib/modules/writing/tools'; import { comicTools } from '$lib/modules/comic/tools'; diff --git a/apps/mana/apps/web/src/lib/i18n/locales/apps/de.json b/apps/mana/apps/web/src/lib/i18n/locales/apps/de.json index 60e97a641..bd39bb602 100644 --- a/apps/mana/apps/web/src/lib/i18n/locales/apps/de.json +++ b/apps/mana/apps/web/src/lib/i18n/locales/apps/de.json @@ -51,7 +51,7 @@ "activity": "Aktivität", "companion": "Begleiter", "ai-missions": "KI-Missionen", - "ai-agents": "KI-Agenten", + "agents": "KI-Agenten", "ai-workbench": "KI-Werkbank", "rituals": "Rituale", "ai-policy": "KI-Richtlinien", diff --git a/apps/mana/apps/web/src/lib/i18n/locales/apps/en.json b/apps/mana/apps/web/src/lib/i18n/locales/apps/en.json index 838b0d1eb..506cc3f62 100644 --- a/apps/mana/apps/web/src/lib/i18n/locales/apps/en.json +++ b/apps/mana/apps/web/src/lib/i18n/locales/apps/en.json @@ -51,7 +51,7 @@ "activity": "Activity", "companion": "Companion", "ai-missions": "AI Missions", - "ai-agents": "AI Agents", + "agents": "AI Agents", "ai-workbench": "AI Workbench", "rituals": "Rituals", "ai-policy": "AI Policy", diff --git a/apps/mana/apps/web/src/lib/i18n/locales/apps/es.json b/apps/mana/apps/web/src/lib/i18n/locales/apps/es.json index c7c8d3001..f9907a91e 100644 --- a/apps/mana/apps/web/src/lib/i18n/locales/apps/es.json +++ b/apps/mana/apps/web/src/lib/i18n/locales/apps/es.json @@ -51,7 +51,7 @@ "activity": "Actividad", "companion": "Compañero", "ai-missions": "Misiones IA", - "ai-agents": "Agentes IA", + "agents": "Agentes IA", "ai-workbench": "Workbench IA", "rituals": "Rituales", "ai-policy": "Políticas IA", diff --git a/apps/mana/apps/web/src/lib/i18n/locales/apps/fr.json b/apps/mana/apps/web/src/lib/i18n/locales/apps/fr.json index 1b2e67e66..d469badc2 100644 --- a/apps/mana/apps/web/src/lib/i18n/locales/apps/fr.json +++ b/apps/mana/apps/web/src/lib/i18n/locales/apps/fr.json @@ -51,7 +51,7 @@ "activity": "Activité", "companion": "Compagnon", "ai-missions": "Missions IA", - "ai-agents": "Agents IA", + "agents": "Agents IA", "ai-workbench": "Workbench IA", "rituals": "Rituels", "ai-policy": "Règles IA", diff --git a/apps/mana/apps/web/src/lib/i18n/locales/apps/it.json b/apps/mana/apps/web/src/lib/i18n/locales/apps/it.json index 110bf5689..a8aeb9277 100644 --- a/apps/mana/apps/web/src/lib/i18n/locales/apps/it.json +++ b/apps/mana/apps/web/src/lib/i18n/locales/apps/it.json @@ -51,7 +51,7 @@ "activity": "Attività", "companion": "Compagno", "ai-missions": "Missioni IA", - "ai-agents": "Agenti IA", + "agents": "Agenti IA", "ai-workbench": "Workbench IA", "rituals": "Rituali", "ai-policy": "Regole IA", diff --git a/apps/mana/apps/web/src/lib/i18n/locales/nav/de.json b/apps/mana/apps/web/src/lib/i18n/locales/nav/de.json index df738e2dc..b9b15ebd6 100644 --- a/apps/mana/apps/web/src/lib/i18n/locales/nav/de.json +++ b/apps/mana/apps/web/src/lib/i18n/locales/nav/de.json @@ -2,7 +2,6 @@ "home": "Home", "dashboard": "Dashboard", "spiral": "Spiral", - "observatory": "Observatory", "credits": "Credits", "gifts": "Geschenke", "api_keys": "API Keys", diff --git a/apps/mana/apps/web/src/lib/i18n/locales/nav/en.json b/apps/mana/apps/web/src/lib/i18n/locales/nav/en.json index 0f03b6ba1..35b45360e 100644 --- a/apps/mana/apps/web/src/lib/i18n/locales/nav/en.json +++ b/apps/mana/apps/web/src/lib/i18n/locales/nav/en.json @@ -2,7 +2,6 @@ "home": "Home", "dashboard": "Dashboard", "spiral": "Spiral", - "observatory": "Observatory", "credits": "Credits", "gifts": "Gifts", "api_keys": "API Keys", diff --git a/apps/mana/apps/web/src/lib/i18n/locales/nav/es.json b/apps/mana/apps/web/src/lib/i18n/locales/nav/es.json index 573ac0bfe..a02c78756 100644 --- a/apps/mana/apps/web/src/lib/i18n/locales/nav/es.json +++ b/apps/mana/apps/web/src/lib/i18n/locales/nav/es.json @@ -2,7 +2,6 @@ "home": "Inicio", "dashboard": "Panel", "spiral": "Spiral", - "observatory": "Observatorio", "credits": "Créditos", "gifts": "Regalos", "api_keys": "API Keys", diff --git a/apps/mana/apps/web/src/lib/i18n/locales/nav/fr.json b/apps/mana/apps/web/src/lib/i18n/locales/nav/fr.json index 5b902dd21..872656e2a 100644 --- a/apps/mana/apps/web/src/lib/i18n/locales/nav/fr.json +++ b/apps/mana/apps/web/src/lib/i18n/locales/nav/fr.json @@ -2,7 +2,6 @@ "home": "Accueil", "dashboard": "Tableau de bord", "spiral": "Spiral", - "observatory": "Observatoire", "credits": "Crédits", "gifts": "Cadeaux", "api_keys": "Clés API", diff --git a/apps/mana/apps/web/src/lib/i18n/locales/nav/it.json b/apps/mana/apps/web/src/lib/i18n/locales/nav/it.json index 9f33a8f1c..c00b13258 100644 --- a/apps/mana/apps/web/src/lib/i18n/locales/nav/it.json +++ b/apps/mana/apps/web/src/lib/i18n/locales/nav/it.json @@ -2,7 +2,6 @@ "home": "Home", "dashboard": "Dashboard", "spiral": "Spiral", - "observatory": "Osservatorio", "credits": "Crediti", "gifts": "Regali", "api_keys": "Chiavi API", diff --git a/apps/mana/apps/web/src/lib/modules/broadcasts/module.config.ts b/apps/mana/apps/web/src/lib/modules/broadcasts/module.config.ts index 7ab01bedc..1f65e43cf 100644 --- a/apps/mana/apps/web/src/lib/modules/broadcasts/module.config.ts +++ b/apps/mana/apps/web/src/lib/modules/broadcasts/module.config.ts @@ -1,7 +1,7 @@ import type { ModuleConfig } from '$lib/data/module-registry'; export const broadcastModuleConfig: ModuleConfig = { - appId: 'broadcast', + appId: 'broadcasts', tables: [ { name: 'broadcastCampaigns' }, { name: 'broadcastTemplates' }, diff --git a/apps/mana/apps/web/src/lib/modules/broadcasts/queries.ts b/apps/mana/apps/web/src/lib/modules/broadcasts/queries.ts index b1de77330..6dbd5fb58 100644 --- a/apps/mana/apps/web/src/lib/modules/broadcasts/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/broadcasts/queries.ts @@ -80,7 +80,7 @@ export function toSettings(local: LocalBroadcastSettings): BroadcastSettings { export function useAllCampaigns() { return useScopedLiveQuery(async () => { const rows = await scopedForModule( - 'broadcast', + 'broadcasts', 'broadcastCampaigns' ).toArray(); const visible = rows.filter((r) => !r.deletedAt); @@ -92,7 +92,7 @@ export function useAllCampaigns() { export function useAllTemplates() { return useScopedLiveQuery(async () => { const rows = await scopedForModule( - 'broadcast', + 'broadcasts', 'broadcastTemplates' ).toArray(); const visible = rows.filter((r) => !r.deletedAt); diff --git a/apps/mana/apps/web/src/lib/modules/broadcasts/stores/campaigns.svelte.ts b/apps/mana/apps/web/src/lib/modules/broadcasts/stores/campaigns.svelte.ts index c7cc6b7eb..a69d1d92c 100644 --- a/apps/mana/apps/web/src/lib/modules/broadcasts/stores/campaigns.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/broadcasts/stores/campaigns.svelte.ts @@ -70,7 +70,7 @@ export const broadcastCampaignsStore = { await encryptRecord('broadcastCampaigns', newLocal); await campaignTable.add(newLocal); - emitDomainEvent('BroadcastCampaignCreated', 'broadcast', 'broadcastCampaigns', newLocal.id, { + emitDomainEvent('BroadcastCampaignCreated', 'broadcasts', 'broadcastCampaigns', newLocal.id, { campaignId: newLocal.id, name: newLocal.name, }); @@ -142,7 +142,7 @@ export const broadcastCampaignsStore = { status: 'scheduled' as CampaignStatus, scheduledAt, }); - emitDomainEvent('BroadcastCampaignScheduled', 'broadcast', 'broadcastCampaigns', id, { + emitDomainEvent('BroadcastCampaignScheduled', 'broadcasts', 'broadcastCampaigns', id, { campaignId: id, scheduledAt, }); @@ -159,7 +159,7 @@ export const broadcastCampaignsStore = { status: 'cancelled' as CampaignStatus, scheduledAt: null, }); - emitDomainEvent('BroadcastCampaignCancelled', 'broadcast', 'broadcastCampaigns', id, { + emitDomainEvent('BroadcastCampaignCancelled', 'broadcasts', 'broadcastCampaigns', id, { campaignId: id, }); }, @@ -198,7 +198,7 @@ export const broadcastCampaignsStore = { await campaignTable.update(id, { deletedAt: new Date().toISOString(), }); - emitDomainEvent('BroadcastCampaignDeleted', 'broadcast', 'broadcastCampaigns', id, { + emitDomainEvent('BroadcastCampaignDeleted', 'broadcasts', 'broadcastCampaigns', id, { campaignId: id, }); }, diff --git a/apps/mana/apps/web/src/lib/modules/broadcasts/stores/settings.svelte.ts b/apps/mana/apps/web/src/lib/modules/broadcasts/stores/settings.svelte.ts index b9b28b9ca..8821d83f6 100644 --- a/apps/mana/apps/web/src/lib/modules/broadcasts/stores/settings.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/broadcasts/stores/settings.svelte.ts @@ -53,7 +53,7 @@ export const broadcastSettingsStore = { }); emitDomainEvent( 'BroadcastSettingsUpdated', - 'broadcast', + 'broadcasts', 'broadcastSettings', BROADCAST_SETTINGS_ID, { fields: Object.keys(patch) } diff --git a/apps/mana/apps/web/src/lib/modules/broadcasts/tools.ts b/apps/mana/apps/web/src/lib/modules/broadcasts/tools.ts index c15ecb013..5e8730bf4 100644 --- a/apps/mana/apps/web/src/lib/modules/broadcasts/tools.ts +++ b/apps/mana/apps/web/src/lib/modules/broadcasts/tools.ts @@ -60,7 +60,7 @@ async function listDecryptedCampaigns(): Promise[] export const broadcastTools: ModuleTool[] = [ { name: 'create_campaign_draft', - module: 'broadcast', + module: 'broadcasts', description: 'Erstellt einen Newsletter-/Kampagnen-Entwurf mit Name, Betreff, optionalem Preheader und fertigem HTML-Body.', parameters: [ @@ -97,7 +97,7 @@ export const broadcastTools: ModuleTool[] = [ { name: 'list_campaigns', - module: 'broadcast', + module: 'broadcasts', description: 'Listet Kampagnen (id, name, subject, status, Empfängerzahl, sentAt).', parameters: [ { @@ -132,7 +132,7 @@ export const broadcastTools: ModuleTool[] = [ { name: 'get_campaign_stats', - module: 'broadcast', + module: 'broadcasts', description: 'Gibt Raten zu einer Kampagne zurück: Öffnungs-, Klick-, Bounce- und Abmelderate.', parameters: [{ name: 'campaignId', type: 'string', description: 'ID', required: true }], async execute(params) { diff --git a/apps/mana/apps/web/src/lib/modules/broadcasts/widgets/BroadcastsWidget.svelte b/apps/mana/apps/web/src/lib/modules/broadcasts/widgets/BroadcastsWidget.svelte index 762d845c8..f674b3923 100644 --- a/apps/mana/apps/web/src/lib/modules/broadcasts/widgets/BroadcastsWidget.svelte +++ b/apps/mana/apps/web/src/lib/modules/broadcasts/widgets/BroadcastsWidget.svelte @@ -8,10 +8,10 @@ */ import { liveQuery } from 'dexie'; - import { campaignTable } from '$lib/modules/broadcast/collections'; + import { campaignTable } from '$lib/modules/broadcasts/collections'; import { decryptRecords } from '$lib/data/crypto'; - import { toCampaign, computeStats, formatRate } from '$lib/modules/broadcast/queries'; - import type { Campaign, LocalCampaign } from '$lib/modules/broadcast/types'; + import { toCampaign, computeStats, formatRate } from '$lib/modules/broadcasts/queries'; + import type { Campaign, LocalCampaign } from '$lib/modules/broadcasts/types'; let campaigns = $state([]); let loading = $state(true); diff --git a/apps/mana/apps/web/src/lib/modules/timeline/ListView.svelte b/apps/mana/apps/web/src/lib/modules/timeline/ListView.svelte new file mode 100644 index 000000000..dbd8533c4 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/timeline/ListView.svelte @@ -0,0 +1,506 @@ + + + +
+
+
+

{formatHeaderDate(currentDate)}

+ +
+ +
+ {#if totalSeconds > 0} + {formatDuration(totalSeconds)} erfasst + {/if} + +
+
+ + {#if showFilters} +
+ {#each typeConfig as cfg} + {@const active = visibleTypes.has(cfg.type)} + {@const Icon = cfg.icon} + + {/each} +
+ {/if} + + +
+ {#if blocks.length === 0} +
+ +

{isToday(currentDate) ? 'Noch nichts heute' : 'Keine Einträge an diesem Tag'}

+
+ {:else} +
+ {#each blocks as block, i (block.id)} + {@const duration = getBlockDuration(block)} + {@const habitIcon = + block.type === 'habit' && block.icon ? getIconComponent(block.icon) : null} + {@const typeCfg = typeConfig.find((c) => c.type === block.type)} + +
+ +
+ {format(new Date(block.startDate), 'HH:mm')} +
+ + +
+
+ {#if i < blocks.length - 1} +
+ {/if} +
+ + +
+
+ {#if habitIcon} + {@const HabitIcon = habitIcon} + + {:else if typeCfg} + {@const TypeIcon = typeCfg.icon} + + {/if} + {block.title} + {#if block.linkedBlockId} + erledigt + {/if} + {#if block.isLive} + live + {/if} +
+ +
+ {formatBlockTime(block)} + {#if duration > 0} + {formatDuration(duration)} + {/if} +
+ + {#if block.description} +

{block.description}

+ {/if} +
+
+ {/each} +
+ {/if} +
+
+ + diff --git a/apps/mana/apps/web/src/routes/(app)/activity/+page.svelte b/apps/mana/apps/web/src/routes/(app)/activity/+page.svelte new file mode 100644 index 000000000..452a1b6c6 --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/activity/+page.svelte @@ -0,0 +1,12 @@ + + + + Aktivität - Mana + + + + + diff --git a/apps/mana/apps/web/src/routes/(app)/automations/+page.svelte b/apps/mana/apps/web/src/routes/(app)/automations/+page.svelte new file mode 100644 index 000000000..a7e2635de --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/automations/+page.svelte @@ -0,0 +1,12 @@ + + + + Automations - Mana + + + + {}} goBack={() => history.back()} params={{}} /> + diff --git a/apps/mana/apps/web/src/routes/(app)/broadcasts/+page.svelte b/apps/mana/apps/web/src/routes/(app)/broadcasts/+page.svelte index 61f318942..da226e5f0 100644 --- a/apps/mana/apps/web/src/routes/(app)/broadcasts/+page.svelte +++ b/apps/mana/apps/web/src/routes/(app)/broadcasts/+page.svelte @@ -1,5 +1,5 @@ diff --git a/apps/mana/apps/web/src/routes/(app)/broadcasts/[id]/+page.svelte b/apps/mana/apps/web/src/routes/(app)/broadcasts/[id]/+page.svelte index a48ddc033..55c1555d5 100644 --- a/apps/mana/apps/web/src/routes/(app)/broadcasts/[id]/+page.svelte +++ b/apps/mana/apps/web/src/routes/(app)/broadcasts/[id]/+page.svelte @@ -1,8 +1,8 @@ diff --git a/apps/mana/apps/web/src/routes/(app)/goals/+page.svelte b/apps/mana/apps/web/src/routes/(app)/goals/+page.svelte new file mode 100644 index 000000000..bf8b86d40 --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/goals/+page.svelte @@ -0,0 +1,12 @@ + + + + Ziele - Mana + + + + + diff --git a/apps/mana/apps/web/src/routes/(app)/kontext/+page.svelte b/apps/mana/apps/web/src/routes/(app)/kontext/+page.svelte new file mode 100644 index 000000000..692bbc133 --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/kontext/+page.svelte @@ -0,0 +1,12 @@ + + + + Web-Context - Mana + + + + + diff --git a/apps/mana/apps/web/src/routes/(app)/myday/+page.svelte b/apps/mana/apps/web/src/routes/(app)/myday/+page.svelte new file mode 100644 index 000000000..16761edae --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/myday/+page.svelte @@ -0,0 +1,12 @@ + + + + Mein Tag - Mana + + + + + diff --git a/apps/mana/apps/web/src/routes/(app)/rituals/+page.svelte b/apps/mana/apps/web/src/routes/(app)/rituals/+page.svelte new file mode 100644 index 000000000..393e05ae2 --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/rituals/+page.svelte @@ -0,0 +1,12 @@ + + + + Rituale - Mana + + + + + diff --git a/apps/mana/apps/web/src/routes/(app)/timeline/+page.svelte b/apps/mana/apps/web/src/routes/(app)/timeline/+page.svelte index 671d5270e..489376a2e 100644 --- a/apps/mana/apps/web/src/routes/(app)/timeline/+page.svelte +++ b/apps/mana/apps/web/src/routes/(app)/timeline/+page.svelte @@ -1,510 +1,12 @@ - + + Timeline - Mana + + -
- -
-
-

{formatHeaderDate(currentDate)}

- -
- -
- {#if totalSeconds > 0} - {formatDuration(totalSeconds)} erfasst - {/if} - -
-
- - {#if showFilters} -
- {#each typeConfig as cfg} - {@const active = visibleTypes.has(cfg.type)} - {@const Icon = cfg.icon} - - {/each} -
- {/if} - - -
- {#if blocks.length === 0} -
- -

{isToday(currentDate) ? 'Noch nichts heute' : 'Keine Einträge an diesem Tag'}

-
- {:else} -
- {#each blocks as block, i (block.id)} - {@const duration = getBlockDuration(block)} - {@const habitIcon = - block.type === 'habit' && block.icon ? getIconComponent(block.icon) : null} - {@const typeCfg = typeConfig.find((c) => c.type === block.type)} - -
- -
- {format(new Date(block.startDate), 'HH:mm')} -
- - -
-
- {#if i < blocks.length - 1} -
- {/if} -
- - -
-
- {#if habitIcon} - {@const HabitIcon = habitIcon} - - {:else if typeCfg} - {@const TypeIcon = typeCfg.icon} - - {/if} - {block.title} - {#if block.linkedBlockId} - erledigt - {/if} - {#if block.isLive} - live - {/if} -
- -
- {formatBlockTime(block)} - {#if duration > 0} - {formatDuration(duration)} - {/if} -
- - {#if block.description} -

{block.description}

- {/if} -
-
- {/each} -
- {/if} -
-
+
- - diff --git a/package.json b/package.json index 6f762c8c6..6b0ab229f 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "validate:i18n-hardcoded": "node scripts/validate-no-hardcoded-strings.mjs", "validate:i18n-keys": "node scripts/validate-i18n-keys.mjs", "validate:llm-strings": "node scripts/validate-llm-strings.mjs", - "validate:all": "pnpm run validate:turbo && pnpm run validate:pg-schema && pnpm run validate:theme-variables && pnpm run validate:theme-utilities && pnpm run validate:theme-parity && pnpm run validate:i18n-parity && pnpm run validate:i18n-hardcoded && pnpm run validate:i18n-keys && pnpm run validate:llm-strings && pnpm run check:crypto && pnpm run audit:encrypted-tools", + "validate:tier-patches": "node scripts/validate-tier-patches.mjs", + "validate:all": "pnpm run validate:turbo && pnpm run validate:pg-schema && pnpm run validate:theme-variables && pnpm run validate:theme-utilities && pnpm run validate:theme-parity && pnpm run validate:i18n-parity && pnpm run validate:i18n-hardcoded && pnpm run validate:i18n-keys && pnpm run validate:llm-strings && pnpm run validate:tier-patches && pnpm run check:crypto && pnpm run audit:encrypted-tools", "check:crypto": "node scripts/audit-crypto-registry.mjs", "check:crypto:seed": "node scripts/audit-crypto-registry.mjs --seed", "audit:encrypted-tools": "bun run scripts/audit-encrypted-tools.ts", diff --git a/packages/shared-branding/src/app-icons.ts b/packages/shared-branding/src/app-icons.ts index 0d34460d8..58a25d46c 100644 --- a/packages/shared-branding/src/app-icons.ts +++ b/packages/shared-branding/src/app-icons.ts @@ -276,7 +276,7 @@ export const APP_ICONS = { // Emerald→teal sits next to finance green in the Arbeit & Finanzen row. `` ), - broadcast: svgToDataUrl( + broadcasts: svgToDataUrl( // Megaphone / loudspeaker with three radiating sound arcs. // Indigo→cyan gradient sets it apart from mail (blue) and invoices // (emerald) while staying in the "communication" colour family. diff --git a/packages/shared-branding/src/mana-apps.ts b/packages/shared-branding/src/mana-apps.ts index 8236aa947..f8aae0d85 100644 --- a/packages/shared-branding/src/mana-apps.ts +++ b/packages/shared-branding/src/mana-apps.ts @@ -255,7 +255,6 @@ export const MANA_APPS: ManaApp[] = [ comingSoon: false, status: 'development', requiredTier: 'guest', - archived: true, }, { id: 'contacts', @@ -1106,7 +1105,7 @@ export const MANA_APPS: ManaApp[] = [ requiredTier: 'guest', // LOCAL TIER PATCH — revert to 'beta' before release }, { - id: 'broadcast', + id: 'broadcasts', name: 'Broadcasts', description: { de: 'Newsletter & Kampagnen', @@ -1116,7 +1115,7 @@ export const MANA_APPS: ManaApp[] = [ de: 'Newsletter und Ankündigungen an Kontaktgruppen versenden — mit Rich-Text-Editor, Open/Click-Tracking, DSGVO-konformem Unsubscribe und Kampagnen-Statistik.', en: 'Send newsletters and announcements to contact segments — with a rich-text editor, open/click tracking, GDPR-compliant unsubscribe, and per-campaign stats.', }, - icon: APP_ICONS.broadcast, + icon: APP_ICONS.broadcasts, color: '#6366f1', comingSoon: false, status: 'development', @@ -1315,9 +1314,6 @@ const APP_URL_OVERRIDES: Partial = Object.fromEntries( diff --git a/scripts/validate-tier-patches.mjs b/scripts/validate-tier-patches.mjs new file mode 100644 index 000000000..c74d644a1 --- /dev/null +++ b/scripts/validate-tier-patches.mjs @@ -0,0 +1,86 @@ +#!/usr/bin/env node +/** + * Validate that no `LOCAL TIER PATCH` markers leak into the release. + * + * Background: tier downgrades during local dev are marked with a comment + * `// LOCAL TIER PATCH — revert to '' before release` so devs can + * temporarily expose alpha/beta modules to themselves on the guest tier. + * If those markers ship to prod every guest sees premium modules. + * + * This script is informational by default — it lists every marker so the + * release engineer sees them. Set MANA_TIER_PATCH_STRICT=1 to fail the + * build instead (use this in the release / RC pipeline, NOT in local + * validate:all where the markers are intentional). + * + * Zero deps — runs as plain Node ESM. Uses `git ls-files` so it + * automatically respects .gitignore. + */ + +import { execSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = join(__dirname, '..'); + +const STRICT = process.env.MANA_TIER_PATCH_STRICT === '1'; +const MARKER = 'LOCAL TIER PATCH'; + +function listTrackedFiles() { + const out = execSync('git ls-files "*.ts" "*.tsx" "*.js" "*.mjs" "*.svelte" "*.json" "*.md"', { + cwd: REPO_ROOT, + encoding: 'utf8', + maxBuffer: 32 * 1024 * 1024, + }); + return out + .split('\n') + .map((p) => p.trim()) + .filter(Boolean); +} + +function scan() { + const hits = []; + for (const rel of listTrackedFiles()) { + const abs = join(REPO_ROOT, rel); + let body; + try { + body = readFileSync(abs, 'utf8'); + } catch { + continue; + } + if (!body.includes(MARKER)) continue; + const lines = body.split('\n'); + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes(MARKER)) { + hits.push({ file: rel, line: i + 1, text: lines[i].trim() }); + } + } + } + return hits; +} + +const hits = scan(); + +if (hits.length === 0) { + console.log('✓ No LOCAL TIER PATCH markers found.'); + process.exit(0); +} + +console.log(`Found ${hits.length} LOCAL TIER PATCH marker(s):\n`); +for (const h of hits) { + console.log(` ${h.file}:${h.line}`); + console.log(` ${h.text}`); +} + +if (STRICT) { + console.error( + '\n✗ MANA_TIER_PATCH_STRICT=1 — refusing to ship while LOCAL TIER PATCH markers are present.\n' + + ' Revert each marker to the documented production tier before tagging the release.' + ); + process.exit(1); +} + +console.log( + '\n (informational — not failing. Set MANA_TIER_PATCH_STRICT=1 in release CI to gate.)' +);