import { useEffect, useRef } from 'preact/hooks';
import { signal } from '@preact/signals';
import { localGet, localFetch } from '../../lib/api.js';
const FIELDS = ['bell', 'bell_emoji', 'program_exit', 'program_exit_nonzero'];
const prefs = signal({});
const status = signal(null);
export function NotificationsCard() {
const t = useRef(null);
useEffect(() => {
localGet('/api/settings/notifications')
.then((p) => (prefs.value = p || {}))
.catch((e) => flash('Load failed: ' + e.message, false));
}, []);
const flash = (msg, ok = true) => {
status.value = { msg, ok };
clearTimeout(t.current);
t.current = setTimeout(() => (status.value = null), 1500);
};
const onToggle = (field) => async (e) => {
prefs.value = { ...prefs.value, [field]: e.target.checked };
const body = {};
for (const k of FIELDS) body[k] = !!prefs.value[k];
try {
const res = await localFetch('/api/settings/notifications', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (!res.ok) throw new Error('PUT ' + res.status);
flash('Saved.');
} catch (err) {
flash('Save failed: ' + err.message, false);
}
};
const row = (field, title, small) => (
<label class="settings-row">
<input type="checkbox" name={field} checked={!!prefs.value[field]} onChange={onToggle(field)} />
<span class="settings-label">
<strong>{title}</strong>
<small>{small}</small>
</span>
</label>
);
return (
<section class="settings-card">
<h2>Notifications</h2>
<p class="settings-lede">
Pick what fires a push to subscribed devices. Everything is detected by parsing the PTY
stream — no shell hooks needed except the OSC-133 prompt for the exit toggles.
</p>
{row(
'bell',
'Terminal bell (\\x07)',
'Standard ASCII BEL byte. Most apps fire this on tab-complete failures, vim errors, irc highlights.',
)}
{row(
'bell_emoji',
'🔔 emoji in output',
'Used for intentional pings — Claude, scripts, anything that prints the bell glyph.',
)}
{row(
'program_exit',
'Program exit (any code)',
'Detected via OSC 133;D semantic prompt. Requires Starship, Powerlevel10k, or a PS1 that emits the exit marker.',
)}
{row('program_exit_nonzero', 'Program exit (non-zero only)', 'Same OSC 133;D detection, fires only on failures.')}
{status.value && (
<div class="settings-status" style={{ color: status.value.ok ? '' : '#f87171' }}>
{status.value.msg}
</div>
)}
</section>
);
}