refactor(morfo): align 22 events with book family doctrine (Capa 2)

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) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent a74f26728c
commit 103c49a7ab

@ -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'

@ -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'

@ -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: [
{

@ -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'

@ -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'
}

@ -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'
}

@ -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'
}

@ -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'
}
}

@ -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'
}
}

@ -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' }
}

@ -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);
}

Loading…
Cancel
Save

Powered by TurnKey Linux.