fix(app): Inbox Scroll-Bug + feat(diagnostic): Trigger-Edit
App-Inbox-Modal:
- ScrollView der Top-Section ('Aus diesem Chat') nestedScrollEnabled=true
- MemoryBrowser darunter in einen flex:1-Wrapper gepackt damit er den
verbleibenden Platz bekommt — ohne den hat seine FlatList intern
null Hoehe gehabt und Scroll-Gestures verschluckt.
Diagnostic Trigger-Tab:
- ✎ Bearbeiten-Knopf pro Zeile (neben Aktivieren/Deaktivieren/Loeschen)
- Modal hat jetzt einen Edit-Modus: Type+Name disabled, Save-Button
zeigt 'Speichern', Modal-Title 'Trigger bearbeiten — <name>'
- Fuer Timer im Edit-Modus ein zusaetzliches Feld 'Feuert am (ISO, UTC)'
damit man den absoluten Zeitpunkt direkt aendern kann (statt 'in X
Minuten ab jetzt' das nur fuer Create Sinn macht)
- saveTrigger() unterscheidet jetzt zwischen Create-Modus (POST
/triggers/timer|watcher) und Edit-Modus (PATCH /triggers/{name})
- openTriggerEdit(name) fuellt das Modal mit Werten aus dem Cache
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2539,7 +2539,7 @@ const ChatScreen: React.FC = () => {
|
||||
<Text style={{color:'#8888AA', fontSize:11, paddingHorizontal:14, paddingTop:8, paddingBottom:4, textTransform:'uppercase', letterSpacing:0.5}}>
|
||||
Aus diesem Chat
|
||||
</Text>
|
||||
<ScrollView style={{paddingHorizontal:8}}>
|
||||
<ScrollView style={{paddingHorizontal:8}} nestedScrollEnabled={true}>
|
||||
{specials.map(m => {
|
||||
if (m.memorySaved) {
|
||||
const ms = m.memorySaved;
|
||||
@@ -2595,7 +2595,12 @@ const ChatScreen: React.FC = () => {
|
||||
<Text style={{color:'#8888AA', fontSize:11, paddingHorizontal:14, paddingTop:10, paddingBottom:4, textTransform:'uppercase', letterSpacing:0.5}}>
|
||||
Alle Memories aus der DB
|
||||
</Text>
|
||||
<MemoryBrowser onOpenMemory={(id) => { setInboxVisible(false); setMemoryDetailId(id); }} />
|
||||
{/* flex:1 Wrapper damit MemoryBrowser den verbleibenden Platz
|
||||
bekommt (sonst rendert die FlatList intern mit 0 Hoehe und
|
||||
nimmt nur was der Inhalt sagt → Scroll-Gestures verschwinden). */}
|
||||
<View style={{flex:1}}>
|
||||
<MemoryBrowser onOpenMemory={(id) => { setInboxVisible(false); setMemoryDetailId(id); }} />
|
||||
</View>
|
||||
</View>
|
||||
</ErrorBoundary>
|
||||
</Modal>
|
||||
|
||||
+98
-5
@@ -968,11 +968,11 @@
|
||||
</div>
|
||||
</div><!-- /tab-triggers -->
|
||||
|
||||
<!-- Trigger-Create Modal -->
|
||||
<!-- Trigger-Create/Edit Modal -->
|
||||
<div class="modal-overlay" id="trigger-modal">
|
||||
<div class="modal-box" style="max-width:600px;">
|
||||
<div class="modal-header">
|
||||
<h3>Neuer Trigger</h3>
|
||||
<h3 id="trigger-modal-title">Neuer Trigger</h3>
|
||||
<button class="modal-close" onclick="closeTriggerModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body" style="padding:16px;">
|
||||
@@ -986,8 +986,16 @@
|
||||
|
||||
<!-- Timer-spezifisch -->
|
||||
<div id="trigger-timer-fields">
|
||||
<label style="display:block;font-size:11px;color:#8888AA;margin-bottom:4px;">In wievielen Minuten?</label>
|
||||
<input type="number" id="trigger-timer-minutes" min="1" max="10080" value="10" style="width:100%;background:#0D0D1A;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:inherit;margin-bottom:10px;">
|
||||
<!-- Create-mode: relativ („in X Minuten ab jetzt") -->
|
||||
<div id="trigger-timer-create-fields">
|
||||
<label style="display:block;font-size:11px;color:#8888AA;margin-bottom:4px;">In wievielen Minuten?</label>
|
||||
<input type="number" id="trigger-timer-minutes" min="1" max="10080" value="10" style="width:100%;background:#0D0D1A;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:inherit;margin-bottom:10px;">
|
||||
</div>
|
||||
<!-- Edit-mode: absoluter ISO-Timestamp (UTC) -->
|
||||
<div id="trigger-timer-edit-fields" style="display:none;">
|
||||
<label style="display:block;font-size:11px;color:#8888AA;margin-bottom:4px;">Feuert am (ISO, UTC)</label>
|
||||
<input type="text" id="trigger-timer-fires-at" placeholder="2026-05-15T20:00:00+00:00" style="width:100%;background:#0D0D1A;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:monospace;margin-bottom:10px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Watcher-spezifisch -->
|
||||
@@ -1008,7 +1016,7 @@
|
||||
</div>
|
||||
<div class="modal-footer" style="padding:10px 16px;border-top:1px solid #1E1E2E;display:flex;justify-content:flex-end;gap:8px;">
|
||||
<button class="btn secondary" onclick="closeTriggerModal()">Abbrechen</button>
|
||||
<button class="btn" onclick="saveTrigger()">Anlegen</button>
|
||||
<button class="btn" id="trigger-modal-save-btn" onclick="saveTrigger()">Anlegen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3101,6 +3109,7 @@
|
||||
<div style="color:#8888AA;font-size:11px;margin-top:4px;">${detailLine}</div>
|
||||
<div style="color:#888;font-size:12px;margin-top:2px;">"${escapeHtml(t.message || '')}"</div>
|
||||
<div style="margin-top:6px;display:flex;gap:6px;">
|
||||
<button class="btn secondary" onclick="openTriggerEdit('${escapeHtml(t.name)}')" style="padding:2px 10px;font-size:10px;color:#0096FF;border-color:#0096FF;">✎ Bearbeiten</button>
|
||||
<button class="btn secondary" onclick="toggleTriggerActive('${escapeHtml(t.name)}', ${!active})" style="padding:2px 10px;font-size:10px;color:#FF9500;border-color:#FF9500;">${active ? '⏸ Deaktivieren' : '▶ Aktivieren'}</button>
|
||||
<button class="btn secondary" onclick="deleteTrigger('${escapeHtml(t.name)}')" style="padding:2px 10px;font-size:10px;color:#FF6B6B;border-color:#FF6B6B;">🗑 Löschen</button>
|
||||
</div>
|
||||
@@ -3138,10 +3147,21 @@
|
||||
document.getElementById('trigger-watcher-fields').style.display = t === 'watcher' ? '' : 'none';
|
||||
}
|
||||
|
||||
// null = Create-Modus, string = Edit-Modus (Name der bearbeiteten Bubble)
|
||||
let editingTriggerName = null;
|
||||
|
||||
async function openTriggerCreate() {
|
||||
editingTriggerName = null;
|
||||
document.getElementById('trigger-modal-title').textContent = 'Neuer Trigger';
|
||||
document.getElementById('trigger-modal-save-btn').textContent = 'Anlegen';
|
||||
document.getElementById('trigger-type').disabled = false;
|
||||
document.getElementById('trigger-name').disabled = false;
|
||||
document.getElementById('trigger-timer-create-fields').style.display = '';
|
||||
document.getElementById('trigger-timer-edit-fields').style.display = 'none';
|
||||
document.getElementById('trigger-type').value = 'timer';
|
||||
document.getElementById('trigger-name').value = '';
|
||||
document.getElementById('trigger-timer-minutes').value = '10';
|
||||
document.getElementById('trigger-timer-fires-at').value = '';
|
||||
document.getElementById('trigger-condition').value = '';
|
||||
document.getElementById('trigger-check-interval').value = '300';
|
||||
document.getElementById('trigger-throttle').value = '3600';
|
||||
@@ -3170,6 +3190,52 @@
|
||||
|
||||
function closeTriggerModal() {
|
||||
document.getElementById('trigger-modal').classList.remove('open');
|
||||
editingTriggerName = null;
|
||||
}
|
||||
|
||||
/** Edit-Modus: Modal mit existierenden Trigger-Werten fuellen. */
|
||||
async function openTriggerEdit(name) {
|
||||
const t = triggersCache.find(x => x.name === name);
|
||||
if (!t) { alert('Trigger nicht in cache, lade neu...'); loadTriggers(); return; }
|
||||
editingTriggerName = name;
|
||||
document.getElementById('trigger-modal-title').textContent = 'Trigger bearbeiten — ' + name;
|
||||
document.getElementById('trigger-modal-save-btn').textContent = 'Speichern';
|
||||
// Type + Name sind im Edit-Modus nicht aenderbar
|
||||
document.getElementById('trigger-type').value = t.type;
|
||||
document.getElementById('trigger-type').disabled = true;
|
||||
document.getElementById('trigger-name').value = t.name;
|
||||
document.getElementById('trigger-name').disabled = true;
|
||||
// Timer: relative-Minutes-Feld aus, absoluter ISO-Feld an
|
||||
document.getElementById('trigger-timer-create-fields').style.display = 'none';
|
||||
document.getElementById('trigger-timer-edit-fields').style.display = '';
|
||||
document.getElementById('trigger-timer-fires-at').value = t.fires_at || '';
|
||||
// Watcher-Felder vorbefuellen
|
||||
document.getElementById('trigger-condition').value = t.condition || '';
|
||||
document.getElementById('trigger-check-interval').value = String(t.check_interval_sec || 300);
|
||||
document.getElementById('trigger-throttle').value = String(t.throttle_sec || 3600);
|
||||
document.getElementById('trigger-message').value = t.message || '';
|
||||
document.getElementById('trigger-modal-error').style.display = 'none';
|
||||
onTriggerTypeChange();
|
||||
// Variablen-Hinweis fuer Watcher auch im Edit-Modus
|
||||
if (t.type === 'watcher') {
|
||||
try {
|
||||
const r = await fetch('/api/brain/triggers/conditions');
|
||||
const d = await r.json();
|
||||
const info = document.getElementById('trigger-vars-info');
|
||||
if (info) {
|
||||
const vars = (d.variables || []).map(v =>
|
||||
`<code>${escapeHtml(v.name)}</code>=${escapeHtml(String(d.current[v.name]))} <span style="color:#444;">(${escapeHtml(v.desc)})</span>`
|
||||
).join(' · ');
|
||||
const fns = (d.functions || []).map(f =>
|
||||
`<code>${escapeHtml(f.signature)}</code> — ${escapeHtml(f.desc)}`
|
||||
).join('<br>');
|
||||
info.innerHTML =
|
||||
'<strong>Variablen:</strong> ' + vars +
|
||||
(fns ? '<br><br><strong>Funktionen:</strong><br>' + fns : '');
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
document.getElementById('trigger-modal').classList.add('open');
|
||||
}
|
||||
|
||||
async function saveTrigger() {
|
||||
@@ -3181,6 +3247,33 @@
|
||||
if (!name) { errEl.textContent = 'Name fehlt.'; errEl.style.display = 'block'; return; }
|
||||
if (!message) { errEl.textContent = 'Nachricht fehlt.'; errEl.style.display = 'block'; return; }
|
||||
try {
|
||||
// ── EDIT-MODUS ──────────────────────────────────────────
|
||||
if (editingTriggerName) {
|
||||
const patch = { message };
|
||||
if (ttype === 'watcher') {
|
||||
const condition = document.getElementById('trigger-condition').value.trim();
|
||||
if (!condition) { errEl.textContent = 'Condition fehlt.'; errEl.style.display = 'block'; return; }
|
||||
patch.condition = condition;
|
||||
patch.check_interval_sec = parseInt(document.getElementById('trigger-check-interval').value, 10) || 300;
|
||||
patch.throttle_sec = parseInt(document.getElementById('trigger-throttle').value, 10) || 3600;
|
||||
} else if (ttype === 'timer') {
|
||||
const fa = document.getElementById('trigger-timer-fires-at').value.trim();
|
||||
if (fa) patch.fires_at = fa;
|
||||
}
|
||||
const r = await fetch('/api/brain/triggers/' + encodeURIComponent(editingTriggerName), {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(patch),
|
||||
});
|
||||
if (!r.ok) {
|
||||
const txt = await r.text();
|
||||
throw new Error('HTTP ' + r.status + ': ' + txt.slice(0, 200));
|
||||
}
|
||||
closeTriggerModal();
|
||||
loadTriggers();
|
||||
return;
|
||||
}
|
||||
// ── CREATE-MODUS ────────────────────────────────────────
|
||||
let url, body;
|
||||
if (ttype === 'timer') {
|
||||
const mins = parseInt(document.getElementById('trigger-timer-minutes').value, 10) || 10;
|
||||
|
||||
Reference in New Issue
Block a user