CL-6472: harden MCP presets section against silent empty renders - #203
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/presetsreturns 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
plugin-card.tsxand the grouped-rows heading/caption inplugins-gallery.tsx— it does not touchMcpPresetCardsSection,mcp-preset-cards.tsx, ormcp-servers-api.tsat all. Ruled out as the cause.connected: false, zero connections) and renderedPluginsGalleryandMcpPresetCardsSectionin isolation with that fixture on currentmain(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.packages/plugins-ui/src/mcp-preset-cards.tsx:McpPresetCardsSectionreturn nulled the entire "Connect apps" section whenever its ownpresetslist 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.listMcpPresets) glued next to the native-connector list (listPluginsForTenant/ResolvedPlugin) only by JSX proximity inPluginsGallery— GitHub's "Connected" status comes entirely from the separate, unaffectedlistPluginsForTenantpath, 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 aloadedflag and replaced the singlereturn nullwith 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.test/mcp-preset-cards.test.tsx: mounts the section with the realMCP_PRESETSregistry 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 fullPluginsGallerycomposition (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
apps/webwiring I didn't trace to ground). Per instructions, no stack was booted to check this live.ResolvedPlugin, chain-aware) and MCP presets (McpPreset, tenant-localconnected: booleanonly, 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.bun test,bunx tsc --noEmit, andbunx eslintinsidepackages/plugins-ui, plusbun test test/plugins-page.test.tsxinapps/web. Did not run the full-repobun run check.Do not merge — reporting for peer review given the root cause is not conclusively confirmed.