Tracking the deferred follow-ups from the deep-review on #2201 (containers + tabs) and #2200 (heatmap external API).
From plan-06b9fd33 (containers + tabs, post-#2201 merge)
These were intentionally deferred during #2201 because they reshape behavior in ways that need a separate decision.
From #2200 deep-review (heatmap external API)
Two P2 findings deferred from the in-flight #2200 + #2199 work.
Out of scope here (already tracked)
Tracking the deferred follow-ups from the deep-review on #2201 (containers + tabs) and #2200 (heatmap external API).
From plan-06b9fd33 (containers + tabs, post-#2201 merge)
These were intentionally deferred during #2201 because they reshape behavior in ways that need a separate decision.
containers, P1 payload risk at scale. Origin: deep-review on feat(external-api): round-trip dashboard containers, tabs, and tile container/tab refs #2201, 2026-05-06 (citepackages/api/src/routers/external-api/v2/dashboards.ts:1398): "LIST endpoint now selectscontainersfor every dashboard. Worst case ≈310KB × 200 dashboards = ~62MB JSON per LIST → dropcontainersfromEXTERNAL_DASHBOARD_PROJECTIONand require GET-by-id to read it (or paginate LIST)." The merged code onmainstill hascontainers: 1inEXTERNAL_DASHBOARD_PROJECTION(used by bothDashboard.findandDashboard.findOne); live verification on 2026-05-12 confirms LIST returns containers on a small dataset, but small-data behavior doesn't resolve a payload-size P1. Decide: (a) dropcontainersfrom the projection and require GET-by-id; (b) keep on LIST, add pagination and a documented per-dashboard cap; (c) conditional projection at a response-size threshold. Earlier wording on this bullet said "LIST currently strips it"; that was my own error and contradicts merged code.containers: []round-trip. Today an empty containers array on PUT is preserved as-is on read; should an explicit empty array be rejected at validation, or treated equivalent to absent? Either is defensible; pick one and pin it with a regression test.From #2200 deep-review (heatmap external API)
Two P2 findings deferred from the in-flight #2200 + #2199 work.
convertTileToExternalChart's heatmap arm returnsundefinedfor malformed/legacy stored docs and the caller falls through todefaultTileConfig, which is hardcodeddisplayType: 'line'. A GET → mutate → PUT round-trip on a corrupt heatmap silently persists the loss ofdisplayType: 'heatmap'. Sibling Number/Pie/Table arms preservedisplayTypeunder the same kind of corruption. Either substitute a heatmap-shaped default or drop the tile entirely rather than morphing it.kindis later changed from Trace to another kind (the v1 source-update endpoint allows this with no dashboard-tile check) wedges every subsequent dashboard PUT, even when editing an unrelated tile, becausegetHeatmapTilesWithIncompatibleSourcesre-runs over the round-tripped tiles on every write. Either block source-kind changes when a heatmap tile references the source, or scope the PUT-time check to tiles that actually changed in this request.Out of scope here (already tracked)
hyperdx_save_dashboard: see External Dashboards API: expose containers and tabs in MCP hyperdx_save_dashboard #2212.