microsoft / microsoft/AI-Engineering-Coach
Output page intermittently shows "Failed to render Output — Cannot read properties of null (reading 'id')"
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 4.2k
- Forks
- 585
- Avg merge
- 22h 5m
- Merged PRs (30d)
- 16
Description
Description
Clicking Measure → Output in the dashboard intermittently shows the error boundary:
⚠️ Failed to render Output
Cannot read properties of null (reading 'id')
Try reloading the dashboard (Ctrl+Shift+P → "AI Engineer Coach: Reload Data")
(Screenshot below — please drag-drop the attached image into this issue after creation; the GitHub API cannot upload binary attachments to issue bodies.)
The error originates from Chart.js, which reads .id off the canvas element it is given. The string Cannot read properties of null (reading 'id') is the standard message produced when new Chart(null, …) is called.
Root cause
createChart in src/webview/shared.ts passes whatever document.getElementById(canvasId) returns straight to Chart.js with only a type cast, no null check:
const c = new Chart(document.getElementById(canvasId) as HTMLCanvasElement, { ... });
If the canvas is not in the live DOM at the moment createChart runs, Chart.js dereferences .id on null and throws. The error then bubbles into the withErrorBoundary('Output', …) wrapper in src/webview/app.ts, producing the panel shown in the screenshot.
The Output page is the most exposed surface because both renderProductionTab and renderTokenUsageTab in src/webview/page-output.ts follow this shape:
- Capture
const target = document.getElementById('output-tab-content')! - Render a spinner
await rpc(...)— async gap, nothing is cancelled- Re-render the tab markup containing canvases
- Call
createChart('creditWeeklyChart', …),createChart('prodModelChart', …), etc.
Between steps 3 and 5 the user can change DOM state. The known race windows are:
- Switch nav pages while Output is loading —
renderPage(src/webview/app.tslines 626–631) callsunmount(content); content.textContent = ''; destroyCharts(). The stale Output promise then resolves and renders into a detached tree;document.getElementById('creditWeeklyChart')returnsnullfrom the live DOM → crash. - Switch the inner Output tab (Code Output ↔ Token Usage) before the slow tab finishes. The new render replaces
#output-tab-contentwith the other tab's canvases. The stale promise callscreateChart('creditWeeklyChart', …)but onlyprodModelChart/prodDailyChart/prodHarnessChart/prodLangChart/prodWsChartexist → crash. - Fast clicking on the date-range buttons. The handler at the bottom of
renderOutputfiresvoid renderActiveTab()per click; two concurrent runs race. - Re-index / reload data while Output is the active page —
destroyCharts()runs andrenderPagere-runs, but any RPC already in flight from the previous Output mount keeps going and renders into a stale root.
Secondary contributor: several unguarded non-null assertions in the same path (e.g. document.getElementById('output-tab-content')!, document.getElementById('creditModelTable')!, document.getElementById(panelMap[tab || 'tokens'])!.classList.add('active')) would also throw similar null-derefs if any future change ever rendered the Output shell asynchronously.
Steps to Reproduce
The bug is timing-sensitive and not 100% reproducible. The following all increase likelihood:
- Open the dashboard with a large session corpus (the user's instance shows 341K on Output and 532 sessions on Timeline).
- Click Measure → Output.
- Immediately do one of:
a. Click another nav page (e.g. Timeline) before the spinner clears, then click Output again, or
b. Toggle between the Code Output and Token Usage tabs as fast as possible, or
c. Click multiple date-range buttons (Last 7 days→Last 4 weeks→Last 3 months) in rapid succession, or
d. Run theAI Engineer Coach: Reload Datacommand while Output is mid-load. - Observe the error boundary panel.
Expected Behavior
- The Output page should render charts when data is available, or show an empty state when not.
- Stale async work from a previous mount / previous tab / previous range click should never reach the rendering DOM; it should be cancelled or no-op out.
- If a canvas is somehow missing,
createChartshould log a warning and skip that chart rather than tearing down the whole page.
Suggested Fix
Two layered guards:
- In
createChart(src/webview/shared.ts) — bail out cleanly when the canvas is missing:
(Adjust the return type accordingly.) This alone prevents the page crash.const el = document.getElementById(canvasId) as HTMLCanvasElement | null; if (!el) { console.warn(`createChart: canvas '${canvasId}' not in DOM (stale render?)`); return null; } - In
renderOutput(src/webview/page-output.ts) — add a per-mount generation counter. Bump it on everyrenderActiveTabcall and on unmount. After eachawaitinrenderProductionTab/renderTokenUsageTab, return early if the generation has changed. This avoids wasted renders and visual flicker.
Extension Version
0.1.0
VS Code Version
(please fill in — Help → About)
Operating System
Windows 11
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with createChart in src/webview/shared.ts and the renderOutput, renderProductionTab, and renderTokenUsageTab entry points in src/webview/page-output.ts; review renderPage and destroyCharts in src/webview/app.ts. Reproduce the race by switching pages, tabs, or date ranges during loading. Done means stale work no-ops, missing canvases do not crash the Output page, and valid data or empty states still render.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100