Skip to content

CL-6472: harden MCP presets section against silent empty renders - #203

Merged
TheGreatAxios merged 2 commits into
mainfrom
cl-6472-plugins-catalog
Aug 21, 2026
Merged

TheGreatAxios merged 2 commits into
mainfrom
cl-6472-plugins-catalog

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor

Context

Owner symptom (live, fresh reset stack): Plugins page shows GitHub as connected but no other plugins appear — expected ~10 MCP presets (Granola, Exa, Linear, github-mcp, Notion, Sentry, Attio, Railway, PostHog, Sumble).

Already established before this unit started: GET /api/tenants/{tenant}/mcp-servers/presets returns all 10 presets on a fresh bench with zero connections — the data layer is fine, so the loss was scoped to client-side rendering/composition.

What I found

  • PR CL-6467: plugins list as grouped rows #192 (CL-6467, merged just before this) only touched plugin-card.tsx and the grouped-rows heading/caption in plugins-gallery.tsx — it does not touch McpPresetCardsSection, mcp-preset-cards.tsx, or mcp-servers-api.ts at all. Ruled out as the cause.
  • I mocked the exact API shape the owner's live repro confirmed (10 presets, connected: false, zero connections) and rendered PluginsGallery and McpPresetCardsSection in isolation with that fixture on current main (before any change here). Both rendered all 10 presets correctly. I could not reproduce the reported empty state locally from the client code as authored, given the confirmed API contract.
  • The one real defect I did find in packages/plugins-ui/src/mcp-preset-cards.tsx: McpPresetCardsSection return nulled the entire "Connect apps" section whenever its own presets list was empty and there was no error — which is indistinguishable from "the fetch hasn't resolved yet" (a load-in-progress renders as if the whole catalog vanished). This matches the ticket's named candidate cause ("a query that errors silently and renders an empty state") even though I couldn't force it to actually happen with the confirmed API response.
  • Architecturally, the presets catalog is a third, independently-fetched surface (listMcpPresets) glued next to the native-connector list (listPluginsForTenant/ResolvedPlugin) only by JSX proximity in PluginsGallery — GitHub's "Connected" status comes entirely from the separate, unaffected listPluginsForTenant path, so a failure isolated to the presets fetch is fully consistent with "GitHub shows connected, nothing else appears."

What I did

  • packages/plugins-ui/src/mcp-preset-cards.tsx: added a loaded flag and replaced the single return null with three explicit, always-visible states under the "Connect apps" heading — loading, loaded-and-empty (distinguishing "no apps" from "no search match"), and error. The section can no longer render nothing.
  • Added regression tests:
    • test/mcp-preset-cards.test.tsx: mounts the section with the real MCP_PRESETS registry via the exact response shape the live route returns, asserts all 10 display names reach the DOM; a second test asserts a failed load surfaces a visible error instead of silence.
    • test/plugins-gallery.test.tsx: same 10-preset assertion through the full PluginsGallery composition (not just the isolated component), to catch a future regression in how the gallery wires the section in, not just the section itself.

What I did not verify / next steps

  • I could not reproduce the actual reported bug. All new tests pass against the pre-fix code too (verified by stashing the implementation change and re-running — 19/19 pass). This PR is a hardening/mitigation, not a confirmed fix — the owner's real defect may be something only visible in a running browser session (real cookie/grant timing on a freshly-provisioned bench, a stale bundle, or something else in apps/web wiring I didn't trace to ground). Per instructions, no stack was booted to check this live.
  • Not implemented: the owner's full four-state catalog model (inherited / added-here / blocked / not-connected, 1:1 with the tenant's full list, mock §7). The current architecture keeps native connectors (ResolvedPlugin, chain-aware) and MCP presets (McpPreset, tenant-local connected: boolean only, no provenance) as two separate data shapes fetched independently — collapsing them into one catalog with per-row provenance is a real data-model change I did not attempt in this timebox. Flagging as the next unit; happy to scope it separately.
  • Ran only scoped checks: bun test, bunx tsc --noEmit, and bunx eslint inside packages/plugins-ui, plus bun test test/plugins-page.test.tsx in apps/web. Did not run the full-repo bun run check.
  • No stack was booted (memory-constrained; another lane holds the live slot) — static analysis + render tests only.

Do not merge — reporting for peer review given the root cause is not conclusively confirmed.

Covers the owner's live-repro scenario (fresh bench, zero connections,
presets route returns all 10 curated apps) end to end through
PluginsGallery, plus a guard that the presets section never renders
nothing on a failed load.
McpPresetCardsSection returned null whenever its own list came back
empty and had no error — indistinguishable from a load still in
flight. Track whether the fetch has resolved and always render the
'Connect apps' heading with an explicit loading, empty, or error
state instead of silently disappearing.
@TheGreatAxios
TheGreatAxios merged commit 4b426a0 into main Aug 21, 2026
0 of 2 checks passed
@TheGreatAxios
TheGreatAxios deleted the cl-6472-plugins-catalog branch August 25, 2026 15:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant