Slice 6: notifications per §15
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -399,3 +399,100 @@ export async function streamChatTurn(slug, branch, threadId, { text, quote, mode
|
||||
return { assistantId, userMsgId }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// §15 / Slice 6: notifications surface
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function listNotifications({ unread, rfcSlug, category, actorUserId, bundled } = {}) {
|
||||
const params = new URLSearchParams()
|
||||
if (unread) params.set('unread', '1')
|
||||
if (rfcSlug) params.set('rfc_slug', rfcSlug)
|
||||
if (category) params.set('category', category)
|
||||
if (actorUserId) params.set('actor_user_id', actorUserId)
|
||||
if (bundled) params.set('bundled', '1')
|
||||
const qs = params.toString()
|
||||
return jsonOrThrow(await fetch(`/api/notifications${qs ? `?${qs}` : ''}`))
|
||||
}
|
||||
|
||||
export async function markNotificationRead(id) {
|
||||
return jsonOrThrow(await fetch(`/api/notifications/${id}/read`, { method: 'POST' }))
|
||||
}
|
||||
|
||||
export async function markNotificationsReadByFilter(filter) {
|
||||
return jsonOrThrow(await fetch('/api/notifications/read', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(filter || {}),
|
||||
}))
|
||||
}
|
||||
|
||||
export async function listWatches() {
|
||||
return jsonOrThrow(await fetch('/api/watches'))
|
||||
}
|
||||
|
||||
export async function setWatch(slug, state) {
|
||||
return jsonOrThrow(await fetch(`/api/rfcs/${slug}/watch`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ state }),
|
||||
}))
|
||||
}
|
||||
|
||||
export async function getNotificationPreferences() {
|
||||
return jsonOrThrow(await fetch('/api/users/me/notification-preferences'))
|
||||
}
|
||||
|
||||
export async function setNotificationPreferences(prefs) {
|
||||
return jsonOrThrow(await fetch('/api/users/me/notification-preferences', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(prefs),
|
||||
}))
|
||||
}
|
||||
|
||||
export async function getQuietHours() {
|
||||
return jsonOrThrow(await fetch('/api/users/me/quiet-hours'))
|
||||
}
|
||||
|
||||
export async function setQuietHours({ start, end, timezone } = {}) {
|
||||
return jsonOrThrow(await fetch('/api/users/me/quiet-hours', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ start: start || null, end: end || null, timezone: timezone || null }),
|
||||
}))
|
||||
}
|
||||
|
||||
export async function muteUser(userId) {
|
||||
return jsonOrThrow(await fetch(`/api/users/${userId}/notification-mute`, { method: 'POST' }))
|
||||
}
|
||||
|
||||
export async function unmuteUser(userId) {
|
||||
return jsonOrThrow(await fetch(`/api/users/${userId}/notification-mute`, { method: 'DELETE' }))
|
||||
}
|
||||
|
||||
export async function advanceChatSeen(slug, branch, lastSeenMessageId) {
|
||||
return jsonOrThrow(await fetch(`/api/rfcs/${slug}/branches/${branch}/chat-seen`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ last_seen_message_id: lastSeenMessageId || null }),
|
||||
}))
|
||||
}
|
||||
|
||||
// SSE subscription helper. Returns a close() function. The handler
|
||||
// surface mirrors §15.3: a snapshot event on open, then per-notification
|
||||
// `notification` events, plus `read` events when another tab marks a row.
|
||||
export function subscribeToNotifications({ onSnapshot, onNotification, onRead, onError } = {}) {
|
||||
const source = new EventSource('/api/notifications/stream')
|
||||
source.addEventListener('snapshot', e => {
|
||||
try { onSnapshot?.(JSON.parse(e.data)) } catch {}
|
||||
})
|
||||
source.addEventListener('notification', e => {
|
||||
try { onNotification?.(JSON.parse(e.data)) } catch {}
|
||||
})
|
||||
source.addEventListener('read', e => {
|
||||
try { onRead?.(JSON.parse(e.data)) } catch {}
|
||||
})
|
||||
source.onerror = err => { onError?.(err) }
|
||||
return () => source.close()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user