diff --git a/apps/manacore/apps/web/src/lib/components/SuggestionToast.svelte b/apps/manacore/apps/web/src/lib/components/SuggestionToast.svelte
new file mode 100644
index 000000000..3e76fa2bf
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/components/SuggestionToast.svelte
@@ -0,0 +1,149 @@
+
+
+
+{#if visible && suggestion}
+
+
+ ⚡
+ {suggestion.description}
+
+
+
+
+
+
+{/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/data/database.ts b/apps/manacore/apps/web/src/lib/data/database.ts
index f61ff2f7f..a7e5a0b5d 100644
--- a/apps/manacore/apps/web/src/lib/data/database.ts
+++ b/apps/manacore/apps/web/src/lib/data/database.ts
@@ -10,6 +10,7 @@
import Dexie, { type EntityTable } from 'dexie';
import { trackFirstContent } from '$lib/stores/funnel-tracking';
import { fire as fireTrigger } from '$lib/triggers/registry';
+import { checkInlineSuggestion } from '$lib/triggers/inline-suggest';
// ─── Database ──────────────────────────────────────────────
@@ -203,6 +204,44 @@ db.version(1).stores({
manaLinks: 'id, sourceAppId, sourceRecordId, targetAppId, targetRecordId',
});
+// ─── Schema Migrations ────────────────────────────────────────
+// Version 2: Habits emoji → icon field migration
+
+const EMOJI_TO_ICON: Record = {
+ '\u2615': 'coffee',
+ '\ud83d\udeb6': 'person-simple-walk',
+ '\ud83c\udfc3': 'person-simple-run',
+ '\ud83e\uddd8': 'person-simple-tai-chi',
+ '\ud83d\udca7': 'drop',
+ '\ud83c\udf4e': 'apple-logo',
+ '\ud83d\udcda': 'book-open',
+ '\ud83d\udcaa': 'barbell',
+ '\ud83d\udecc': 'bed',
+ '\ud83c\udfb5': 'music-note',
+ '\ud83d\udc8a': 'pill',
+ '\ud83c\udf7a': 'beer-stein',
+ '\ud83c\udf55': 'pizza',
+ '\ud83d\udeb4': 'bicycle',
+ '\ud83d\udcdd': 'pencil-simple',
+ '\ud83e\uddfc': 'tooth',
+ '\u2b50': 'star',
+ '\ud83d\ude2e\u200d\ud83d\udca8': 'wind',
+};
+
+db.version(2)
+ .stores({})
+ .upgrade((tx) => {
+ return tx
+ .table('habits')
+ .toCollection()
+ .modify((habit: Record) => {
+ if (habit.emoji !== undefined && habit.icon === undefined) {
+ habit.icon = EMOJI_TO_ICON[habit.emoji as string] ?? 'star';
+ delete habit.emoji;
+ }
+ });
+ });
+
// ─── Sync App Map ──────────────────────────────────────────
// Maps each table to its appId for sync routing.
// The SyncEngine uses this to group pending changes and push to /sync/{appId}.
@@ -373,6 +412,9 @@ for (const [appId, tables] of Object.entries(SYNC_APP_MAP)) {
});
trackFirstContent(appId);
fireTrigger(appId, tableName, 'insert', { ...obj });
+ checkInlineSuggestion(appId, tableName, { ...obj }).then((sug) => {
+ if (sug) window.dispatchEvent(new CustomEvent('mana:automation-suggest', { detail: sug }));
+ });
});
table.hook('updating', function (modifications, primKey) {
diff --git a/apps/manacore/apps/web/src/lib/modules/automations/ListView.svelte b/apps/manacore/apps/web/src/lib/modules/automations/ListView.svelte
index 1445abca1..358d5eb98 100644
--- a/apps/manacore/apps/web/src/lib/modules/automations/ListView.svelte
+++ b/apps/manacore/apps/web/src/lib/modules/automations/ListView.svelte
@@ -12,12 +12,15 @@
import type { ConditionOp } from '$lib/triggers/conditions';
import type { ViewProps } from '$lib/app-registry';
import { Trash } from '@manacore/shared-icons';
+ import { generateSuggestions, dismissSuggestion, isSuggestionDismissed } from '$lib/triggers';
+ import type { AutomationSuggestion } from '$lib/triggers';
let { navigate, goBack, params }: ViewProps = $props();
// ─── Data ────────────────────────────────────────────────
let automations = $state([]);
- let habits = $state<{ id: string; title: string; emoji: string }[]>([]);
+ let habits = $state<{ id: string; title: string; icon: string }[]>([]);
+ let suggestions = $state([]);
$effect(() => {
const sub = liveQuery(async () => {
@@ -40,7 +43,7 @@
.map((h: Record) => ({
id: h.id as string,
title: h.title as string,
- emoji: h.emoji as string,
+ icon: (h.icon ?? h.emoji ?? 'star') as string,
}));
}).subscribe((val) => {
habits = val ?? [];
@@ -48,6 +51,43 @@
return () => sub.unsubscribe();
});
+ // Load suggestions
+ async function refreshSuggestions() {
+ const all = await generateSuggestions();
+ suggestions = all.filter((s) => !isSuggestionDismissed(s.id));
+ }
+
+ $effect(() => {
+ refreshSuggestions();
+ });
+
+ // Refresh suggestions when automations change
+ $effect(() => {
+ automations; // track
+ refreshSuggestions();
+ });
+
+ async function acceptSuggestion(sug: AutomationSuggestion) {
+ await automationsStore.create({
+ name: sug.name,
+ sourceApp: sug.sourceApp,
+ sourceCollection: sug.sourceCollection,
+ sourceOp: sug.sourceOp,
+ conditionField: sug.conditionField,
+ conditionOp: sug.conditionOp,
+ conditionValue: sug.conditionValue,
+ targetApp: sug.targetApp,
+ targetAction: sug.targetAction,
+ targetParams: sug.targetParams,
+ });
+ suggestions = suggestions.filter((s) => s.id !== sug.id);
+ }
+
+ function handleDismiss(id: string) {
+ dismissSuggestion(id);
+ suggestions = suggestions.filter((s) => s.id !== id);
+ }
+
// ─── Create Form ─────────────────────────────────────────
let showCreate = $state(false);
let newName = $state('');
@@ -129,6 +169,25 @@
{/if}
+
+ {#if suggestions.length > 0}
+
+
Vorschlaege
+ {#each suggestions as sug (sug.id)}
+
+
+ {sug.name}
+ {sug.description}
+
+
+
+
+
+
+ {/each}
+
+ {/if}
+
{#if showCreate}