From 1186d794cd0892bd19beaf97ce1c27bf75a25585 Mon Sep 17 00:00:00 2001 From: dev Date: Tue, 2 Jun 2026 13:30:44 +0200 Subject: [PATCH] refactor(words): close audit debt D1 (honest cast) + D2 (dedup image picker) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - D1 (soma) block-tree.ts:104 — replace `doc as never` (which disabled all type checking on the descend container) with `doc as unknown as {...}`, the honest cast that still asserts the permissive descend-node shape childStacks() reads. - D2 (eidos) — extract the duplicated ~25-line "file picker → FileReader → data URL" flow (copy-pasted in words-block-gutter.svelte and words-block-panel.svelte) into a shared `pickImageFile(doc, onPick)` helper in words-image-file.ts. Both consumers now call it; zero inline FileReader blocks remain. Kept as a plain helper (not ActiveDom) — a transient + FileReader is not instrumented by active-dom, so a direct Document touch is correct; the caller resolves the right Document. Gates: npm run check 1 error (pre-existing grafito, not Words) · words soma 452/452 · prettier clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../words/words-block-gutter.svelte | 31 +++---------- .../components/words/words-block-panel.svelte | 39 +++++----------- .../components/words/words-image-file.ts | 44 +++++++++++++++++++ .../components/words/engine/block-tree.ts | 28 +++++++----- 4 files changed, 80 insertions(+), 62 deletions(-) create mode 100644 src/uix/eidos/components/words/words-image-file.ts diff --git a/src/uix/eidos/components/words/words-block-gutter.svelte b/src/uix/eidos/components/words/words-block-gutter.svelte index 84dcd4180..536b86ddb 100644 --- a/src/uix/eidos/components/words/words-block-gutter.svelte +++ b/src/uix/eidos/components/words/words-block-gutter.svelte @@ -26,6 +26,7 @@ type ProviderSnippetProps, type WordsBlockMenuEntry } from '$soma/components/words'; + import { pickImageFile } from './words-image-file'; let { api, @@ -206,32 +207,13 @@ // until the user picks a file or types a URL). The gutter inserter // opens a file picker → FileReader → data URL when an image entry // is selected, then inserts the block with a valid `src`. URLs from - // real backends would replace this with a fetch + upload. + // real backends would replace this with a fetch + upload. The picker + // flow is shared with the inspector's image panel (`pickImageFile`). function insertImageInteractive() { const doc = - (typeof document !== 'undefined' ? document : null) ?? - content?.ownerDocument ?? - null; + (typeof document !== 'undefined' ? document : null) ?? content?.ownerDocument ?? null; if (!doc) return; - const input = doc.createElement('input'); - input.type = 'file'; - input.accept = 'image/*'; - input.style.display = 'none'; - input.addEventListener('change', () => { - const file = input.files?.[0]; - input.remove(); - if (!file) return; - const reader = new FileReader(); - reader.onload = () => { - const src = typeof reader.result === 'string' ? reader.result : ''; - if (!src) return; - const alt = file.name.replace(/\.[^/.]+$/, '') || undefined; - insert({ type: 'image', src, alt }); - }; - reader.readAsDataURL(file); - }); - doc.body.appendChild(input); - input.click(); + pickImageFile(doc, (src, alt) => insert({ type: 'image', src, alt })); } function handleInsert(entry: WordsBlockMenuEntry) { @@ -303,7 +285,8 @@ {#if inGutter || menuOpen}
{/if}