Fix Trakt shared credentials UX, headers panel, and watchlist seed reset - #1041
Fix Trakt shared credentials UX, headers panel, and watchlist seed reset#1041electather wants to merge 3 commits into
Conversation
…empty state fix: remove redundant success toast for personal API key fallback policy fix: correct Trakt shared credential testing to accurately report token status fix: update shared credentials empty state message for clarity fix: adjust shared credentials empty state UI for better user experience feat: add shared client_id/secret verification for user connection in Trakt plugin
Fallow combined reportNo GitHub PR/MR findings. Generated by fallow. |
Code Review — PR #1041OverviewFour logical fixes bundled together: Trakt shared-credential verification, headers panel table→list refactor, shared-credentials empty-state dedup, watchlist seed reset on new connection. Changesets are correct (four separate files, right scopes and bump levels). 🔴 BLOCKING — Design-doc syncPR description links no design document. Under repo policy every substantive implementation PR must link the relevant doc. The `verifyShared` hook is a new extension of the plugin SDK surface. Action required: Add a link to the design doc(s) this PR belongs to in the description, and either confirm 🔴 BLOCKING —
|
| className="gap-1.5" | ||
| > | ||
| <Label className="flex items-start gap-2 rounded-md px-2 py-1.5 text-sm font-normal hover:bg-muted/50"> | ||
| <RadioGroupItem value="inherit" className="mt-0.5" /> | ||
| <RadioGroupItem value="inherit"> | ||
| <span>{m.admin_plugins_allowlist_mode_inherit()}</span> | ||
| </Label> | ||
| <Label className="flex items-start gap-2 rounded-md px-2 py-1.5 text-sm font-normal hover:bg-muted/50"> | ||
| <RadioGroupItem value="restrict" className="mt-0.5" /> | ||
| </RadioGroupItem> | ||
| <RadioGroupItem value="restrict"> |
There was a problem hiding this comment.
BLOCKING — layout styles lost + incorrect Radix usage
RadioGroupItem renders as <button role="radio">. The <Label> wrapper was doing two jobs here: (1) the flex/gap/padding layout and (2) the full-row hover:bg-muted/50 interactive area. Both are gone.
Putting <span> inside RadioGroupItem works for click semantics but the visual result is a small circle with text crammed inside a <button>, not a styled selectable row.
Suggested fix — keep the label wrapper, just tighten it:
| className="gap-1.5" | |
| > | |
| <Label className="flex items-start gap-2 rounded-md px-2 py-1.5 text-sm font-normal hover:bg-muted/50"> | |
| <RadioGroupItem value="inherit" className="mt-0.5" /> | |
| <RadioGroupItem value="inherit"> | |
| <span>{m.admin_plugins_allowlist_mode_inherit()}</span> | |
| </Label> | |
| <Label className="flex items-start gap-2 rounded-md px-2 py-1.5 text-sm font-normal hover:bg-muted/50"> | |
| <RadioGroupItem value="restrict" className="mt-0.5" /> | |
| </RadioGroupItem> | |
| <RadioGroupItem value="restrict"> | |
| <Label className="flex items-center gap-2 rounded-md px-2 py-1.5 text-sm font-normal hover:bg-muted/50 cursor-pointer"> | |
| <RadioGroupItem value="inherit" /> | |
| {m.admin_plugins_allowlist_mode_inherit()} | |
| </Label> | |
| <Label className="flex items-center gap-2 rounded-md px-2 py-1.5 text-sm font-normal hover:bg-muted/50 cursor-pointer"> | |
| <RadioGroupItem value="restrict" /> | |
| {m.admin_plugins_allowlist_mode_restrict()} | |
| </Label> |
This also means the Label import should be restored in the import block.
| export async function verifyShared(ctx: unknown): Promise<{ ok: boolean; message?: string }> { | ||
| const c = ctx as Ctx; | ||
| const shared = c.sharedCredentials as TraktSharedCreds | null; | ||
| if (!shared?.clientId) return { ok: false, message: "client id missing" }; | ||
| const res = await c.fetch(`${BASE}/oauth/device/code`, { | ||
| method: "POST", | ||
| headers: { "content-type": "application/json" }, | ||
| body: JSON.stringify({ client_id: shared.clientId }), | ||
| }); | ||
| if (res.ok) return { ok: true }; | ||
| if (res.status === 401 || res.status === 403) return { ok: false, message: "invalid client id" }; |
There was a problem hiding this comment.
Non-blocking — unhandled network error
c.fetch can throw on timeout, DNS failure, or TLS error. The exception propagates uncaught instead of returning { ok: false }. Callers expecting a result shape will see an unhandled rejection.
| export async function verifyShared(ctx: unknown): Promise<{ ok: boolean; message?: string }> { | |
| const c = ctx as Ctx; | |
| const shared = c.sharedCredentials as TraktSharedCreds | null; | |
| if (!shared?.clientId) return { ok: false, message: "client id missing" }; | |
| const res = await c.fetch(`${BASE}/oauth/device/code`, { | |
| method: "POST", | |
| headers: { "content-type": "application/json" }, | |
| body: JSON.stringify({ client_id: shared.clientId }), | |
| }); | |
| if (res.ok) return { ok: true }; | |
| if (res.status === 401 || res.status === 403) return { ok: false, message: "invalid client id" }; | |
| try { | |
| const res = await c.fetch(`${BASE}/oauth/device/code`, { | |
| method: "POST", | |
| headers: { "content-type": "application/json" }, | |
| body: JSON.stringify({ client_id: shared.clientId }), | |
| }); | |
| if (res.ok) return { ok: true }; | |
| if (res.status === 401 || res.status === 403) return { ok: false, message: "invalid client id" }; | |
| return { ok: false, message: `Trakt ${res.status}` }; | |
| } catch { | |
| return { ok: false, message: "network error" }; | |
| } |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fab9757fc6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // already gave up on (locked after an earlier, possibly empty, run). | ||
| // Clearing the lock lets the next GET re-fetch instead of waiting for | ||
| // the 6-hourly cron sync. | ||
| await resetWatchlistSeed(args.userId); |
There was a problem hiding this comment.
Preserve seeded users when resetting the lock
When a user who already has watchlist rows adds any connection, this deletes the user_watchlist_seed row via clearSeedLock. The 6-hour sync job sources rows from listSeededUserIds() (apps/server/src/watchlist/jobs/sync-plugin-watchlist.ts), and the first-page read only recreates the marker when hasActiveRows() is false (apps/server/src/watchlist/internal/reads.ts), so seeded users with existing rows are dropped from scheduled plugin sync indefinitely. Use a separate retry marker or explicitly re-seed/sync without deleting the scheduler marker.
Useful? React with 👍 / 👎.
Summary
Test plan