diff --git a/apps/manacore/apps/web/src/lib/app-registry/apps.ts b/apps/manacore/apps/web/src/lib/app-registry/apps.ts
index dfcbb6ab5..a697d7ecf 100644
--- a/apps/manacore/apps/web/src/lib/app-registry/apps.ts
+++ b/apps/manacore/apps/web/src/lib/app-registry/apps.ts
@@ -456,6 +456,15 @@ registerApp({
},
});
+registerApp({
+ id: 'automations',
+ name: 'Automations',
+ color: '#8B5CF6',
+ views: {
+ list: { load: () => import('$lib/modules/automations/ListView.svelte') },
+ },
+});
+
registerApp({
id: 'playground',
name: 'Playground',
diff --git a/apps/manacore/apps/web/src/lib/modules/automations/ListView.svelte b/apps/manacore/apps/web/src/lib/modules/automations/ListView.svelte
new file mode 100644
index 000000000..1445abca1
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/automations/ListView.svelte
@@ -0,0 +1,549 @@
+
+
+
+
+
+
+
+
+ {#if showCreate}
+
+ {/if}
+
+
+
+ {#each automations as auto (auto.id)}
+
+
+
+ {auto.name}
+
+ {sourceLabel(auto)}
+ →
+ {conditionLabel(auto)}
+ →
+ {actionLabel(auto)}
+
+
+
+
+ {/each}
+
+
+ {#if automations.length === 0 && !showCreate}
+
+
Keine Automations angelegt.
+
Erstelle Regeln die Module miteinander verbinden.
+
+
+ {/if}
+
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/automations/collections.ts b/apps/manacore/apps/web/src/lib/modules/automations/collections.ts
new file mode 100644
index 000000000..6075c91c8
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/automations/collections.ts
@@ -0,0 +1,8 @@
+/**
+ * Automations module — collection accessors.
+ */
+
+import { db } from '$lib/data/database';
+import type { LocalAutomation } from './types';
+
+export const automationTable = db.table('automations');
diff --git a/apps/manacore/apps/web/src/lib/modules/automations/index.ts b/apps/manacore/apps/web/src/lib/modules/automations/index.ts
new file mode 100644
index 000000000..b5d048127
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/automations/index.ts
@@ -0,0 +1,4 @@
+export { automationsStore } from './stores/automations.svelte';
+export { automationTable } from './collections';
+export type { LocalAutomation, SourceOption, ActionOption } from './types';
+export { SOURCE_OPTIONS, ACTION_OPTIONS, CONDITION_OPS } from './types';
diff --git a/apps/manacore/apps/web/src/lib/modules/automations/stores/automations.svelte.ts b/apps/manacore/apps/web/src/lib/modules/automations/stores/automations.svelte.ts
new file mode 100644
index 000000000..1ff3fa9ee
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/automations/stores/automations.svelte.ts
@@ -0,0 +1,72 @@
+/**
+ * Automations Store — Mutation-Only
+ *
+ * CRUD for automation rules. After each mutation, reloads triggers.
+ */
+
+import { automationTable } from '../collections';
+import { loadAutomations } from '$lib/triggers';
+import type { LocalAutomation } from '../types';
+import type { ConditionOp } from '$lib/triggers/conditions';
+
+export const automationsStore = {
+ async create(data: {
+ name: string;
+ sourceApp: string;
+ sourceCollection: string;
+ sourceOp: 'insert' | 'update';
+ conditionField?: string;
+ conditionOp?: ConditionOp;
+ conditionValue?: string;
+ targetApp: string;
+ targetAction: string;
+ targetParams?: Record;
+ }) {
+ const now = new Date().toISOString();
+ const auto: LocalAutomation = {
+ id: crypto.randomUUID(),
+ name: data.name,
+ enabled: true,
+ sourceApp: data.sourceApp,
+ sourceCollection: data.sourceCollection,
+ sourceOp: data.sourceOp,
+ conditionField: data.conditionField,
+ conditionOp: data.conditionOp,
+ conditionValue: data.conditionValue,
+ targetApp: data.targetApp,
+ targetAction: data.targetAction,
+ targetParams: data.targetParams,
+ createdAt: now,
+ updatedAt: now,
+ };
+ await automationTable.add(auto);
+ await loadAutomations();
+ return auto;
+ },
+
+ async update(id: string, data: Partial) {
+ await automationTable.update(id, {
+ ...data,
+ updatedAt: new Date().toISOString(),
+ });
+ await loadAutomations();
+ },
+
+ async toggle(id: string) {
+ const auto = await automationTable.get(id);
+ if (!auto) return;
+ await automationTable.update(id, {
+ enabled: !auto.enabled,
+ updatedAt: new Date().toISOString(),
+ });
+ await loadAutomations();
+ },
+
+ async remove(id: string) {
+ await automationTable.update(id, {
+ deletedAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ });
+ await loadAutomations();
+ },
+};
diff --git a/apps/manacore/apps/web/src/lib/modules/automations/types.ts b/apps/manacore/apps/web/src/lib/modules/automations/types.ts
new file mode 100644
index 000000000..e9e8a65f1
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/automations/types.ts
@@ -0,0 +1,121 @@
+/**
+ * Automations module types.
+ */
+
+import type { BaseRecord } from '@manacore/local-store';
+import type { ConditionOp } from '$lib/triggers/conditions';
+
+export interface LocalAutomation extends BaseRecord {
+ name: string;
+ enabled: boolean;
+ sourceApp: string;
+ sourceCollection: string;
+ sourceOp: 'insert' | 'update';
+ conditionField?: string;
+ conditionOp?: ConditionOp;
+ conditionValue?: string;
+ targetApp: string;
+ targetAction: string;
+ targetParams?: Record;
+}
+
+// ─── UI Metadata ─────────────────────────────────────────
+
+export interface SourceOption {
+ app: string;
+ appLabel: string;
+ collection: string;
+ collectionLabel: string;
+ fields: string[];
+}
+
+export interface ActionOption {
+ app: string;
+ appLabel: string;
+ action: string;
+ actionLabel: string;
+ params: {
+ key: string;
+ label: string;
+ type: 'text' | 'select';
+ options?: { value: string; label: string }[];
+ }[];
+}
+
+/** Available source apps/collections for trigger conditions. */
+export const SOURCE_OPTIONS: SourceOption[] = [
+ {
+ app: 'calendar',
+ appLabel: 'Kalender',
+ collection: 'events',
+ collectionLabel: 'Events',
+ fields: ['title', 'description', 'location'],
+ },
+ {
+ app: 'todo',
+ appLabel: 'Todo',
+ collection: 'tasks',
+ collectionLabel: 'Aufgaben',
+ fields: ['title', 'description'],
+ },
+ {
+ app: 'contacts',
+ appLabel: 'Kontakte',
+ collection: 'contacts',
+ collectionLabel: 'Kontakte',
+ fields: ['firstName', 'lastName', 'company'],
+ },
+ {
+ app: 'notes',
+ appLabel: 'Notes',
+ collection: 'notes',
+ collectionLabel: 'Notizen',
+ fields: ['title', 'content'],
+ },
+ {
+ app: 'habits',
+ appLabel: 'Habits',
+ collection: 'habitLogs',
+ collectionLabel: 'Habit-Logs',
+ fields: ['habitId'],
+ },
+ {
+ app: 'places',
+ appLabel: 'Places',
+ collection: 'places',
+ collectionLabel: 'Orte',
+ fields: ['name', 'address', 'category'],
+ },
+];
+
+/** Available target actions. Params with type='select' get populated dynamically. */
+export const ACTION_OPTIONS: ActionOption[] = [
+ {
+ app: 'habits',
+ appLabel: 'Habits',
+ action: 'logHabit',
+ actionLabel: 'Habit loggen',
+ params: [{ key: 'habitId', label: 'Habit', type: 'select' }],
+ },
+ {
+ app: 'todo',
+ appLabel: 'Todo',
+ action: 'createTask',
+ actionLabel: 'Aufgabe erstellen',
+ params: [{ key: 'title', label: 'Titel', type: 'text' }],
+ },
+ {
+ app: 'notes',
+ appLabel: 'Notes',
+ action: 'createNote',
+ actionLabel: 'Notiz erstellen',
+ params: [{ key: 'title', label: 'Titel', type: 'text' }],
+ },
+];
+
+export const CONDITION_OPS: { value: ConditionOp; label: string }[] = [
+ { value: 'contains', label: 'enthält' },
+ { value: 'equals', label: 'ist gleich' },
+ { value: 'startsWith', label: 'beginnt mit' },
+ { value: 'matches', label: 'Regex' },
+];
diff --git a/apps/manacore/apps/web/src/lib/splitscreen/registry.ts b/apps/manacore/apps/web/src/lib/splitscreen/registry.ts
index db56ea4b7..32270b192 100644
--- a/apps/manacore/apps/web/src/lib/splitscreen/registry.ts
+++ b/apps/manacore/apps/web/src/lib/splitscreen/registry.ts
@@ -31,6 +31,7 @@ const SPLIT_APP_ID_LIST = [
'moodlit',
'memoro',
'places',
+ 'automations',
'playground',
] as const;