Summary
When a Forge macro fails to load (orphan customContentId, missing customContentId, throw before render, etc.), the viewer currently mounts NULL_DIAGRAM and the user sees a silent blank macro. They cannot tell the difference between a slow-loading page, a missing macro, and a permission error — and have no path to self-service recovery.
Current behavior
| Viewer |
Where NULL_DIAGRAM is set |
Renderer behavior |
| sequence / mermaid / plantuml |
src/forgeIndex.ts:240 (doc = { ...NULL_DIAGRAM } when CC fail + legacy fallback fail) |
src/components/DiagramPortal.vue:3-5 has v-if for Mermaid / PlantUml / Sequence only; diagramType: 'unknown' matches nothing → renders empty |
| graph (DrawIO) |
src/forge-graph-viewer.ts:135 (store.state.diagram = doc ?? NULL_DIAGRAM) |
empty canvas |
| openapi |
src/forge-swagger-ui.ts:74 (same pattern) + window.updateSpec(OpenApiExample) shows the EXAMPLE spec, not the user's data |
misleading: looks like content loaded, but it's the example |
| embed |
src/forge-embed-viewer.ts:30 (same pattern) |
empty |
The recovery banner at src/components/Viewer/GenericViewer.vue:64-77 (Recovered from backup) covers the case where load failed but legacy/sibling recovery succeeded — it does NOT cover the case where everything failed and NULL_DIAGRAM is mounted.
Impact
- Users have no signal that the macro is broken — they assume "page still loading" and either wait or refresh forever
- No path to self-service: a user who saw "Couldn't load — retry?" would just retry; today they have no button to click
- Support burden: customer reports of "the diagram disappeared" require investigation that could be short-circuited by including the content id in the empty state
Proposed UX
When doc.diagramType === DiagramType.Unknown (sequence-class viewers) OR store.state.diagram === NULL_DIAGRAM (other viewers), render:
┌─────────────────────────────────────────────┐
│ ⚠ Couldn't load this diagram │
│ │
│ Content ID: <customContentId> │
│ │
│ [Retry] [Report issue] │
└─────────────────────────────────────────────┘
Style consistent with the existing viewer-recovered-banner (gray, non-alarming) but with a clearer "this failed" icon.
Don't fire on transient blips
Production telemetry shows a non-trivial bucket of orphan events is bursty and often resolves on retry. Before showing the empty-state UI, do a single automatic retry of loadCustomContentWithOrphanRecovery after a 500-1000ms delay. Only if the second attempt also fails should the empty-state render.
This keeps the UI quiet for the API hiccups that the platform already silently recovers from on refresh.
Out of scope
Related
Summary
When a Forge macro fails to load (orphan customContentId, missing customContentId, throw before render, etc.), the viewer currently mounts
NULL_DIAGRAMand the user sees a silent blank macro. They cannot tell the difference between a slow-loading page, a missing macro, and a permission error — and have no path to self-service recovery.Current behavior
src/forgeIndex.ts:240(doc = { ...NULL_DIAGRAM }when CC fail + legacy fallback fail)src/components/DiagramPortal.vue:3-5hasv-iffor Mermaid / PlantUml / Sequence only;diagramType: 'unknown'matches nothing → renders emptysrc/forge-graph-viewer.ts:135(store.state.diagram = doc ?? NULL_DIAGRAM)src/forge-swagger-ui.ts:74(same pattern) +window.updateSpec(OpenApiExample)shows the EXAMPLE spec, not the user's datasrc/forge-embed-viewer.ts:30(same pattern)The recovery banner at
src/components/Viewer/GenericViewer.vue:64-77(Recovered from backup) covers the case where load failed but legacy/sibling recovery succeeded — it does NOT cover the case where everything failed and NULL_DIAGRAM is mounted.Impact
Proposed UX
When
doc.diagramType === DiagramType.Unknown(sequence-class viewers) ORstore.state.diagram === NULL_DIAGRAM(other viewers), render:Style consistent with the existing
viewer-recovered-banner(gray, non-alarming) but with a clearer "this failed" icon.Don't fire on transient blips
Production telemetry shows a non-trivial bucket of orphan events is bursty and often resolves on retry. Before showing the empty-state UI, do a single automatic retry of
loadCustomContentWithOrphanRecoveryafter a 500-1000ms delay. Only if the second attempt also fails should the empty-state render.This keeps the UI quiet for the API hiccups that the platform already silently recovers from on refresh.
Out of scope
macro_viewed.macro_typefor openapi/graph/embed — separate concern, tracked in feat(telemetry): macro_viewed.macro_type should reflect load outcome for openapi / graph / embed viewers #149Related
src/components/Viewer/GenericViewer.vue— existingviewer-recovered-bannerto match stylesrc/components/DiagramPortal.vue— sequence-class renderer dispatchsrc/model/Diagram/Diagram.ts:88— NULL_DIAGRAM definition