Skip to content

Commit 12ad381

Browse files
committed
docs: capture quote selection seam
1 parent 37a8035 commit 12ad381

File tree

1 file changed

+99
-0
lines changed

1 file changed

+99
-0
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
---
2+
title: Blockquote transforms must keep selection inside the new quote
3+
date: 2026-04-02
4+
category: ui-bugs
5+
module: apps/www editor transforms
6+
problem_type: ui_bug
7+
component: documentation
8+
symptoms:
9+
- Turning a paragraph into a blockquote moved the caret into the previous block.
10+
- Inserting a blockquote from the slash menu selected the previous block instead of the new quote.
11+
- Core `tf.blockquote.toggle()` worked in isolation, which made the regression look like a plugin bug first.
12+
root_cause: wrong_api
13+
resolution_type: code_fix
14+
severity: high
15+
tags:
16+
- blockquote
17+
- selection
18+
- transforms
19+
- apps-www
20+
- slash-menu
21+
- context-menu
22+
- container-blocks
23+
---
24+
25+
# Blockquote transforms must keep selection inside the new quote
26+
27+
## Problem
28+
29+
After blockquote became a real container node, the app-level editor helpers in `apps/www` still treated it like a flat text block.
30+
31+
That mismatch broke the editing flow: inserting or converting a quote created the right shape eventually, but the caret landed in the previous block instead of inside the new quote.
32+
33+
## Symptoms
34+
35+
- `/quote` in `/blocks/editor-ai` inserted a blockquote, then typing went into the previous paragraph.
36+
- Converting a paragraph into a blockquote moved selection from `[1, 0]` to the previous block instead of the wrapped paragraph at `[1, 0, 0]`.
37+
- `editor.tf.blockquote.toggle()` did not reproduce the bug by itself, so the broken seam was easy to misread.
38+
39+
## What Didn't Work
40+
41+
- Treating this like another `BaseBlockquotePlugin` regression. The core wrap transform already preserved selection.
42+
- Keeping `setBlockType(...)` on flat `setNodes({ type: KEYS.blockquote })`. That let normalization repair the node shape later, after selection had already drifted.
43+
- Relying on generic `select: true` after inserting a blockquote node. For a container block, that is not precise enough.
44+
45+
## Solution
46+
47+
Fix the shared `apps/www` editor transform seam instead of patching one UI caller at a time.
48+
49+
- Add a `createBlockquote(...)` helper that inserts a container quote with an inner paragraph.
50+
- Add `selectBlockquoteStart(...)` so quote insertion explicitly selects the nested paragraph start.
51+
- Special-case `insertBlock(editor, KEYS.blockquote)` to insert the container shape and select `[path, 0, 0]`.
52+
- Special-case `setBlockType(editor, KEYS.blockquote)` to call `editor.tf.toggleBlock(type, { wrap: true })` instead of flat `setNodes`.
53+
- Route block-context-menu quote conversion through `setBlockType(...)` so it uses the same fixed path.
54+
- Add regressions for quote insert and quote conversion selection behavior.
55+
56+
The important transform seams became:
57+
58+
```ts
59+
if (type === KEYS.blockquote) {
60+
const insertPath = PathApi.next(path);
61+
62+
editor.tf.insertNodes(createBlockquote(editor), { at: insertPath });
63+
selectBlockquoteStart(editor, insertPath);
64+
65+
return;
66+
}
67+
```
68+
69+
```ts
70+
if (type === KEYS.blockquote) {
71+
editor.tf.toggleBlock(type, {
72+
...(at ? { at } : {}),
73+
wrap: true,
74+
});
75+
76+
return;
77+
}
78+
```
79+
80+
## Why This Works
81+
82+
`blockquote` is now a container element, so insertion and conversion must preserve two things together:
83+
84+
- the nested paragraph child
85+
- the nested selection path inside that paragraph
86+
87+
The core wrap transform already knew how to do that. The app helpers did not. Once the app seam stopped creating flat quotes and stopped using flat block conversion, selection stayed anchored inside the new quote.
88+
89+
## Prevention
90+
91+
- When a node becomes a container element, audit app-level insert and convert helpers. They often lag behind package-level transforms.
92+
- Do not use generic `setNodes` or generic `select: true` for container-block insertion when the user must land inside a nested text block.
93+
- If a core transform path behaves correctly but the UI still breaks, inspect the caller helpers before reopening plugin internals.
94+
- Add one regression for conversion and one for insertion whenever selection behavior depends on nested paths.
95+
96+
## Related Issues
97+
98+
- `#4898`
99+
- Related learning: [2026-04-01-markdown-blockquotes-must-round-trip-as-container-blocks](../logic-errors/2026-04-01-markdown-blockquotes-must-round-trip-as-container-blocks.md)

0 commit comments

Comments
 (0)