feat(app): Trigger-CRUD-Section in Settings + nested-Scroll-Fix

Settings hatte zwei Probleme:

1) Gedächtnis-Liste scrollte nur runter, nicht hoch. Klassisches Android
   nested-Scroll-Problem: aeussere ScrollView + innere FlatList mit
   fixer height:600 = nur eine Richtung wird respektiert.

   Fix: outer ScrollView mit scrollEnabled=false wenn die Section eine
   eigene voll-hoch-scrollende Sub-Liste hat (memory/triggers). Plus
   dynamische Hoehe via useWindowDimensions (winHeight - 220 statt
   hardcoded 600) damit MemoryBrowser sauber den verfuegbaren Platz
   nutzt.

2) Trigger waren bisher nur via Diagnostic-Tab editierbar — keine App-
   side CRUD. Stefan wollte das.

   Neu: TriggerBrowser-Komponente (analog MemoryBrowser-Struktur)
   - Liste aller Trigger mit Filter (alle/aktive/inaktive)
   - Toggle aktiv/inaktiv via Switch direkt in der Zeile
   - Tap oeffnet TriggerEditModal (Nachricht/Condition/fires_at/intervals
     editieren, Loeschen-Knopf mit Confirm)
   - "+ Neu"-Knopf oeffnet TriggerNewModal mit Type-Switch (Watcher/Timer),
     Watcher zeigt Hinweis auf verfuegbare Funktionen + Variablen
   - Live Reload-Button, Meta-Info (fire_count, last_fired_at, ...)

   brainApi um Trigger-Endpoints erweitert: listTriggers, getTrigger,
   createTimer, createWatcher, updateTrigger (patch), deleteTrigger,
   getTriggerConditions, getTriggerLogs. Plus Trigger-Type-Definition.

Settings-Liste hat eine neue Section " Trigger" zwischen Gedaechtnis
und Protokoll.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-15 22:44:24 +02:00
parent b4923bc221
commit 7093ebaf0b
3 changed files with 695 additions and 2 deletions
+86
View File
@@ -121,6 +121,24 @@ export interface Memory {
attachments?: MemoryAttachment[];
}
/** Trigger-Manifest wie aus Brain `/triggers/list` zurueckkommt. */
export interface Trigger {
name: string;
type: 'timer' | 'watcher' | string;
active: boolean;
author?: string;
message: string;
fires_at?: string; // ISO, nur timer
condition?: string; // nur watcher
check_interval_sec?: number; // nur watcher
throttle_sec?: number; // nur watcher
fire_count?: number;
last_fired_at?: string | null;
last_checked_at?: string | null;
created_at?: string;
updated_at?: string;
}
// ── Memory CRUD ──────────────────────────────────────────────────────
export const brainApi = {
@@ -215,6 +233,74 @@ export const brainApi = {
{ expectBinary: true, timeoutMs: 60000 },
);
},
// ── Triggers ────────────────────────────────────────────────────────
/** Liste aller Trigger (aktive + inaktive). */
listTriggers(): Promise<Trigger[]> {
return _send('/triggers/list');
},
/** Einzelnen Trigger holen (inkl. fire_count, last_fired_at, ...). */
getTrigger(name: string): Promise<Trigger> {
return _send(`/triggers/${encodeURIComponent(name)}`);
},
/** Verfuegbare Condition-Variablen + Funktionen (fuer Watcher-Editor). */
getTriggerConditions(): Promise<{ variables: any[]; functions: any[] }> {
return _send('/triggers/conditions');
},
/** Trigger-Logs (last N Feuerungen). */
getTriggerLogs(name: string, limit: number = 50): Promise<any[]> {
return _send(`/triggers/${encodeURIComponent(name)}/logs?limit=${limit}`);
},
/** Timer anlegen. fires_at = ISO timestamp (UTC). */
createTimer(body: { name: string; fires_at: string; message: string; author?: string }): Promise<Trigger> {
return _send('/triggers/timer', {
method: 'POST',
body: { author: 'app', ...body },
});
},
/** Watcher anlegen. */
createWatcher(body: {
name: string;
condition: string;
message: string;
check_interval_sec?: number;
throttle_sec?: number;
author?: string;
}): Promise<Trigger> {
return _send('/triggers/watcher', {
method: 'POST',
body: { author: 'app', ...body },
});
},
/** Trigger patchen (active/message/condition/throttle/interval/fires_at). */
updateTrigger(name: string, body: Partial<{
active: boolean;
message: string;
condition: string;
throttle_sec: number;
check_interval_sec: number;
fires_at: string;
}>): Promise<Trigger> {
return _send(`/triggers/${encodeURIComponent(name)}`, {
method: 'PATCH',
body,
});
},
/** Trigger loeschen. */
deleteTrigger(name: string): Promise<{ deleted: string }> {
return _send(`/triggers/${encodeURIComponent(name)}`, {
method: 'DELETE',
timeoutMs: 15000,
});
},
};
export default brainApi;