palabras(perf): quita `provider.html` del hot path de tecleo (usa `blocks`)

Investigando el atasco de imágenes base64 se destapó un anti-patrón general:
SEIS effects del chrome (bubble/slash reposition · activate re-stamp · grip/
column-inserter/table-resize remeasure) dependían de `provider.html` como señal
de «cambió el contenido». Pero `html` es el `join('')` O(documento) de TODOS los
htmls de bloque, así que cada tecla lo recomputaba + reasignaba el string entero
(con imágenes base64 en el modelo, MB por tecla; con docs de texto grandes, O(texto)).

Migrados a `provider.blocks` / `api.blocks` — la MISMA señal (el array que el
render ya computa, referencia nueva por cambio, cubre contenido + decoraciones)
pero O(1). `html` deja de computarse en el hot path (solo en export/tests).
Comentarios en cada sitio para que no vuelva a `html`.

NOTA: este NO es el coste dominante del atasco base64 (medido: ~igual antes/
después). El residual (~4ms/MB/char) parece intrínseco del navegador — un
atributo `src` de varios MB en el DOM del contenteditable ralentiza el tecleo.
La raíz (base64 en el modelo por falta de adapter `onUploadImage`) se decide
aparte. Este commit es una mejora de hot path independiente, correcta para docs
grandes en general.

Verificado: check 0 errores palabras; Playwright — grip aparece al activar,
bubble aparece y se posiciona al seleccionar (top 363), 0 errores de consola.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
alpha-0.1-sec-dom
dev 3 months ago
parent e838e7107b
commit 79a7e4dea6

@ -219,9 +219,12 @@
$effect(() => {
const on = enabled; // re-run (and clear) when activation is turned off
const idPath = api.activeBlockId;
// Touch `html` so this effect re-runs after each re-render and
// re-stamps the (freshly recreated) element.
void api.html;
// Touch `blocks` (the render input — O(1) reference, new array per re-render)
// so this effect re-runs after each re-render and re-stamps the freshly
// recreated element. NOT `api.html`: that is the O(document) join of every
// block's HTML — reading it here recomputes it on every keystroke, which is
// catastrophic with large base64 image data-URLs in the model.
void api.blocks;
if (!content) return;
// Clear any stale marker (activation can change WITHOUT a re-render
// — clicking a sibling block — so the previous element keeps its

@ -152,9 +152,11 @@
inserters = out;
}
// Re-measure when the document changes...
// Re-measure when the render changes...
$effect(() => {
void api.html;
// `blocks` (render input, O(1) reference) not `api.html`: html is the
// O(document) HTML join — reading it per keystroke janks large docs / base64.
void api.blocks;
untrack(() => remeasure());
});
// ...and on scroll / resize (positions are viewport-derived).

@ -135,7 +135,9 @@
$effect(() => {
const h = host();
const activeId = api.activeBlockId;
void api.html;
// `blocks` (render input, O(1)) not `api.html` — html is the O(document) join,
// janks large docs / base64 images if read per keystroke.
void api.blocks;
if (!h || !content) return;
let disposed = false;
const place = () => {

@ -87,9 +87,11 @@
handles = out;
}
// Re-measure on document change (skip mid-drag — the live width is inline).
// Re-measure on render change (skip mid-drag — the live width is inline).
$effect(() => {
void api.html;
// `blocks` (render input, O(1)) not `api.html` — html is the O(document) join,
// janks large docs / base64 images if read per keystroke.
void api.blocks;
untrack(() => {
if (!dragKey) remeasure();
});

@ -41,7 +41,12 @@
$effect(() => {
state.open;
state.provider.selection;
state.provider.html;
// Reposition when the render changes. Depend on the `blocks` REFERENCE (O(1) —
// already computed for the content render, new array per change), NOT
// `provider.html`: `html` is the O(document) join of every block's serialized
// HTML, so reading it here would recompute + reallocate the whole string on
// every keystroke (catastrophic with large base64 image data-URLs in the model).
state.provider.blocks;
schedulePosition();
return () => {
cancelFrame?.();

@ -39,7 +39,11 @@
$effect(() => {
state.open;
state.provider.selection;
state.provider.html;
// Reposition on render change via the `blocks` REFERENCE (O(1)), NOT
// `provider.html` — `html` is the O(document) join of every block's HTML, so
// reading it here recomputes it on every keystroke (catastrophic with large
// base64 image data-URLs in the model).
state.provider.blocks;
schedulePosition();
return () => {
cancelFrame?.();

Loading…
Cancel
Save

Powered by TurnKey Linux.