feat(words): gutter rework — drop "+", move insert into drag menu, silver band

User-directed editor rework, step 1 (left gutter):

- Remove the "+" inserter button from the block-handle gutter. Its
  action moves into the drag-handle menu as a new "Insert below" item
  (folded into `act('insertBelow')`); the standalone `insertBelow`
  helper is gone. The separate seam inserter (words-block-inserter)
  is untouched.
- Turn the left gutter into a soft silver margin band: narrow it
  (`--_words-gutter-size` 3.5rem→2.5rem) and paint it via a
  background-gradient on [data-words-content] — `--words-rail-bg`
  #ededed band with a 1px, slightly stronger `--words-rail-border`
  #cfcfcf right edge. Background only, so the contenteditable text /
  selection is undisturbed. Recipe tokens softened + eidos CSS
  regenerated.

Verified in browser: gutter shows only the grip on hover (no "+");
the menu reads Insert below / Move up / Move down / Duplicate / Delete
and "Insert below" inserts a paragraph (7→8 blocks) and closes the
menu; the silver band renders at 40px with the 1px border. npm run
check: 0 errors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
active-uix
dev 4 months ago
parent 005239c732
commit 540c35e679

@ -172,6 +172,33 @@ browser: caret en heading → el panel muestra "Heading level"; el toggle
refleja el nivel actual; el click convierte bidireccional (H1→H3→H2) y
el toggle se actualiza. `npm run check` 0 errors.
## Rework del editor Words (2026-05-29 #2 — redirect del usuario)
El usuario redirigió a un nuevo bloque de trabajo sobre el editor. Plan
en 3 pasos (acordado: overlays a eidos POR FASES, con revisión entre
cada una):
- **Paso 1 — gutter izquierdo (✅ hecho):** quitado el botón "+" del
gutter; "insertar bloque" integrado en el menú del drag handle como
"Insert below" (vía `act('insertBelow')`). Gutter convertido en banda
silver estrecha: `--_words-gutter-size` 3.5rem→2.5rem, fondo
`--words-rail-bg` #ededed (muy suave) + borde derecho 1px
`--words-rail-border` #cfcfcf (un poco más fuerte), pintado como
`linear-gradient` en `[data-words-content]` (solo background, no toca
el contenteditable). Tokens del recipe actualizados + `generate:eidos-css`.
El inserter de SEAM (`words-block-inserter.svelte`, el "+" entre
bloques) NO se tocó — es otro overlay; preguntar si también debe irse.
- **Paso 2 — props de bloque (#3, pendiente):** añadir `color` (texto)
a `BlockVisual` + per-type subsets + `visualToStyle` + validador; y
sección común (margen/espaciado/fondo/color-texto) en el block panel.
Construir esos color controls con **eidos ColorPicker** y migrar los
`<input type=color>` nativos existentes (foreground/background marks +
panel de imagen) a ColorPicker. (Cubre #3 + la parte "color pickers →
eidos" de #4-fase1.)
- **Paso 3 — menú del drag → eidos (#4-fase1 resto) + #4-fase2:**
migrar el menú del block-handle a eidos Menu/Popover; luego link
editor / find / status a Popover+Tooltip.
## Pendiente para mañana
### P2 — Heredados

@ -254,10 +254,18 @@
return () => document.removeEventListener('click', onclick);
});
function act(command: 'moveUp' | 'moveDown' | 'duplicate' | 'delete') {
function act(command: 'insertBelow' | 'moveUp' | 'moveDown' | 'duplicate' | 'delete') {
const idx = hoverBlockIndex;
if (idx === undefined) return;
if (command === 'moveUp') {
if (command === 'insertBelow') {
// Canon Notion: "+ here adds a line below". The caret follows
// so the user can type right away.
snippet.applyCommand({
type: 'insertBlock',
blockIndex: idx + 1,
block: { type: 'paragraph', children: [{ type: 'text', text: '' }] }
});
} else if (command === 'moveUp') {
snippet.applyCommand({ type: 'moveBlock', blockIndex: idx, direction: 'up' });
} else if (command === 'moveDown') {
snippet.applyCommand({ type: 'moveBlock', blockIndex: idx, direction: 'down' });
@ -271,23 +279,6 @@
position = null;
}
/**
* "+" affordance — inserts a fresh empty paragraph immediately
* AFTER the hovered block (canon Notion behavior: "+ here adds a
* line below"). The caret follows so the user can type right
* away. Mousedown is `preventDefault`ed so the click doesn't blur
* the editor.
*/
function insertBelow() {
const idx = hoverBlockIndex;
if (idx === undefined) return;
snippet.applyCommand({
type: 'insertBlock',
blockIndex: idx + 1,
block: { type: 'paragraph', children: [{ type: 'text', text: '' }] }
});
}
function ondragstart(e: DragEvent) {
const idx = hoverBlockIndex;
if (idx === undefined) {
@ -364,16 +355,6 @@
style="top: {position.top}px; left: {position.left}px;"
role="presentation"
>
<button
type="button"
data-words-block-inserter-button
title="Insert paragraph below"
aria-label="Insert paragraph below"
onclick={insertBelow}
onmousedown={(e) => e.preventDefault()}
>
<Plus size="xs" decorative />
</button>
<button
type="button"
data-words-block-handle
@ -402,6 +383,11 @@
role="menu"
style="top: {position.top + 28}px; left: {position.left + 28}px;"
>
<button type="button" role="menuitem" onclick={() => act('insertBelow')}>
<Plus size="xs" decorative />
<span>Insert below</span>
</button>
<div data-words-block-handle-menu-divider aria-hidden="true"></div>
<button
type="button"
role="menuitem"

@ -1741,6 +1741,11 @@
}
[data-words-content] {
/* Narrowed left margin band ("estrechar" — was 3.5rem). Painted
* below as a soft silver strip with a 1px, slightly-stronger-silver
* right border (fixed-tone notebook margin). The drag handle lives
* inside this band on hover. */
--_words-gutter-size: 2.5rem;
/* Starting baseline keeps the editor at a sensible height when
* empty; max-block-size caps growth so long documents scroll
* INSIDE the content area instead of pushing the page. */
@ -1754,9 +1759,21 @@
* width by overlapping icons over text. Right side keeps the
* regular px so the editor still feels symmetric where there are
* no controls. */
padding-inline-start: var(--_words-gutter-size, 3.5rem);
padding-inline-start: var(--_words-gutter-size);
padding-inline-end: var(--_words-content-px);
color: var(--words-content-color);
/* Soft silver left margin band + 1px stronger-silver right border,
* painted in the reserved gutter zone. Background only — does not
* disturb the contenteditable text/selection. */
background-image: linear-gradient(
to right,
var(--words-rail-bg) 0,
var(--words-rail-bg) calc(var(--_words-gutter-size) - 1px),
var(--words-rail-border) calc(var(--_words-gutter-size) - 1px),
var(--words-rail-border) var(--_words-gutter-size),
transparent var(--_words-gutter-size)
);
background-repeat: no-repeat;
outline: none;
overflow-wrap: anywhere;
white-space: pre-wrap;

@ -2282,8 +2282,8 @@
--words-content-max-block-size-sm: 50vh;
--words-content-max-block-size-md: 60vh;
--words-content-max-block-size-lg: 70vh;
--words-rail-bg: #d4d4d4;
--words-rail-border: #9a9a9a;
--words-rail-bg: #ededed;
--words-rail-border: #cfcfcf;
--words-selection-color: var(--color-content-primary);
--words-block-gap: var(--space-2);
--words-heading-color: var(--color-content-primary);

@ -2718,11 +2718,12 @@ export const THEME_BASE_RECIPE_TOKENS = {
'content-max-block-size-sm': '50vh',
'content-max-block-size-md': '60vh',
'content-max-block-size-lg': '70vh',
// Flat silver gutter rail — fixed tone, intentionally NOT
// theme-aware. The rail is meant to look like the physical
// margin of a notebook regardless of the editor theme.
'rail-bg': '#d4d4d4',
'rail-border': '#9a9a9a',
// Soft silver gutter band — fixed tone, intentionally NOT
// theme-aware. Looks like the physical margin of a notebook
// regardless of the editor theme. `rail-bg` is a very soft
// silver; `rail-border` is a touch stronger for the 1px edge.
'rail-bg': '#ededed',
'rail-border': '#cfcfcf',
'selection-color': 'var(--color-content-primary)',
'block-gap': 'var(--space-2)',
'heading-color': 'var(--color-content-primary)',

Loading…
Cancel
Save

Powered by TurnKey Linux.