Repository navigation
Put slash-menu blocks where the dialect allows them - #538
Conversation
|
Review: Looks good Checked in Chromium against a fake-GitHub server built from this branch (desktop 1440×900 and phone 390×844). What I verified
Dialect alignment (drift risk: low, with one gap)
Non-blocking findings
Follow-up already noted: the selection bubble's Bulleted/Numbered conversions still use CI: |
A table cell is a Lexical shadow root, so `getTopLevelElement` stopped inside it and every slash block (callout, table, columns, task list, code, diagram, formula, tabs, diff) landed in the cell, which holds only inline text. The server refused the batch or saved a corrupted cell. Those blocks now go after the whole table, as Research already did; the inline Image stays in the cell. The same placement helper also moves a callout or tabs out of a container that forbids them, and inserts a table, tabs or task list inside a callout, column or tab instead of `$insertNodes` dropping them or `$insertList` replacing the container. The phone dock's `/` and Insert panel share it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Source hash only: SlashMenu's SHELL, DIVIDER and ROW classes and its position style are unchanged; the edits are in command run functions and the phone slash handler, which carry no styling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-ups for the slash placement fix: - Columns now go through the same rule. The dialect marks Columns `topLevel`, so the validator and the slash menu read one flag, and a Columns chosen anywhere nested lands after the top-level block. - The phone dock opens its line beside the caret's block, inside its callout, column or tab, and only after the whole table, list or quote. It opens the line and inserts the block in one update, so one undo removes both. - `CONTAINER_COMPONENTS` maps container node types to dialect names; a test checks it covers every component that can hold a caret. - Bulleted, Numbered and Task list conversions from the selection bubble and the dock kept replacing a whole callout, column or tab, because `$insertList` walks up to a root or shadow root. They now build the list in place, joining an adjacent list of the same kind. An imported quote becomes one item per line instead of an item holding a paragraph. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Source hashes only: in bubble.tsx and slash.tsx the edits are in convertBlock and command run functions; the toolbar and menu classes and position styles are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2905109 to
9557f87
Compare
Found while reviewing #499: the slash menu placed blocks with
getTopLevelElement, which stops inside a table cell because Lexical treats cells as shadow roots. Every slash block typed in a cell landed inside it, but a cell holds only inline text in the dialect.Reproduced in Chromium, typing
/in a body cell. Callout, Columns, Diagram and Tabs were refused by the server ("Edits not saved"). Code, Formula and Diff were refused or saved as bare text. Table and Task list saved as corrupted cell text (MARK | | |and- [ ] MARK)./codein a cellBehaviour per action
While checking columns I found the same root cause, containers that are not shadow roots, causing two more silent failures:
$insertNodesflattens blocks to inline text there.list > Tab.Both now insert relative to the caret's block.
Changes
toolbar/block-target.tsholds the shared placement:$topLevel(moved fromresearch.tsx),$openBlock,$leaveForBlock,$insertBlockand$taskListInContainer. Research, the slash menu, the phone dock's/button (OPEN_SLASH_MENU_COMMAND) and its Insert panel all use it, so the draft or/position and the insert position agree.placed(), which moves the caret and runs the command in one update, so one undo removes both.flowchart LR A[Slash block chosen] --> B{Caret in a table cell,<br/>or a container forbidding it?} B -- yes --> C[New paragraph after the top-level block] B -- no --> D[Caret stays] C --> E[Run command] D --> E E --> F{Host is a paragraph<br/>in a callout/column/tab?} F -- yes --> G[Insert relative to the host block] F -- no --> H[$insertNodes / $setBlocksType / $insertList]Tests
block-target.test.ts(headless Lexical, 50 cases): each block action from a table cell lands after the table, the cell is unchanged, and the result is valid and round-trips. Image stays in the cell.$openBlockfrom a cell opens after the table. Every action in a list, columns, callout and tabs leaves a valid document, with forbidden and allowed placements asserted. Without the fix, 16 fail.toolbar.e2e.ts:/codein an empty cell writes the fence after the table, with no "Edits not saved". Fails on main, passes here locally.bun test(editor),bun run typesandbun run cipass.Renews the design-contract source hash for
slash.tsxin a separate commit. That covers only the hash: the listbox classes and position style are unchanged.Review follow-ups
ColumnsastopLevel, and the validator and the slash menu both read that flag. Columns can't be placed inside a callout or tab, because the dialect only allows them at the top level, so from any nested caret they go after the top-level block.CONTAINER_COMPONENTSmaps container node types to dialect names. A test checks it covers every dialect component that can hold a caret, and that each node type is registered.$insertListwalks up to a root or shadow root, so they now build the list in place and join an adjacent list of the same kind. An imported root quote converted to a list now gives one item per line, instead of an item holding a paragraph.block-target.test.ts; 11 of the new ones fail on the previous code. Added an e2e for a bulleted list made inside a callout. In Chromium, Bulleted and Numbered list in a callout, column and tab, plus the 45-case slash matrix, all save with no rejected batches.🤖 Generated with Claude Code
Landing validation
Preserves the author's insertion fixes and the corrected lane anchor. Duplicate connector test work already on main is excluded. Fresh CI, types, production build, 146 toolbar units, and all 22 toolbar browsers passed locally. Design and lane checks pass after rebasing onto current main. Prior GitHub CI was green; administrator bypass avoids another full cycle for this source-preserving refresh.