Files
kandev/docs/plans/plan-bubble-menu-transaction-loop/plan.md
T
2026-08-27 12:05:22 +00:00

112 lines
4.2 KiB
Markdown

---
created: 2026-08-27
status: done
requirements:
- REQ-UI-RESPONSIVE-PLAN-FORMATTING-001
system_design:
- ../../specs/ui/system-design/responsive-plan-formatting.md
legacy_specs: []
---
# Implementation Plan: Stop the Plan Bubble Menu Transaction Loop
## Overview
Stop the desktop Plan editor from running a continuous React and Tiptap
transaction loop. Implement one focused TDD work order because the fault and
its regression boundary are in one component.
The trace shows approximately 44,800 React scheduler callbacks in 3.56
seconds. The DOM stays at 4,862 elements. The source-mapped stack shows a
Tiptap BubbleMenu option update followed by an editor transaction and another
PlanBubbleMenu render.
## Scope
### In scope
- Keep the desktop BubbleMenu option identity stable across unchanged renders.
- Publish a new editor snapshot only when its derived values change.
- Add a focused regression that models the real BubbleMenu option-update
transaction.
- Preserve the desktop selection bubble and the mobile docked formatting
strip.
### Out of scope
- Change Plan formatting commands, layout, touch geometry, or viewport
behavior.
- Change Tiptap, ProseMirror, task persistence, or WebSocket code.
- Add performance telemetry or a general React render monitor.
- Change the public plugin editor contract.
## Technical approach
### Stable BubbleMenu inputs
Define the placement options once in
`apps/web/components/editors/tiptap/plan-bubble-menu.tsx`. Pass that stable
object to the desktop BubbleMenu.
### Deduplicated editor snapshots
Compare the next `EditorSnapshot` with the current snapshot. Return the
current state when all derived values are equal. Publish the next snapshot
only after focus, selection, context, or active marks change.
### Regression boundary
Extend `plan-bubble-menu.test.tsx` with a controlled BubbleMenu fake. The fake
models Tiptap's option dependency and its metadata transaction. The test fails
on the current code because the transaction repeats after each render.
## Tests
| Acceptance criterion | Evidence |
| --- | --- |
| `AC-UI-RESPONSIVE-PLAN-FORMATTING-001.1` | The focused Vitest regression proves that the desktop BubbleMenu settles after one option update. Focus, selection, active-mark, and desktop/mobile code-block transitions remain reactive. |
| `AC-UI-RESPONSIVE-PLAN-FORMATTING-001.2` through `.8` | The existing mobile component tests and `mobile-plan-formatting-toolbar.spec.ts` prove that the docked presentation keeps its current behavior. |
## E2E tests
This fix changes render-state normalization only. It does not change layout,
touch behavior, scrolling, navigation, or viewport-dependent interaction.
The existing `mobile-plan-formatting-toolbar.spec.ts` remains the mobile
parity evidence. Run its `mobile-chrome` scenario after the focused unit test.
## Work orders
- [x] [Task 01: Stop the BubbleMenu transaction loop](task-01-stop-bubble-menu-transaction-loop.md) (done)
## Verification results
- Red regression: the controlled BubbleMenu fake observed four option-update
transactions before the fix and failed the settlement assertion.
- `cd apps/web && pnpm exec vitest run components/editors/tiptap/plan-bubble-menu.test.tsx`
passed, 13 tests, including active-mark and code-block transaction changes.
- `cd apps/web && pnpm run typecheck` passed.
- `cd apps/web && pnpm exec eslint components/editors/tiptap/plan-bubble-menu.tsx
components/editors/tiptap/plan-bubble-menu.test.tsx` passed.
- `cd apps/web && pnpm e2e:run --project mobile-chrome
tests/task/mobile-plan-formatting-toolbar.spec.ts` passed, 1 test.
- Fresh desktop and mobile PR screenshots were captured, inspected, validated,
and compressed. The disposable desktop capture spec was removed.
## Risks
- An incomplete snapshot comparison can leave pressed formatting state stale.
- An unstable BubbleMenu prop can reintroduce metadata transactions on each
render.
- A weak menu mock can hide the Tiptap effect that caused the fault.
## Public documentation
None. This fix changes no public command, configuration, API, workflow, or
plugin contract.
## Decisions
No ADR is required. The fix completes the current component design without a
new ownership or public contract boundary.