From 7be1d54f8980975b99fc21ecb14a8cd43681cd31 Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 31 May 2026 14:41:30 +0200 Subject: [PATCH] =?UTF-8?q?fix(words):=20inspector=20follows=20every=20cli?= =?UTF-8?q?ck=20=E2=80=94=20drop=20sticky=20atomic-block=20selection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The provider had a "smart" rule in syncSelectionFromDom that kept the atomic-block highlight (and therefore the Inspector's panel) sticky while the caret moved to a different block AS LONG AS that next block was inside the same TOP-LEVEL wrapper (the rationale: editing the trailing paragraph after an image-in-column shouldn't kick the user out of the image inspector). In practice users got the opposite of what they expected: they clicked on something new, the inspector didn't follow. They thought the editor was broken. Drop the smart rule. Every real selection change now also clears the atomic-block highlight — the inspector follows the caret, period. If the user wants the atomic's panel back they click the figure again (which `handleAtomicClick` re-selects). We still skip the clear when `sameWordsSelection` reports no-op so the synthetic selectionchange echo that fires right after `selectAtomicBlock` (which doesn't move the DOM caret) can't wipe the highlight a tick after being set. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/words/words-provider.svelte.ts | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/uix/soma/components/words/words-provider.svelte.ts b/src/uix/soma/components/words/words-provider.svelte.ts index 983c097a7..1359ce6fb 100644 --- a/src/uix/soma/components/words/words-provider.svelte.ts +++ b/src/uix/soma/components/words/words-provider.svelte.ts @@ -530,25 +530,22 @@ export class WordsProvider { if (this.shouldIgnoreTransientSelectionCollapse(next, root, options.source ?? 'direct')) { return false; } - // Clear the atomic-block highlight only when the text selection - // genuinely moved AND landed OUTSIDE the wrapper of the currently - // selected atomic. Synthetic selectionchange echoes that arrive - // right after `selectAtomicBlock` (which doesn't actually move - // the DOM caret) would otherwise wipe the highlight a tick - // later, defeating the click. Same intent for nested atomics - // inside a column: when the engine drops the caret into the - // column's trailing paragraph after `insertImage`, the new - // selection is still inside the SAME top-level wrapper as the - // selected image — don't clear in that case (the inspector needs - // to keep showing the image panel until the user moves to a - // different top-level block). + // Clear the atomic-block highlight whenever the text selection + // genuinely moves. We used to keep the highlight sticky when the + // new caret was in the SAME top-level wrapper as the selected + // atomic (so the image inspector would stay open while editing + // the trailing paragraph in the same column). That sounded clever + // but in practice users were left staring at the wrong inspector + // panel — they clicked somewhere new, the inspector didn't + // follow. Inspector-as-a-source-of-truth wins: every move that + // the engine accepts as a real selection change ALSO clears the + // atomic highlight. The user re-selects the atomic via a fresh + // click on the figure if they want its panel back. + // We still skip the clear when `sameWordsSelection` reports + // no-op so synthetic selectionchange echoes can't wipe the + // highlight a tick after `selectAtomicBlock` set it. if (!sameWordsSelection(this.selection, next)) { - const selectedTopIdx = - this.selectedBlockPath?.[0] ?? this.selectedBlockIndex; - const nextTopIdx = next.anchor.path[0]; - if (selectedTopIdx === undefined || nextTopIdx !== selectedTopIdx) { - this.clearSelectedBlock(); - } + this.clearSelectedBlock(); } this.updateSelection(next); return true;