Summary
Five live Lists routes the client does not build. Together they are the whole "lists other people gave me access to" story, which the macOS sidebar has no equivalent of at all.
The gap, route by route
| Route |
Live status |
What it unlocks |
GET /api/lists/watching |
✅ 200 with real rows (probed 2026-09-07) |
Watched / shared-with-me lists — the datagrid the web shows on /lists |
GET /api/lists/{id}/contributors |
exists |
The full ranked contributor list |
POST /api/lists/{id}/watchers |
exists, subscriber-gated |
Add a watcher. The client can read and delete watchers but not add one |
GET /api/lists/shared/{token}/data |
exists, public |
Row data for a token-shared list — the read-only viewer's missing half |
GET /api/lists/invite/{token} |
exists, public |
The email-invite landing page |
Live shape for GET /api/lists/watching (2026-09-07)
{"lists":[{"id":"1df0fe30-…","userId":"c65092fa-…","messageId":null,
"parentId":"7835c874-…","folderId":null,
"title":"Shows Upcoming & Seen","description":null,"isPublic":true,
"metadata":null,"source":"local","githubRepo":null,"githubRepoPrivate":null,
"createdAt":"…","updatedAt":"…","deletedAt":null,
"user":{"id":"c65092fa-…","username":"adron","displayName":"Adron Hall"},
"parent":{"id":"7835c874-…","title":"The Metal"},
"children":[],"role":"collaborator"}]}
Note it carries role (the caller's role on that list), an embedded user (the owner), and a parent projection — everything the web's datagrid column set needs ("Each entry shows the list title, owner, your role, and a link to view the list", /help/lists).
It also carries folderId and githubRepoPrivate, which matter to two sibling issues (list folders; GitHub-backed lists).
⚠️ Backend constraint on the invite pair
GET /api/lists/invite/{token} is public, but POST /api/lists/invite/{token} (the accept half) is declared x-auth-type: session in the live spec — a Bearer-only client cannot claim an invite. Same for POST /api/lists/shared/{token}.
So this issue delivers the landing experience (show what the invite grants, who sent it, which list) and must hand the accept step to the browser until the backend exposes a Bearer-reachable claim route. That backend ask is filed separately. Do not ship a native Accept button that 401s.
Accepting an invite is documented as always free, so no entitlement gate belongs on the landing view.
Roles vocabulary (/help/lists)
UI label → API role name: Read-only → watcher, Edit → collaborator, Admin → manager. The domain already uses these names in Sharing.swift; keep the UI labels aligned with the web's.
Division of labor
Kit
Domain
App
Tests
Acceptance criteria
- A list someone shared with me appears in the macOS sidebar without me knowing its URL.
- Opening a share-link as a viewer shows the rows.
- Full E2E gate green.
Notes
work-consolidation.md tracks this as G23. Size M.
Summary
Five live Lists routes the client does not build. Together they are the whole "lists other people gave me access to" story, which the macOS sidebar has no equivalent of at all.
The gap, route by route
GET /api/lists/watching/listsGET /api/lists/{id}/contributorsPOST /api/lists/{id}/watchersGET /api/lists/shared/{token}/dataGET /api/lists/invite/{token}Live shape for
GET /api/lists/watching(2026-09-07){"lists":[{"id":"1df0fe30-…","userId":"c65092fa-…","messageId":null, "parentId":"7835c874-…","folderId":null, "title":"Shows Upcoming & Seen","description":null,"isPublic":true, "metadata":null,"source":"local","githubRepo":null,"githubRepoPrivate":null, "createdAt":"…","updatedAt":"…","deletedAt":null, "user":{"id":"c65092fa-…","username":"adron","displayName":"Adron Hall"}, "parent":{"id":"7835c874-…","title":"The Metal"}, "children":[],"role":"collaborator"}]}Note it carries
role(the caller's role on that list), an embeddeduser(the owner), and aparentprojection — everything the web's datagrid column set needs ("Each entry shows the list title, owner, your role, and a link to view the list",/help/lists).It also carries
folderIdandgithubRepoPrivate, which matter to two sibling issues (list folders; GitHub-backed lists).GET /api/lists/invite/{token}is public, butPOST /api/lists/invite/{token}(the accept half) is declaredx-auth-type: sessionin the live spec — a Bearer-only client cannot claim an invite. Same forPOST /api/lists/shared/{token}.So this issue delivers the landing experience (show what the invite grants, who sent it, which list) and must hand the accept step to the browser until the backend exposes a Bearer-reachable claim route. That backend ask is filed separately. Do not ship a native Accept button that 401s.
Accepting an invite is documented as always free, so no entitlement gate belongs on the landing view.
Roles vocabulary (
/help/lists)UI label → API role name: Read-only →
watcher, Edit →collaborator, Admin →manager. The domain already uses these names inSharing.swift; keep the UI labels aligned with the web's.Division of labor
Kit
Lists.watching()→GET /api/lists/watchingLists.contributors(id:)→GET /api/lists/{id}/contributorsLists.addWatcher(listID:userID:)→POST /api/lists/{id}/watchersLists.sharedData(token:)→GET /api/lists/shared/{token}/dataLists.invite(token:)→GET /api/lists/invite/{token}watchingrows are a superset of the owned-list shape — reuse the existingListDTOwhere it fits rather than forking it, and addrole/user/parentas optionals.Domain
WatchedListprojection (list + owner + caller's role) and aListsService.watching().contributors(listID:)andaddWatcher(...), the latter gated on the subscriber entitlement (see the entitlement issue).App
AddWatcherSheetView(already present) to the real add-watcher call.interlinedlist://…/invite/{token}, ending in "Accept in your browser" until the claim route is Bearer-reachable.Tests
watching500s → the sidebar section shows an error, owned lists still renderparentprojection must not imply access)Acceptance criteria
Notes
work-consolidation.mdtracks this as G23. Size M.