The extension only reads public data, so it can't know what you've already seen. That's why "Replies on your PRs" was removed (see the PR that drops it): a reply stayed in the menu until you acted on the PR, or for up to 30 days. There was no way to dismiss it.
Tangled already tracks this. Its notification service, deliberi (notifs.tangled.network, the org.tangled.temp.notification.* lexicons), offers:
listNotifications, filterable by category and by read state, with types such aspull_commented,pull_merged,pull_closedanduser_mentioned;updateSeen(one notification),markEntityRead(everything about one PR or issue) andmarkAllRead;getUnreadCount(the bell badge).
Read state syncs with tangled.org both ways. Visiting a PR on the web marks its notifications read; dismissing one in the panel would mark it read on the web. The same feed would also cover two backlog items: mentions, and issue activity.
What's needed #
All of this needs a signed-in user, which is the bulk of the work:
- Login. An OAuth flow to the user's PDS from GNOME: a loopback redirect (as atgc does), PKCE, DPoP-bound tokens, and the
rpc:org.tangled.temp.notification.*?aud=*scopes. It probably belongs in Settings ("Sign in to Tangled…"), opening the browser. - Service auth. deliberi takes an atproto service-auth JWT per call, minted by the PDS (
com.atproto.server.getServiceAuth, withaudset to deliberi's did:web andlxmto the method). Today it answers 403 without one. - Token storage. The refresh token and DPoP key go in the user's keyring (libsecret), never in GSettings or the state dir. Sign-out deletes them, and revokes the session if the PDS supports it.
- Refresh and re-prompt.
- Refresh access tokens before they expire.
- When refresh fails (revoked, expired, scopes changed), drop to signed-out mode with one clear "Sign in again" notification, not a stream of errors.
- Survive the PDS being unreachable for a while.
- Where the code runs. The helper process, so the shell never holds secrets and a crash doesn't take the panel down. The extension only shows the state ("Signed in as @x", "Sign in again").
- Fallback. Signed out, everything works exactly as now. Signed in adds:
- a "Notifications" section (or the return of replies, driven by unread
pull_*notifications on your PRs); - dismissal, via
updateSeen/markEntityRead; - optionally, the unread count as its own pill.
- a "Notifications" section (or the return of replies, driven by unread
Open questions #
- Build our own OAuth client, or use atgc? atgc already logs in, stores tokens and refreshes them. If
atgc apilearned a--hostfor deliberi (minting service auth the way it does for knot procedures), the helper could call it and skip steps 1–4. Today it can't: through the PDS it gets 501MethodNotImplemented, and called directly it gets 403 (no token). - Stability. The endpoints are
temp, so expect renames. Keep the method names in one place, as withmergeCheck. - Client metadata. A loopback client needs no hosted metadata. A hosted
client_idwould show a proper name on the consent screen.
Done when #
- Signing in and out from Settings works, and survives a restart.
- A notification read on tangled.org disappears from the panel within one refresh, and one dismissed in the panel is read on tangled.org.
- Expired or revoked tokens lead to exactly one "Sign in again" prompt, and the extension keeps working signed-out meanwhile.
- No secrets in GSettings, the state dir, the fetch log or diagnostics.