Skip to content

fix(plan): keep plan tool switching from corrupting the view - #567

Merged
jplacht merged 1 commit into
mainfrom
fix/561-plan-tool-suspense
Oct 3, 2026
Merged

jplacht merged 1 commit into
mainfrom
fix/561-plan-tool-suspense

Conversation

@jplacht

@jplacht jplacht commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

What and why

Found by /error-review: on /plan/:planet/:uuid, switching plan tools while a tool chunk is still loading throws Cannot read properties of null (reading 'insertBefore') (21 events, 2 users in 24h), and the view stays broken until a reload (_vtc, emitsOptions, bum/parentNode afterwards). The real cause is PlanToolFallback, not the unkeyed Suspense: its root switched from a v-if comment to the spinner <div> after 200 ms, but Suspense keeps the fallback's first root node as its own el (Vue only updates it when the Suspense is the parent's root). On the next tool switch, PlanView unmounts the Suspense and inserts its replacement next to that detached comment, so the parent is null.

This also explains the timing: the spinner component arrived in 0.31.0 (9183d006), when the errors started, and it only breaks when the first tool is still loading more than 200 ms after it was opened.

Changes

  • PlanToolFallback.vue: a fixed root <div> around the delayed spinner.
  • src/tests/views/PlanView.test.ts (new, local component suite): mounts the real PlanView, opens the construction cart while its chunk is held pending, waits past the 200 ms spinner delay, switches to the supply cart, then loads both chunks. It asserts that no Vue error was raised and that the supply cart renders.

Not changed: the :key on the tool Suspense and hoisting the defineAsyncComponent definitions, both suggested in the issue. toggleTool always goes through null, so the Suspense is remounted on every switch anyway. With the old fallback, the test still fails with :key alone. A timing-matrix harness with key and hoisting combined still failed 24 of 32 scenarios. Neither change is needed.

How it was verified

  • Test fails without the fix: yes. It fails with the production error (insertBefore on null) and passes with the fix. This happens in dev-mode Vue too, because the guard skipped in production builds does not hide this path.
  • pnpm test, pnpm tsc, pnpm lint and pnpm knip all pass. pnpm vitest run --config vitest.components.config.ts src/tests/views/PlanView.test.ts src/tests/features/planning/components passes.
  • No in-app e2e run. The unit test reproduces the exact error.

Closes #561

🤖 Generated with Claude Code

- PlanToolFallback renders a fixed root div. Its root used to switch from
  a v-if comment to the spinner div after 200 ms, but Suspense keeps the
  fallback's first root node, so switching tools while a tool still
  loaded inserted into null and left the vnode tree broken until reload.
- Add a PlanView test that switches tools while the first tool chunk is
  still loading; it fails with the production error without the fix.

Closes #561

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@netlify

netlify Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for prunplanner-preview ready!

Name Link
🔨 Latest commit 2b67f4a
🔍 Latest deploy log https://app.netlify.com/projects/prunplanner-preview/deploys/6ac0ac0535e24800085f32ae
😎 Deploy Preview https://deploy-preview-567--prunplanner-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@codacy-production

codacy-production Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity · 0 duplication

Metric Results
Complexity 0
Duplication 0

View in Codacy

🟢 Coverage ∅ diff coverage · +0.00% coverage variation

Metric Results
Coverage variation ✅ +0.00% coverage variation (-1.00%)
Diff coverage ✅ ∅ diff coverage

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (b9e4f79) 3715 3628 97.66%
Head commit (2b67f4a) 3715 (+0) 3628 (+0) 97.66% (+0.00%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#567) 0 0 ∅ (not applicable)

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@jplacht
jplacht merged commit cb0ee51 into main Oct 3, 2026
11 checks passed
@jplacht
jplacht deleted the fix/561-plan-tool-suspense branch October 3, 2026 07:27
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.

Fast plan tool switching breaks the plan view until reload

1 participant