From 53d9e7c9c94ca9759b417d55af62ce8f35f55cff Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Fri, 30 Oct 2026 22:13:24 +0100 Subject: [PATCH] feat(server): plugins list says which sources sign in with a device code Co-Authored-By: Claude Opus 5.5 --- apps/server/src/routes/sources.ts | 4 ++-- apps/server/test/api.test.ts | 6 ++++++ docs/DECISIONS.md | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/server/src/routes/sources.ts b/apps/server/src/routes/sources.ts index 8cb6061..c8feaa1 100644 --- a/apps/server/src/routes/sources.ts +++ b/apps/server/src/routes/sources.ts @@ -38,9 +38,9 @@ export const sourceRoutes = [ route({ method: "GET", path: "plugins", - summary: "Installed source plugins (manifests).", + summary: "Installed source plugins (manifests, plus deviceAuth: whether sign-in uses a device code).", auth: "any:read", - handler: async ({ deps }) => deps.registry.list(), + handler: async ({ deps }) => deps.registry.list().map((m) => ({ ...m, deviceAuth: !!deps.registry.get(m.id)?.deviceAuth })), }), route({ method: "GET", diff --git a/apps/server/test/api.test.ts b/apps/server/test/api.test.ts index 4ab1ccb..e24d4c7 100644 --- a/apps/server/test/api.test.ts +++ b/apps/server/test/api.test.ts @@ -348,6 +348,12 @@ forEachDialect("api", (getDb) => { expect(await (await call(app, "GET", "copies", { cookie: ana })).json()).toEqual([expect.objectContaining({ workTitle: "Dune", format: "Paperback" })]); }); + test("plugins: manifests say whether the source signs in with a device code", async () => { + const ana = await signUp(app, "ana"); + const list = (await (await call(app, "GET", "plugins", { cookie: ana })).json()) as { id: string; deviceAuth: boolean; config: unknown[] }[]; + expect(list.find((p) => p.id === "echo")).toEqual(expect.objectContaining({ deviceAuth: false, config: expect.any(Array) })); + }); + test("JSON bodies need Content-Type application/json (415)", async () => { const res = await app.fetch( new Request(`${BASE}/auth/register`, { diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index b004b5b..b9f3b69 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -17,6 +17,8 @@ No index: `grep -n '^## ' docs/DECISIONS.md` lists every entry (date, title, lin list to its name; null when gone) **and, on merged events, `intoTitle`**: the title of the entry that the same connection's `intoKey` record maps to, else null. Titles come from a fixed number of batched queries (`packages/core/src/titles.ts`), not one per event. +- **`GET plugins` adds `deviceAuth: boolean`** to each manifest, so the Sources screen knows when to offer the + device-code sign-in (the manifest itself has no such capability). - **`listCopies` items carry `workTitle`** (override-aware, null while unmatched) so Owned needs no per-row work lookups. The export bundle is unchanged.