fix(workspace): make Backspace merge work for untyped blocks and blocks with children
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s

The merge only applied to a fixed list of block types, so legacy blocks
without a type were ignored, and blocks with children were rejected.
Now only code/divider/table/progress are excluded and children move up
under the previous block. The focused bullet dot is also neutral now.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Sonnet 5.5 committed 2026-10-28 08:53:35 +01:00
1 parent c7e05b385c
commit b4c8577a61
2 files changed
+21 -8

No files matched your search

@@ -598,7 +598,7 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
block.collapsed && hasChildren block.collapsed && hasChildren
? 'bg-accent ring-2 ring-accent/30' ? 'bg-accent ring-2 ring-accent/30'
: activeFocus : activeFocus
? 'bg-accent scale-125' ? 'bg-main scale-125'
: 'bg-faint group-hover/bullet:bg-accent group-hover/bullet:scale-125' : 'bg-faint group-hover/bullet:bg-accent group-hover/bullet:scale-125'
} ${ } ${
isEmpty isEmpty
@@ -525,24 +525,37 @@ export const OutlinerTree = forwardRef<OutlinerTreeHandle, OutlinerTreeProps>(({
const handleMergeWithPrevious = useCallback( const handleMergeWithPrevious = useCallback(
(blockId: string, content: string) => { (blockId: string, content: string) => {
if (!canEdit) return false; if (!canEdit) return false;
const mergeable = ['text', 'paragraph', 'bullet', 'todo', 'h1', 'h2', 'h3', 'callout']; const notMergeable = ['code', 'divider', 'table', 'progress'];
const visIdx = visibleEntries.findIndex((e) => e.block.id === blockId); const visIdx = visibleEntries.findIndex((e) => e.block.id === blockId);
if (visIdx <= 0) return false; if (visIdx <= 0) return false;
const prev = visibleEntries[visIdx - 1].block; const prev = visibleEntries[visIdx - 1].block;
const idx = blocks.findIndex((b) => b.id === blockId); const idx = blocks.findIndex((b) => b.id === blockId);
const cur = blocks[idx]; const cur = blocks[idx];
if (!cur || !mergeable.includes(prev.type) || !mergeable.includes(cur.type)) return false; if (!cur || notMergeable.includes(prev.type) || notMergeable.includes(cur.type)) return false;
// Blocks with children would leave them orphaned at a wrong indent level
const next = blocks[idx + 1]; // Children of the merged block move up so they become children of the previous block
if (next && (next.level || 0) > (cur.level || 0)) return false; const curLevel = Math.max(0, cur.level || 0);
const delta = Math.max(0, prev.level || 0) - curLevel;
const movedChildren: DocumentBlock[] = [];
for (let j = idx + 1; j < blocks.length; j++) {
const lvl = Math.max(0, blocks[j].level || 0);
if (lvl <= curLevel) break;
movedChildren.push({ ...blocks[j], level: Math.max(levelOffset || 0, lvl + delta) });
}
const joinPos = (prev.content || '').length; const joinPos = (prev.content || '').length;
onUpdateBlock({ ...prev, content: (prev.content || '') + content }, true); const mergedPrev: DocumentBlock = { ...prev, content: (prev.content || '') + content };
if (movedChildren.length > 0 && onUpdateBlocks) {
onUpdateBlocks([mergedPrev, ...movedChildren]);
} else {
onUpdateBlock(mergedPrev, true);
movedChildren.forEach((c) => onUpdateBlock(c));
}
onDeleteBlock(cur.id); onDeleteBlock(cur.id);
focusBlockInput(prev.id, joinPos); focusBlockInput(prev.id, joinPos);
return true; return true;
}, },
[blocks, canEdit, focusBlockInput, onDeleteBlock, onUpdateBlock, visibleEntries] [blocks, canEdit, focusBlockInput, levelOffset, onDeleteBlock, onUpdateBlock, onUpdateBlocks, visibleEntries]
); );
const handleIndentBlock = useCallback( const handleIndentBlock = useCallback(