Quality of life: in-app dialogs, selections in the URL, editable widgets, token dialog, gallery search, status history, stream errors that say why, tab reordering and shortcuts, log out everywhere

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-09-19 10:34:59 -07:00
co-authored by Claude Fable 5.1
parent 8ee2a2d293
commit d7ed4d8549
24 changed files with 361 additions and 101 deletions
+8 -1
View File
@@ -67,7 +67,14 @@ export function useSensorStream<T = unknown>(shape: Shape | null, opts: StreamOp
if (everyMs) params.every = String(everyMs)
const es = new EventSource(url(srcRef.current, '/api/sensors/stream', params))
es.onmessage = (e) => { try { setData(JSON.parse(e.data)); setError('') } catch { /* skip a bad frame */ } }
es.onerror = () => setError('reconnecting…') // EventSource retries by itself
es.onerror = () => {
setError('reconnecting…') // EventSource retries by itself, but never says why it failed — so ask once, plainly
fetch(url(srcRef.current, '/api/sensors/query', { q: key }), { credentials: 'same-origin' }).then(async (r) => {
if (r.ok) return
const why = (await r.json().catch(() => ({}))).error
setError(r.status === 401 ? 'the token was rejected' : r.status === 429 ? 'too many live streams are open' : r.status === 404 ? 'no such machine' : r.status === 502 ? 'that machine is unreachable' : why || `the API answered ${r.status}`)
}).catch(() => setError("can't reach Ham Lab"))
}
return () => es.close()
}, [key, srcKey, visible]) // eslint-disable-line react-hooks/exhaustive-deps
return { data, error, resumed }