From 103c49a7ab6826edb6583ea406a4985223220525 Mon Sep 17 00:00:00 2001 From: dev Date: Mon, 25 May 2026 17:12:46 +0200 Subject: [PATCH] refactor(morfo): align 22 events with book family doctrine (Capa 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves family-level misclassifications across 9 morfos. Each cluster maps a doctrinally wrong family to the book's literal prescription: CLUSTER 4 — announce (cap. 24 §5): - commit-announce-polite (commit.submit) → signal-announce (signal.announce + neutral) - commit-announce-assertive (commit.submit) → signal-alert (signal.alert + threat) Announcing is the canonical signal verb. Was misclassified as commit. CLUSTER 5 — checkbox (cap. 22 §10, option B per user): - commit-check (commit.select) → commit-toggle-check (commit.toggle + affirm) - commit-uncheck (commit.remove) → commit-toggle-uncheck (commit.toggle + neutral) Book literal: "Toggle: contact.press → commit.toggle + affirm". Two events retained (option B) so check/uncheck carry different intents; same canonical verb `toggle` per book. CLUSTER 1 — tree-view + tree-grid (cap. 26): - shift-expand (shift.navigate) → emerge-expand (emerge.expand) - shift-collapse (shift.navigate) → emerge-collapse (emerge.collapse) Book literal: "emerge.expand — una región crece y revela contenido". Tree node expansion reveals content; it's not a context shift. CLUSTER 3 — drag-drop (cap. 25 §4): - commit-drag-start (commit.submit) → handle-pick (handle.pick) - commit-drop (commit.submit) → handle-drop (handle.drop) - commit-cancel verb 'reset' → verb 'cancel' (was inconsistent with name) Book literal pick → carry → drop. The commit (reorder/delete/move) lives downstream in whichever morfo owns the moved entity, not on drag-drop. CLUSTER 6 — table (cap. 23, 26): - shift-sort (shift.navigate) → commit-set-sort (commit.set + neutral) Sort sets a criterion; the reorder is consequence. Not regime change. - shift-expand-row (shift.navigate) → emerge-expand-row (emerge.expand) Same argument as tree. CLUSTER 2 — virtual-list + virtual-grid (cap. 25, 27, 23): - shift-scroll, shift-scroll-row, shift-scroll-column (shift.navigate) → handle-scroll, handle-scroll-row, handle-scroll-column (handle.scroll) User-initiated scroll = direct manipulation of viewport. `scroll` extends the book's literal handle verb list per Cap 8 §1. - shift-scroll-to-index, shift-scroll-to-cell (shift.navigate) → shift-navigate-to-index, shift-navigate-to-cell (shift.navigate) Programmatic scroll = the SYSTEM moves the viewport, not the user. Still shift.navigate, but name now surfaces family+verb canonical shape. - shift-resize (shift.navigate) → commit-set-resize (commit.set + neutral) System applies a new container/data size. Useful for "items loaded" cues. CLUSTER 7 — clipboard (cap. 23, user directive): - commit-copy (commit.submit + affirm) → commit-save-copy (commit.save + affirm) Value persisted to clipboard = save per book. - commit-copy-error (commit.submit + threat) → commit-fail-copy (commit.fail + risk) Failure is fail + risk (correctable, not threat which is exigent). - shift-reset (shift.navigate) → REMOVED Timer-driven internal flag flip; no subscriber; not a perceptive event per user directive ("eliminar si no es perceptivo"). The visible label revert is signalled by `data-copied` attribute. Verification: - `npm run morfo:vocabulary`: warns 57 → 35 (-22), EXIT 0 - `npx vitest src/uix/sema src/uix/morfo`: 195/195 pass - checkbox provider tests: 4/4 pass - `npm run check`: no new errors from these changes Co-Authored-By: Claude Opus 4.7 (1M context) --- src/uix/morfo/components/announce.ts | 18 ++++++--- src/uix/morfo/components/checkbox.ts | 16 +++++--- src/uix/morfo/components/clipboard.ts | 38 ++++++++++--------- src/uix/morfo/components/drag-drop.ts | 27 ++++++++----- src/uix/morfo/components/table.ts | 27 +++++++++---- src/uix/morfo/components/tree-grid.ts | 16 +++++--- src/uix/morfo/components/tree-view.ts | 21 ++++++---- src/uix/morfo/components/virtual-grid.ts | 34 ++++++++++------- src/uix/morfo/components/virtual-list.ts | 32 +++++++++++----- src/uix/sema/components/checkbox.ts | 22 ++++++----- .../checkbox/checkbox-provider.svelte.ts | 6 +-- 11 files changed, 162 insertions(+), 95 deletions(-) diff --git a/src/uix/morfo/components/announce.ts b/src/uix/morfo/components/announce.ts index 8db6ba522..b68183565 100644 --- a/src/uix/morfo/components/announce.ts +++ b/src/uix/morfo/components/announce.ts @@ -11,10 +11,14 @@ export const announceMorfo = { events: [ { // Polite message posted (non-critical info — saved, fetched, queued). - name: 'commit-announce-polite', + // Per book cap. 24 §5.1 literal: `signal.announce + neutral` — + // "esto existe, sin urgencia." Announcing is the canonical signal + // verb. Was misclassified as commit.submit (commit fixes state; + // announce just orients attention). + name: 'signal-announce', semantic: { - family: 'commit', - verb: 'submit', + family: 'signal', + verb: 'announce', target: v.partRef('provider'), intent: 'neutral', sequence: 'post' @@ -22,10 +26,12 @@ export const announceMorfo = { }, { // Assertive message posted (error or urgent notice — interrupts speech). - name: 'commit-announce-assertive', + // Per book cap. 24: `signal.alert + threat` — "atiende ahora." + // Was misclassified as commit.submit + threat. + name: 'signal-alert', semantic: { - family: 'commit', - verb: 'submit', + family: 'signal', + verb: 'alert', target: v.partRef('provider'), intent: 'threat', sequence: 'post' diff --git a/src/uix/morfo/components/checkbox.ts b/src/uix/morfo/components/checkbox.ts index 4bfcad8db..a133e4a2e 100644 --- a/src/uix/morfo/components/checkbox.ts +++ b/src/uix/morfo/components/checkbox.ts @@ -11,13 +11,15 @@ export const checkboxMorfo = { apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/', events: [ { - // Transition from unchecked → checked. Affirm intent: the user is + // Transition from unchecked → checked. Per book cap. 22 §10 + // literal: "Toggle: contact.press → commit.toggle + affirm — el + // press registra; el toggle fija." Affirm intent: the user is // confirming inclusion / acceptance / agreement. Sema pack maps // this to a brighter, slightly higher cue than uncheck. - name: 'commit-check', + name: 'commit-toggle-check', semantic: { family: 'commit', - verb: 'select', + verb: 'toggle', target: v.partRef('provider'), intent: 'affirm', sequence: 'pre' @@ -25,12 +27,14 @@ export const checkboxMorfo = { }, { // Transition from checked / indeterminate → unchecked. Neutral - // intent: revoking a selection has no evaluative load by default + // intent: revoking a state has no evaluative load by default // (apps that need destructive intent override per-instance). - name: 'commit-uncheck', + // Same canonical verb (commit.toggle) as check; the direction is + // communicated by `data-state`. + name: 'commit-toggle-uncheck', semantic: { family: 'commit', - verb: 'remove', + verb: 'toggle', target: v.partRef('provider'), intent: 'neutral', sequence: 'pre' diff --git a/src/uix/morfo/components/clipboard.ts b/src/uix/morfo/components/clipboard.ts index 5654cf922..4a209716e 100644 --- a/src/uix/morfo/components/clipboard.ts +++ b/src/uix/morfo/components/clipboard.ts @@ -12,37 +12,41 @@ export const clipboardMorfo = { }, events: [ { - // User clicked Trigger → writeText succeeded. - name: 'commit-copy', + // User clicked Trigger → writeText succeeded. The value was + // "saved" to the OS clipboard. Per book cap. 23 `commit.save` is + // "algo queda aplicado o guardado" — fits exactly. Misclassified + // before as `commit.submit` (submit is for form/process closure). + name: 'commit-save-copy', semantic: { family: 'commit', - verb: 'submit', + verb: 'save', target: v.partRef('trigger'), intent: 'affirm', sequence: 'post' } }, { - // writeText threw (permission denied, blur, etc.). - name: 'commit-copy-error', + // writeText threw (permission denied, blur, etc.). Per book + // cap. 23 `commit.fail`. Intent `risk` per cap. 10: "problema + // corregible" — the user can retry. Was wrongly `commit.submit + // + threat` (threat = "amenaza activa que exige acción + // inmediata" — overstated for a recoverable copy failure). + name: 'commit-fail-copy', semantic: { family: 'commit', - verb: 'submit', + verb: 'fail', target: v.partRef('trigger'), - intent: 'threat', - sequence: 'post' - } - }, - { - // The auto-reset timer fired and `copied` flipped back to false. - name: 'shift-reset', - semantic: { - family: 'shift', - verb: 'navigate', - target: v.partRef('provider'), + intent: 'risk', sequence: 'post' } } + // Note: the auto-reset timer that flips `copied` back to false + // after N ms used to fire a `shift-reset` event. Removed — that + // timer-driven flag flip is implementation bookkeeping, not a + // perceptive event the user attends to (the visible label change + // from "Copied" back to "Copy" is the perception, and it's + // signalled by the `data-copied` attribute the recipe styles + // against). ], parts: [ { diff --git a/src/uix/morfo/components/drag-drop.ts b/src/uix/morfo/components/drag-drop.ts index 433321763..5340ed13f 100644 --- a/src/uix/morfo/components/drag-drop.ts +++ b/src/uix/morfo/components/drag-drop.ts @@ -20,30 +20,39 @@ export const dragDropMorfo = { }, events: [ { - name: 'commit-drag-start', + // Per book cap. 25 §4 literal: "Pick — handle.pick — ¿he + // agarrado esto?". Start of direct manipulation. NOT a commit + // (commit is the consequence of the drop, fired by whichever + // morfo owns the moved/deleted entity). + name: 'handle-pick', semantic: { - family: 'commit', - verb: 'submit', + family: 'handle', + verb: 'pick', target: v.partRef('draggable'), - intent: 'neutral', sequence: 'post' } }, { - name: 'commit-drop', + // Per book cap. 25 §4 literal: "Drop — handle.drop — ¿qué + // ocurrió al soltar?". Just the release; what the release + // resolves (commit.reorder + affirm, commit.delete + loss, + // etc.) lives in the morfo that owns the target/entity. + name: 'handle-drop', semantic: { - family: 'commit', - verb: 'submit', + family: 'handle', + verb: 'drop', target: v.partRef('droppable'), - intent: 'fulfill', sequence: 'post' } }, { + // User cancelled the drag (Escape during carry). The operation + // reverts; nothing committed downstream. `commit.cancel` per + // book cap. 23. name: 'commit-cancel', semantic: { family: 'commit', - verb: 'reset', + verb: 'cancel', target: v.partRef('draggable'), intent: 'neutral', sequence: 'post' diff --git a/src/uix/morfo/components/table.ts b/src/uix/morfo/components/table.ts index 6f180467d..0c01a223d 100644 --- a/src/uix/morfo/components/table.ts +++ b/src/uix/morfo/components/table.ts @@ -13,12 +13,19 @@ export const tableMorfo = { }, events: [ { - // Column sort change. - name: 'shift-sort', + // Column sort change. User clicked a header → sets the sort + // criterion. Per book cap. 23, `commit.set` fixates a value + // ("algo queda aplicado"). The visual reorder is a consequence + // of the set, not the event itself. Misclassified before as + // `shift.navigate` (sorting doesn't change the operational + // régime — same view, same actions available, just different + // row order). + name: 'commit-set-sort', semantic: { - family: 'shift', - verb: 'navigate', + family: 'commit', + verb: 'set', target: v.partRef('column-header'), + intent: 'neutral', sequence: 'post' } }, @@ -34,11 +41,15 @@ export const tableMorfo = { } }, { - // Expand the row's detail panel. - name: 'shift-expand-row', + // Expand the row's detail panel. Per book cap. 26 literal: + // "emerge.expand — una región crece y revela contenido". Same + // argument as tree-grid/tree-view expansion: misclassified as + // shift.navigate (which is regime change), should be emerge + // (content revealing). + name: 'emerge-expand-row', semantic: { - family: 'shift', - verb: 'navigate', + family: 'emerge', + verb: 'expand', target: v.partRef('row'), sequence: 'post' } diff --git a/src/uix/morfo/components/tree-grid.ts b/src/uix/morfo/components/tree-grid.ts index 61b212dfb..da60522a8 100644 --- a/src/uix/morfo/components/tree-grid.ts +++ b/src/uix/morfo/components/tree-grid.ts @@ -24,19 +24,23 @@ export const treeGridMorfo = { } }, { - name: 'shift-expand', + // Per book cap. 26: `emerge.expand` — "una región crece y + // revela contenido". Expanding a row reveals nested rows, + // which is exactly emerge per the book. Was misclassified + // as shift.navigate. + name: 'emerge-expand', semantic: { - family: 'shift', - verb: 'navigate', + family: 'emerge', + verb: 'expand', target: v.partRef('row'), sequence: 'post' } }, { - name: 'shift-collapse', + name: 'emerge-collapse', semantic: { - family: 'shift', - verb: 'navigate', + family: 'emerge', + verb: 'collapse', target: v.partRef('row'), sequence: 'post' } diff --git a/src/uix/morfo/components/tree-view.ts b/src/uix/morfo/components/tree-view.ts index b97988a34..4d81683f5 100644 --- a/src/uix/morfo/components/tree-view.ts +++ b/src/uix/morfo/components/tree-view.ts @@ -21,20 +21,27 @@ export const treeViewMorfo = { } }, { - // Expand a node — visible commit (the user wants to see children). - name: 'shift-expand', + // Expand a node — content contained in the tree becomes part of + // the visible field. Per book cap. 26 literal: "emerge.expand — + // una región crece y revela contenido (accordion, details, + // sección desplegable)." Tree expand fits exactly. Was + // misclassified as `shift.navigate` (which is for context/regime + // change, not content revealing). + name: 'emerge-expand', semantic: { - family: 'shift', - verb: 'navigate', + family: 'emerge', + verb: 'expand', target: v.partRef('item'), sequence: 'post' } }, { - name: 'shift-collapse', + // Collapse a node — per book cap. 26: "emerge.collapse — una + // región se contrae." + name: 'emerge-collapse', semantic: { - family: 'shift', - verb: 'navigate', + family: 'emerge', + verb: 'collapse', target: v.partRef('item'), sequence: 'post' } diff --git a/src/uix/morfo/components/virtual-grid.ts b/src/uix/morfo/components/virtual-grid.ts index 1862891a0..09df05a33 100644 --- a/src/uix/morfo/components/virtual-grid.ts +++ b/src/uix/morfo/components/virtual-grid.ts @@ -10,28 +10,34 @@ export const virtualGridMorfo = { }, events: [ { - // Visible rectangle moved on the row axis. - name: 'shift-scroll-row', + // User-initiated viewport scroll on the row axis. Per book + // cap. 25, handle is direct manipulation of an object — + // scrolling the viewport rows fits. `scroll` extends book canon + // per Cap 8 §1. Was misclassified as shift.navigate. + name: 'handle-scroll-row', semantic: { - family: 'shift', - verb: 'navigate', + family: 'handle', + verb: 'scroll', target: v.partRef('viewport'), sequence: 'post' } }, { - // Visible rectangle moved on the column axis. - name: 'shift-scroll-column', + // User-initiated viewport scroll on the column axis. Same as + // above, axis split for sema cascade differentiation. + name: 'handle-scroll-column', semantic: { - family: 'shift', - verb: 'navigate', + family: 'handle', + verb: 'scroll', target: v.partRef('viewport'), sequence: 'post' } }, { - // `scrollToCell` was called imperatively → rectangle jumps in 2D. - name: 'shift-scroll-to-cell', + // `scrollToCell` called imperatively → the SYSTEM moves the + // viewport (not handle, which requires direct user control). + // `shift.navigate` per book cap. 27 §5. + name: 'shift-navigate-to-cell', semantic: { family: 'shift', verb: 'navigate', @@ -41,11 +47,13 @@ export const virtualGridMorfo = { }, { // `rowCount` or `columnCount` changed (data grew or shrank). - name: 'shift-resize', + // The system applied a new size. `commit.set` per book cap. 23. + name: 'commit-set-resize', semantic: { - family: 'shift', - verb: 'navigate', + family: 'commit', + verb: 'set', target: v.partRef('provider'), + intent: 'neutral', sequence: 'post' } } diff --git a/src/uix/morfo/components/virtual-list.ts b/src/uix/morfo/components/virtual-list.ts index 69516fa5e..a7e86c074 100644 --- a/src/uix/morfo/components/virtual-list.ts +++ b/src/uix/morfo/components/virtual-list.ts @@ -10,18 +10,26 @@ export const virtualListMorfo = { }, events: [ { - // Visible range moved (consumer scrolled the viewport). - name: 'shift-scroll', + // User-initiated viewport scroll (wheel, scrollbar, touch). + // Per book cap. 25, handle is "relación continua entre el + // usuario y un objeto" — scrolling a viewport is direct + // manipulation of that surface. `scroll` extends the literal + // book canon (pick/carry/drop/drag/resize/rotate/reorder) per + // Cap 8 §1 ("Algunas diferencias pertenecen al verbo"), fitting + // handle's spirit. Was misclassified as shift.navigate. + name: 'handle-scroll', semantic: { - family: 'shift', - verb: 'navigate', + family: 'handle', + verb: 'scroll', target: v.partRef('viewport'), sequence: 'post' } }, { - // `scrollToIndex` was called imperatively → range jumps. - name: 'shift-scroll-to-index', + // `scrollToIndex` was called imperatively → the SYSTEM moves the + // viewport, not the user. Per book cap. 27 §5 `shift.navigate`: + // "estoy en otro lugar". Not handle (no direct user control). + name: 'shift-navigate-to-index', semantic: { family: 'shift', verb: 'navigate', @@ -31,12 +39,16 @@ export const virtualListMorfo = { }, { // `count` changed (data source grew or shrank) and totalSize was - // recomputed. Useful sema hook for "new items loaded" cues. - name: 'shift-resize', + // recomputed. The SYSTEM set a new size for the virtual list. + // `commit.set` per book cap. 23 ("algo queda aplicado"). Useful + // sema hook for "new items loaded" cues. Was misclassified as + // shift.navigate (no context change happened). + name: 'commit-set-resize', semantic: { - family: 'shift', - verb: 'navigate', + family: 'commit', + verb: 'set', target: v.partRef('provider'), + intent: 'neutral', sequence: 'post' } } diff --git a/src/uix/sema/components/checkbox.ts b/src/uix/sema/components/checkbox.ts index b4fa1aa60..f29ba3bae 100644 --- a/src/uix/sema/components/checkbox.ts +++ b/src/uix/sema/components/checkbox.ts @@ -13,14 +13,16 @@ import type { Sema } from '../sema-map'; * recipe also paints the visual transition synchronously so the user * always knows what happened. * - * Strategy: - * - `commit-check` → family `commit`, intent `affirm` — the affirm - * intent.deltas (capa 2) already contribute a slight positive - * character (higher pitch, brighter timbre). We add minimal sound - * gain + tap haptic. - * - `commit-uncheck` → family `commit`, intent `neutral` — neutral - * stays at the family base; no positive lift. Sound is even softer - * (revoking a state shouldn't compete with the affirmative chime). + * Strategy (book cap. 22 §10 — both directions share `commit.toggle` verb; + * intent differentiates the perceptual signature): + * - `commit-toggle-check` → family `commit`, verb `toggle`, intent + * `affirm` — the affirm intent.deltas (capa 2) already contribute a + * slight positive character (higher pitch, brighter timbre). We add + * minimal sound gain + tap haptic. + * - `commit-toggle-uncheck` → family `commit`, verb `toggle`, intent + * `neutral` — neutral stays at the family base; no positive lift. + * Sound is even softer (revoking a state shouldn't compete with the + * affirmative chime). * - NEVER override `pitch` / `gain` / `contour` from intent.deltas — * doing so collapses the per-intent perceptual difference between * affirm and neutral. @@ -33,12 +35,12 @@ export const checkboxSema: Sema = { name: 'checkbox', cascade: [ { - selector: onProvider({ eventName: 'commit-check' }), + selector: onProvider({ eventName: 'commit-toggle-check' }), sound: soundTuning('form.commit.soft'), haptic: { kind: 'tap' } }, { - selector: onProvider({ eventName: 'commit-uncheck' }), + selector: onProvider({ eventName: 'commit-toggle-uncheck' }), sound: soundTuning('form.commit.subtle'), haptic: { kind: 'tap' } } diff --git a/src/uix/soma/components/checkbox/checkbox-provider.svelte.ts b/src/uix/soma/components/checkbox/checkbox-provider.svelte.ts index 70073b630..169b0e92d 100644 --- a/src/uix/soma/components/checkbox/checkbox-provider.svelte.ts +++ b/src/uix/soma/components/checkbox/checkbox-provider.svelte.ts @@ -65,8 +65,8 @@ export class CheckboxProvider { invalid: () => this.opts.invalid.current }, events: { - 'commit-check': () => this.applyPending(), - 'commit-uncheck': () => this.applyPending() + 'commit-toggle-check': () => this.applyPending(), + 'commit-toggle-uncheck': () => this.applyPending() } }); this.runtimePart = this.runtime.part('provider', { @@ -144,7 +144,7 @@ export class CheckboxProvider { this._pendingChecked = next; const target = triggerEl ?? this.opts.ref?.current ?? null; - const eventName = next ? 'commit-check' : 'commit-uncheck'; + const eventName = next ? 'commit-toggle-check' : 'commit-toggle-uncheck'; void this.runtime.trigger(eventName, target ? { fallbackTarget: target } : undefined); }