You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug(notifications): scope=tray returns unread-only, where the web bell shows the last N #80
Found during PR #73 (G35), outside that issue's scope. Recorded during the 2026-09-13 tracker reconciliation.
The defect
GET /api/notifications?scope=tray returns unread notifications only. The web bell shows the last N regardless of read state.
So on macOS the tray empties as you read it. On the web it retains recent history. Same feature, different behaviour — and the macOS one is the surprising one: a notification you glance at vanishes, with no way to get back to it from the tray.
Already documented at the endpoint:
Packages/InterlinedKit/Sources/InterlinedKit/Endpoints/NotificationsEndpoint.swift:27 — "with scope=tray the server currently ignores limit and returns unread…"
So the client knows; nothing acts on it.
Compounding: the tray limit is now user-configurable
PR #73 shipped notificationTrayLimit in View Preferences — the user can now set how many tray items they want. Against a route that ignores limit and filters to unread, that preference is currently close to inert. Shipping a control the server does not honour is its own small defect.
Two candidate fixes — probe first
A different scope or parameter already does this. Probe GET /api/notifications with other scope values and with limit under each before concluding anything. If a read-inclusive scope exists, this is a one-line change.
Do not work around it by fetching a wider page and filtering client-side. That is exactly the failure mode PR #69 fixed for the DM inbox — client-side reconstruction is only ever as complete as the page fetched.
Acceptance
The tray matches the web bell: last N, read and unread.
notificationTrayLimit visibly changes what the tray shows.
If the route cannot do it, the backend ask is filed and the preference is honestly labelled until then.
Implementation plan (added 2026-09-15) — probe done, it is candidate 1
The issue said "probe first" before concluding anything. Done, and the answer is the cheap one: a read-inclusive scope already exists and honours limit. Live, on the test account:
?scope=tray → 1 item (the single unread one)
?scope=tray&limit=5 → 1 item (limit ignored)
?scope=all&limit=3 → 3 items (read and unread, newest first)
?scope=all&limit=10 → 10 items
?scope=all → 20 items (the account's stored notificationTrayLimit)
unreadCount comes back under both scopes, so the badge is unaffected.
No backend ask is needed, and nothing folds into #58.
The work
Notifications.tray(scope:limit:) defaults to "all". The unread-only scope stays reachable for anything that genuinely wants it — it is just no longer what the tray asks for.
Delete the client-side prefix(limit) in NotificationsService.tray(limit:). It existed only because scope=tray ignored the parameter, and it made notificationTrayLimitappear to work while the rows it trimmed were unread-only — so the preference could only ever shrink an already-wrong list. Not re-added as a belt-and-braces cap either: a client-side trim over a correctly-limited response is invisible when it agrees with the server and wrong when it does not.
Correct the two stale notes that describe scope=tray ignoring limit as a standing constraint (NotificationsEndpoint.swift:27, and the G35 correction block in NotificationsService.swift).
No workaround of the kind the issue warned against: nothing fetches a wider page and filters client-side. The server does the limiting.
Tests
The endpoint sends scope=all by default and still accepts scope: "tray" explicitly.
limit travels alongside the scope; absent limit omits the parameter so the server applies the stored preference.
A fully-read account still sees its tray — the behaviour this whole issue is about.
The server's page is rendered as-is rather than second-guessed.
unreadCount stays server-authoritative regardless of row count.
Acceptance
✅ The tray matches the web bell: last N, read and unread.
✅ notificationTrayLimit visibly changes what the tray shows — now because the server honours it.
Found during PR #73 (G35), outside that issue's scope. Recorded during the 2026-09-13 tracker reconciliation.
The defect
GET /api/notifications?scope=trayreturns unread notifications only. The web bell shows the last N regardless of read state.So on macOS the tray empties as you read it. On the web it retains recent history. Same feature, different behaviour — and the macOS one is the surprising one: a notification you glance at vanishes, with no way to get back to it from the tray.
Already documented at the endpoint:
Packages/InterlinedKit/Sources/InterlinedKit/Endpoints/NotificationsEndpoint.swift:27— "withscope=traythe server currently ignoreslimitand returns unread…"So the client knows; nothing acts on it.
Compounding: the tray limit is now user-configurable
PR #73 shipped
notificationTrayLimitin View Preferences — the user can now set how many tray items they want. Against a route that ignoreslimitand filters to unread, that preference is currently close to inert. Shipping a control the server does not honour is its own small defect.Two candidate fixes — probe first
GET /api/notificationswith otherscopevalues and withlimitunder each before concluding anything. If a read-inclusive scope exists, this is a one-line change.limithonoured under it? Folds into blocked: backend asks - following feed, session-only routes, article_series 502, shape confirmations #58.Do not work around it by fetching a wider page and filtering client-side. That is exactly the failure mode PR #69 fixed for the DM inbox — client-side reconstruction is only ever as complete as the page fetched.
Acceptance
notificationTrayLimitvisibly changes what the tray shows.Implementation plan (added 2026-09-15) — probe done, it is candidate 1
The issue said "probe first" before concluding anything. Done, and the answer is the cheap one: a read-inclusive scope already exists and honours
limit. Live, on the test account:unreadCountcomes back under both scopes, so the badge is unaffected.No backend ask is needed, and nothing folds into #58.
The work
Notifications.tray(scope:limit:)defaults to"all". The unread-only scope stays reachable for anything that genuinely wants it — it is just no longer what the tray asks for.Delete the client-side
prefix(limit)inNotificationsService.tray(limit:). It existed only becausescope=trayignored the parameter, and it madenotificationTrayLimitappear to work while the rows it trimmed were unread-only — so the preference could only ever shrink an already-wrong list. Not re-added as a belt-and-braces cap either: a client-side trim over a correctly-limited response is invisible when it agrees with the server and wrong when it does not.Correct the two stale notes that describe
scope=trayignoringlimitas a standing constraint (NotificationsEndpoint.swift:27, and the G35 correction block inNotificationsService.swift).No workaround of the kind the issue warned against: nothing fetches a wider page and filters client-side. The server does the limiting.
Tests
scope=allby default and still acceptsscope: "tray"explicitly.limittravels alongside the scope; absentlimitomits the parameter so the server applies the stored preference.unreadCountstays server-authoritative regardless of row count.Acceptance
notificationTrayLimitvisibly changes what the tray shows — now because the server honours it.