astra
alpha-0.1-background
alpha-0.1-dir-prefs
alpha-0.1-sec-dom
menubar-v4-safe
active-uix
morfo-runtime
morfo-driven-soma
semantuix
glm-5
main
sium-v1.0
${ noResults }
714 Commits (08bef77f4bcf562358b2da505c09ab7319504494)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
08bef77f4b |
feat(words): image extension foundation — types, render, HTML+Markdown serializers (F3.1-F3.5)
Adds image as a first-class block node to the words engine. Phase F3
of the words rich-text editor. Ships the bottom half of the stack so
images can be authored in source (markdown / HTML / value JSON) and
roundtripped, but UX surfaces (toolbar action, slash command, paste/
drop) land in subsequent commits.
F3.1 — extensions/image/{types,factories,index}.ts:
- WordsImageBlock = { type: 'image', src, alt?, caption?, width?, height?, align?, status? }
- createImage(src, options) factory canonicalising defaults (align='center' stripped, etc.)
- WordsImageStatus = 'pending' | 'error' for upload lifecycle
- WORDS_IMAGE_ALIGNS + isWordsImageAlign predicate
F3.2 — engine/document.ts:
- WordsBlockType + WordsBlock union extended with 'image'
- Image types re-exported (backward-compat with the table-types pattern)
- getBlockText: image → alt text (captions excluded; image stays a
single atom in plain-text contexts)
F3.3 — engine/render.ts:
- Image branch renders <figure data-words-block=image contenteditable=false>
<img src alt width? height? loading=lazy draggable=false>
<figcaption>caption</figcaption?
- WordsRenderTag union gains 'figure' | 'img' | 'figcaption'
- data-words-image-align / data-words-image-status surface state
- renderBlockPlainText: image → alt
F3.4 — engine/serialize-html.ts:
- Out: <figure data-words-image-align?><img alt src width? height?><figcaption?></figure>
- In: 'figure' added to BLOCK_TAGS; <figure><img> elements parsed back
to ImageBlock (with caption from <figcaption>); bare <img> parses
to a block too
- htmlElementText helper added for caption text extraction
F3.5 — engine/serialize-markdown.ts:
- Out:  — escapes ], ( and ) in src, " in caption
- In: standalone line matching ^$ promotes to a
block image. Inline images mid-paragraph become text + no image
(lossy by design — keep the model tight; inline image is a separate
node type if added later)
Engine ripple — exhaustive switches updated:
- normalize.getInlineBlockText
- path.collectContainerChildren (image marked as terminal)
- selection.collectBlockText (image returns empty — opaque atom)
- operations:
- setBlock skips image (image is not convertible to inline-text blocks)
- setTextAlign skips image (no text align on image)
- deleteRange refuses if range crosses an image (atomic)
- insertParagraph on image inserts a fresh paragraph after it and
moves the caret
- blockTextAlign() returns undefined for image
- words-provider.currentTextAlign returns 'left' on image
149/149 tests pass in the full words soma scope. `npx tsc --noEmit`
clean for the touched area. Engine consumers compile unchanged.
Next: F3.6 insertImage operation + toolbar/slash command, F3.7 slash
menu /image entry, F3.8 paste/drop with onUploadImage callback, F3.9
eidos CSS, F3.10 imageExtension stub, F3.11 demo + verify.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
f25bf1558e |
demo(words): enrich status bar with char count + selection + dynamic link/commit (F4.5b)
Before: 'Optional editor · 64 words · none'
After: 'Optional · 64 words · 453 chars · none' (no link, no commit)
'Required · 64 words · 453 chars · range · https://...' (selection + link)
'Optional · 64 words · 453 chars · none · save' (after commit)
- Drops the redundant 'editor' suffix; the surface is already an editor.
- Adds char count from plainText.length — useful for size limits.
- Adds the existing selectionState (none/collapsed/range) inline.
- Splits link/commit into separate optional segments instead of
collapsing them into one slot.
Demo-only change in +page.svelte. The eidos Words.Status wrapper
still passes children through unchanged — enriching the eidos default
status snippet itself would require Words provider to expose
wordCount/charCount, which is a larger refactor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
5bd9f9fe4f |
fix(words): slash menu wider + description wraps to 2 lines instead of ellipsis (F4.5a)
Before:
- Menu width was 7× button-size (≈224px), so even short descriptions
clipped: "Large section headin…", "Blockquote callou…".
- Description was single-line nowrap+ellipsis, doubling the clipping.
After:
- Menu width 10× button-size (≈320px) — enough room for the longest
default description ("Large section heading") without truncation.
- Description switches to a 2-line line-clamp with line-height 1.3,
so longer custom descriptions still degrade gracefully instead of
silently disappearing past the first line.
Label still nowrap+ellipsis (the bold first line is supposed to be a
short title; if it doesn't fit, that's an author issue worth a visual
cue).
CSS-only in words.css. Visible immediately on the demo's SlashMenu.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
eb6a019b5a |
fix(words): vertical rhythm — heading top margin, blockquote distinctive, code block generous padding (F4.3)
Three small typography lifts that compound into a noticeably cleaner document rhythm: - Headings (h1/h2/h3) gain 1.5× block-gap top margin when NOT first child. Opens visible breathing room between a body block and the next section. Skipped on first-child so the editor opens flush against the toolbar. - Blockquote: was just a left border + recolored text. Now adds vertical padding, italic, a 1.5× border-inline-start width, and a 4% accent background tint. Reads unambiguously as a callout rather than indistinguishable from a regular paragraph. - Pre (code block): padding bumped from 1× quote-px to 1.25× and the border mixed with 60% accent-border so the block is visually weightier and stands apart from the body bg. CSS-only change in words.css. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
c09f7b0d87 |
fix(words): table accent tone softer, hover cells, stronger header divider (F4.2)
Before: - tone='accent' painted at 14% solid + full accent-text recolor — the cell read as "broken / special-cased random" rather than emphasised. - striped used 70%-transparent overlay on toolbar-bg ≈ invisible against the editor bg. - no hover state on cells — users had no signal that cells are interactive (clickable to edit). - header row used the same single-width border as body cells, so the divider got lost when striped or accent kicked in. After: - tone='accent' at 8% solid, text color inherited from default — the cell still draws the eye without shouting. - striped uses solid toolbar-bg (no transparency) — actual zebra. - subtle hover (5% accent overlay) on every non-header cell, with a 120ms ease transition. - header row gets a 2× bottom border so the head/body divider is unambiguous regardless of striping/accent. CSS-only change in words.css, no markup or logic touched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
3cc171f747 |
fix(words): toolbar horizontal-scroll instead of multi-line wrap on mobile (F4.1)
Before: at narrow viewports the toolbar wrapped to 2-3 lines with orphan icons in the last row (e.g. at 375px: 5 + 4 + 1 icons = 3 rows). Awkward visually and wasteful of vertical space. After: single-row horizontal scroll with a soft trailing fade (mask-image gradient) hinting at hidden overflow. Same UX as Google Docs / Word / Notion mobile toolbars. All commands stay reachable without changing the markup or the provider logic. Vertical-orientation toolbar opts out of the horizontal-scroll mask (it already lays out top-to-bottom and the mask would clip the trailing icons in the visible direction). Verified: - 375px (mobile) — single row, fade visible on right edge. - 900px (laptop) — single row, no overflow, fade harmless. - Vertical orientation — column layout intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
1043f1de4a |
refactor(words): extract 8 pure table helpers to extensions/table/utils.ts (F2.3h.1)
Moves the table-pure helpers (no engine selection/normalize/marks dependencies) from `engine/operations.ts` to `extensions/table/utils.ts`: - createTableGrid (2×2 default, clamped 1..12 × 1..8) - tableCellPathFromContainer (3-tuple path resolver) - tableOptions / tableCellOptions (option-bag readers) - tableColumnCount (max-cells-across-rows, min 1) - createEmptyTableRow (N-cell row factory) - insertTableCellInRow / deleteTableCellFromRow (cell ops over a row) - WordsTableCellPath type alias The 6 *mixed* helpers (currentTableCellPath, selectTableCell, setTableCellTextAlign, updateCurrentTableCell, updateCurrentTable, preserveTableFormattingSelection) still live in operations.ts — they read private engine state and can only move once F2.3i full lands a visitor pattern that injects those engine APIs as parameters. clampInteger stays duplicated locally in utils.ts (9 LoC, generic numeric helper) instead of being exported from the engine, to keep the extension's surface tight. If a third caller emerges, promote to a shared location. 149/149 tests pass in the full words soma scope. TS check clean in extensions+engine area. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
bde6728590 |
refactor(words): table extension stub — name + nodeTypes + factories + commandNames (F2.3i)
Publishes `tableExtension: WordsExtension` in `extensions/table/table-extension.ts` with the three static facets we can ship without touching the engine: - `nodeTypes`: ['table', 'table-row', 'table-cell'] - `factories`: typed constructors for the three node types - `commandNames`: the 12 public table commands (insertTable, deleteTableRow, toggleTableHeaderRow, setTableCellTone, …) mapped to internal operation types (1:1 today). The dynamic hooks (`render`, `normalize`, `serialize`, `path`, `keyboard`, `commands`, `events`) are intentionally absent — F2.3f-h port each one incrementally. The engine does NOT yet consume the registry; the stub publishes the shape the engine will respect once F2.3i full lands. 6 smoke tests verify the public shape (nodeTypes, factories produce valid nodes, registry registers cleanly + finds by every nodeType, all 12 command names exposed, hooks intentionally absent, default 1×1 table). 102/102 in extensions + engine scope, 149/149 in full words soma scope. CONTINUE.md updated to reflect F2.3i-stub done + next-session order (F2.3h first, then F2.3f/g — F2.3i full wire-up follows F2.3h). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
54cd1744c7 |
docs(CONTINUE): record F2.3a-e completion + remaining sub-plan
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
d839273536 |
refactor(words): extract table render to extensions/table (F2.3e)
Fifth atomic checkpoint of F2.3. Moves the table-specific DOM render
helpers from engine/render.ts to extensions/table/render.ts.
Moved:
- renderTable(block, path, helpers) — replaces the table branch in
renderWordsBlock + internal renderWordsTableRow + renderWordsTableCell
- renderTablePlainText(block, helpers) — replaces the table branch in
renderBlockPlainText
The extension defines structural render-element types
(TableRenderElement, TableRenderText, TableRenderNode) assignment-
compatible with the engine's WordsRenderElement/Node. The engine
passes its constants (WORDS_PATH_ATTR, WORDS_NODE_ATTR) + helpers
(encodePath, renderInlines, getInlineText) via a TableRenderHelpers
object, keeping the extension independent of engine/dom.ts.
The engine's renderWordsBlock dispatcher branch for `block.type === 'table'`
now delegates to `renderTable(block, path, { ... })` in a single call.
~50 LoC of helper functions removed from the engine.
Extension barrel updated to expose:
- renderTable / renderTablePlainText
- TableRenderElement / TableRenderNode / TableRenderText / TableRenderHelpers / RenderPath types
Verification:
- 143/143 tests pass in src/uix/soma/components/words
- 0 new TypeScript errors
F2.3 progress: a + b + c + d + e done. Remaining: f (path), g
(normalize), h (operations — largest at ~400-500 LoC), i (registry
wire-up), j (verify). Operations remains the heaviest piece — likely
its own dedicated session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
5797e69b88 |
refactor(words): extract table HTML serializer (F2.3c)
Fourth atomic checkpoint of F2.3. Moves the HTML pipe-table serialize + parse helpers from engine/serialize-html.ts to extensions/table/serialize-html.ts. Moved (write side): - serializeTableHtml(block, serializeInlines, escapeHtmlAttr) - serializeTableCellHtml (internal) - tableAttrsHtml (internal) - tableCellAttrsHtml (internal) Moved (parse side): - parseTableHtml(node, parseInlines, textAlignFromStyle) - collectTableRows (internal) - htmlRowToWords (internal) - htmlCellToWords (internal) - tableCellVerticalAlignFromNode (internal) - tableCellToneFromNode (internal) Mirrors the F2.3d pattern: extension consumes engine inline serializer + HTML attribute escaper as callback parameters to avoid circular dependencies. The extension defines its own minimal `WordsTableHtmlElement` / `WordsTableHtmlChild` types structurally compatible with the engine's internal HtmlElementNode/HtmlNode — engine passes its own parsed nodes via a safe `as unknown as` cast at the call site (commented). engine/serialize-html.ts now contains only the dispatch call to the extension's serializeTableHtml / parseTableHtml. ~150 LoC of helper code removed from the engine; same LoC + clearer separation in extension. Extension barrel updated to expose: - serializeTableHtml / parseTableHtml - WordsTableHtmlElement / WordsTableHtmlChild types Verification: - 143/143 tests pass in src/uix/soma/components/words - 0 new TypeScript errors F2.3 progress: a + b + c + d done. Remaining: e (render), f (path), g (normalize), h (operations — largest), i (registry wire-up), j (verify). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
d6f0af4846 |
refactor(words): extract table markdown serializer (F2.3d)
Third atomic checkpoint of F2.3. Moves the markdown pipe-table serialize + parse helpers from engine/serialize-markdown.ts to extensions/table/serialize-markdown.ts: Moved: - serializeTableMarkdown(block, renderInline) - tableMarkdownRow (internal) - escapeMarkdownTableCell (internal) - collectTable(lines, startIndex) - parseTableLine (internal) - isTableSeparator (internal) - NEW: buildTableFromMarkdownRows(rows, parseInline) — convenience for the engine to convert collectTable's raw output into a WordsTableBlock, keeping the extension free from the engine's inline parser implementation. The extension takes inline serialize/parse functions as callback parameters (`renderInline`, `parseInline`) instead of importing the engine's inline serializers. Avoids circular dependency — the extension imports engine for shared types (WordsInline, WordsTableRow) and factories only, never for runtime function dependencies. engine/serialize-markdown.ts now imports the 3 extension functions and calls them from `serializeBlockMarkdown` (write side) and `parseWordsMarkdown` (parse side). Internal `collectTable`, `parseTableLine`, `isTableSeparator` removed. Extension barrel updated to expose the new functions: - serializeTableMarkdown - parseTableMarkdownRows (collectTable re-exported under clearer name) - buildTableFromMarkdownRows Verification: - 143/143 tests pass in src/uix/soma/components/words - 0 new TypeScript errors F2.3 progress: a + b + d done. Pending: c (serialize-html), e (render), f (path), g (normalize), h (operations — largest), i (registry wire-up), j (verify). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
a0afa44da4 |
refactor(words): extract table factories to extensions/table (F2.3b)
Second atomic checkpoint of F2.3. Moves table factory functions (createTable, createTableRow, createTableCell), predicate functions (isWordsTableCellVerticalAlign, isWordsTableCellTone), and value-set constants (WORDS_TABLE_CELL_VERTICAL_ALIGNS, WORDS_TABLE_CELL_TONES) from engine/document.ts to extensions/table/factories.ts. engine/document.ts now re-exports the moved symbols so existing imports keep working unchanged. The factories module imports its back-references (createText, WordsInline) from engine/document — the extension may depend on the engine core but the engine core only depends on the extension via re-export indirection (no functional dependency). extensions/table/index.ts barrel updated to expose types (F2.3a) + factories (F2.3b) as the table extension's public surface. Document header notes which pieces have been migrated and which are still pending (serializers, render, path, normalize, operations, registry wire-up). Verification: - 143/143 tests pass in src/uix/soma/components/words - 0 new TypeScript errors - No engine behaviour change (symbol relocation only) F2.3 progress: a + b done. Pending: c (serialize-html), d (serialize-markdown), e (render), f (path), g (normalize), h (operations — largest), i (registry wire-up), j (verify). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
a82c422b05 |
docs(CONTINUE): record F2 sprint state and F2.3 sub-plan
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
088d31ec5d |
refactor(words): extract table types to extensions/table/types.ts (F2.3a)
First atomic checkpoint of F2.3 (table migration to extension). Moves
the type declarations only — interfaces and unions for the table node
tree (WordsTableBlock, WordsTableRow, WordsTableCell, WordsTableOptions,
WordsTableCellOptions, WordsTableCellVerticalAlign, WordsTableCellTone)
go from engine/document.ts to extensions/table/types.ts.
engine/document.ts re-exports the moved types via `export type { ... }
from '../extensions/table/types'` so every existing consumer keeps
compiling without changes. Internal usages within document.ts use a
separate `import type` to keep the types in local scope for factory
function signatures.
The rest of F2.3 (factories + serializers + render + normalize + path
+ operations + registry wire-up) requires deeper dependency analysis
across the engine's ~990 LoC of table code spread over 10 files. That
work is deferred to a dedicated session to avoid mid-refactor breakage.
Verification:
- 143/143 tests pass in src/uix/soma/components/words
- 0 new TypeScript errors
- No engine behaviour change (types-only move)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
1805b0817d |
feat(words): extension system skeleton (F2.1 + F2.2)
Phase 1 of the words rich-text editor extension system. Skeleton +
contract only — engine is NOT yet wired to consult the registry.
That happens in F2.3 (migrate table) and F2.4 (migrate code-block).
New module `src/uix/soma/components/words/extensions/`:
- extension-types.ts: WordsExtension interface + sub-types. Every
hook is optional. Hooks cover document semantics (nodeTypes,
factories), normalization, commands (reducer pattern),
rendering, serialization (HTML + Markdown, in + out), path
navigation, keyboard, sema events, dispose. Each hook
documented inline with rationale.
- extension-registry.ts: createWordsExtensionRegistry() returns
the runtime container. API:
register / unregister / dispose
extensions / findByNodeType / findByCommandName
getRender / getNormalize / getCommand / getSerialize / getPath / getKeyboard
allEvents / allCommandNames / allNodeTypes
tryDeserializeHtml / tryDeserializeMarkdown
Duplicate-name and duplicate-nodeType detection at register-time
with rollback-safe semantics. dispose() tears down extensions in
reverse-registration order.
- extension-registry.test.ts: 14 tests covering register, conflict
rejection (duplicate name, duplicate nodeType, duplicate command
name), all-or-nothing rollback, sema event aggregation, dispose
ordering, unregister, getRender/getNormalize/getCommand walks,
tryDeserializeHtml walk-until-non-null.
- index.ts: barrel.
Verification:
- 14/14 registry tests pass
- 82/82 existing engine tests still pass (no engine touched)
- Total 96/96 in src/uix/soma/components/words
F2.1 (audit) + F2.2 (skeleton) done. F2.3 (migrate table) and
F2.4 (migrate code-block) are separate sessions per the planned
rollback-safe sub-task structure — each one will move the
~990 LoC of table and ~790 LoC of code-block from the engine
core into extensions/{table,code-block}/* and wire them into
the registry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
85b8a56ab0 |
feat(words): add toolbarLayout grouped|inline (responsive)
New optional prop on <Words> for the toolbar layout strategy:
- 'grouped' (default): each family (text/block/list/align/insert/table/
link/tools) becomes a single trigger button that opens a popover with
its commands. Compact, recommended for narrow viewports.
- 'inline': every command in every family renders flat in the toolbar,
with the toolbar group separators marking family boundaries. High
discoverability, recommended for wide editors.
Tools that own their own panel UI (LinkEditor, FindReplace,
HeadingPicker, CodeLanguagePicker) remain popovers/dropdowns in both
layouts by design — they are not "inlineable".
Accepts ResponsiveProp<WordsToolbarLayout> for breakpoint-aware
switching, e.g. `toolbarLayout={{ base: 'grouped', md: 'inline' }}`.
Implementation:
- types.ts: new WordsToolbarLayout = 'grouped' | 'inline'; new
toolbarLayout?: ResponsiveProp<WordsToolbarLayout> on WordsProps.
- index.ts: export WordsToolbarLayout type.
- words.svelte: resolved via eidos.resolve(toolbarLayout, 'grouped').
New helper flattenFamilyToLeafItems(family, customItems) returns
the default leaf items per family for the inline expansion.
Render refactored: leaf rendering extracted to a Svelte snippet
reusable by both layouts, so the conditional only chooses between
<ToolbarFamilyMenu> (grouped) and {#each leafs as leaf}{@render
leafItem(leaf, snippetProps)}{/each} (inline).
- demo: toolbarLayout state added + wired to <Words>.
Verified visually in browser (preview + screenshot):
- grouped: 9 family menu triggers + 1 sibling tool = 10 toolbar buttons
- inline: 0 family triggers + 28 command buttons + 5 tools = 33 buttons
Tests: 161/161 pass in src/uix/{contracts,soma/components/words}.
TypeScript: 0 new errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
58c2c0babf |
fix(words): close audit P0 + quiet focus event (FASE 1)
Cierre de los hallazgos P0/P1 del audit Codex sobre Words + fix del
evento contact-focus molesto reportado por el usuario.
F1.1 — Fix evento foco (UX):
El handler onfocus disparaba `runtime.trigger('contact-focus')` cada vez
que el editor recibía foco, generando sound + haptic + visual stamp en
cada re-focus desde popovers internos (bubble menu / link editor / find
panel / slash menu / toolbar dropdowns). En sesión normal de edición el
foco entra/sale del content docenas de veces, lo que producía fatiga
perceptual.
Cambios en words-provider.svelte.ts:
- onfocus: emit solo en transición real (was-not-focused → focused).
Re-focus desde popover interno NO emite.
- onblur: si el related target es interno al Words tool tree
(isInsideWordsTool), mantener focused=true (Words se trata como un
solo scope de foco). Skip commit('blur') también para evitar
commits espurios entre popover hops.
F1.2 — Declarar 8 data-* hardcoded en morfo (audit P0-1):
Añadidos al Content part:
- data-words-find-match, data-words-find-active (mark highlights)
- data-words-indent (list items con indent > 0)
- data-language (alias parsing-only para interop con HTML externo
emitido por Prism/highlight.js)
Nuevas 6 parts compound (Eidos wrappers):
- HeadingPicker + HeadingPickerPanel (data-words-heading-picker /
-panel)
- CodeLanguagePicker + CodeLanguagePanel
- ToolbarFamilyMenu + ToolbarFamilyPanel
F1.3 — Rename i18n keys a kebab-case (audit P0-1):
4 keys camelCase eran ilegales según contracts.test.ts. Renamed:
- components.words.bubbleMenu → bubble-menu
- components.words.slashMenu → slash-menu
- components.words.linkEditor → link-editor
- components.words.findReplace → find-replace
Propagado a: morfo declarations (8 refs), provider tslangs calls
(4 refs), catalog langs/components/words.ts (4 keys).
F1.4 — Canonizar 9 event names a forma {family}-{verb}[-{variant}]
(audit P2-1):
Renombrados para parsear con el morfo:vocabulary script. El verb
declarado en cada semantic ahora aparece explícito en el name:
- commit-content → commit-save-content (verb: save)
- commit-format → commit-set-format (verb: set)
- commit-link → commit-set-link
- commit-unlink → commit-reset-link (verb: reset)
- commit-slash-command → commit-set-slash
- commit-check → commit-set-check
- commit-history → commit-restore-history (verb: restore)
- commit-clear → commit-reset-content (verb: reset)
- signal-invalid-input → signal-warn-invalid (verb: warn)
Propagado a: sema cascade selectors, soma provider trigger calls,
provider + content tests, demo route. 0 conflictos con event names
de otros componentes (file-upload/tag-group/etc. usan namespaces
propios sin colisión con words).
Tests: 161/161 pass en src/uix/{contracts,soma/components/words}.
Suite full eidos+morfo+soma+sema pendiente de verificar tras commit.
contracts.test.ts ahora verde — cierra P0 audit Codex.
morfo:vocabulary 0 event-name warnings (los 107 data-attr enum
warnings son pre-existentes, separate scope).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
4a34c4f4df |
docs(CONTINUE): note variant canon position in hand-off
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
bd73fb98f3 |
feat(eidos): variants canon — EIDOS_VARIANTS + per-component lint
Variants son canon del eidos, NO del theme. Decisión arquitectónica
firmemente sostenida: el vocabulario de variants (solid/outline/ghost/
soft/surface/line/pills) está fijo a nivel del framework — paralelo
a las 8 sema families del libro. Theme = retintar lo perceptualmente
fijo; cambia QUÉ color es `affirm`, no QUÉ significa `outline`.
Cambios:
- lib/types.ts: nueva constante `EIDOS_VARIANTS` con los 5 archetypes
canónicos (control / selection / chip / marker / tabs). Los 5 union
types se derivan via `[number]` indexed access — valor y tipo no
pueden desincronizarse. Nueva `EIDOS_VARIANT_VALUES` Set flat con
todos los valores canónicos + utilidades cross-component (`plain`,
`subtle`).
- recipe-css-contract.test.ts: nuevo test "variant CSS selectors per
component match the declared type union". Por cada componente:
extrae el union type de `components/{c}/types.ts` (soporta literal
unions + archetype aliases; cae a advisory mode en Extract<> y
conditional types); compara con `[data-{c}][data-variant='X']`
selectores en `{c}.css`; reporta typos y unauthorized extensions
bidireccionalmente.
- THEMING.md §19: nueva sección "Variants son canon del eidos, NO
del theme" con argumentación (portabilidad, type safety, archetypes
perceptuales paralelos a sema families), tabla de las 3 capas de
la cebolla, referencia a `EIDOS_VARIANTS`, comparación con Radix
Themes 3.x / Mantine 7 / Chakra v3 / Ark / shadcn. TOC actualizado.
- eidos/README.md: tabla de referencia ampliada con §19.
- CLAUDE.md: hand-off "2026-05-27 #6 (variants canon)".
- CONTINUE.md: nota de la decisión arquitectónica.
Variants component-specific permitidos (Banner inline/overlay/
persistent, Spinner bars/dots/ring, Button 'plain'): viven en cada
`components/{c}/types.ts` y el lint los valida contra la CSS del
componente.
Tests: 101/101 pass en `src/uix/eidos`. `npm run check`: los mismos 6
errores pre-existentes (lib/_demo, soma/components/internal, web/
routes/active) — no relacionados.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
d3f9be4ab0 |
fix(toggle): add missing per-color track/hover/border forwarders
The toggle palette cascade (palette-track / palette-hover / palette-border
declarations in TSC) referenced --toggle-{X}-track, --toggle-{X}-hover,
--toggle-{X}-border for X in {primary, neutral, secondary, affirm, risk,
threat} — but the recipe only declared 5 forwarders per color (element /
solid / solid-hover / text / contrast). The 3 missing slots × 6 colors =
18 unresolved tokens made --toggle-palette-{track,hover,border} resolve
to empty for any non-neutral color (the host default DID declare them).
Visible effect, only when a non-neutral color was active:
- ghost-on-bg = var(--toggle-palette-track) → empty → transparent fallback
- ghost-on-border = color-mix(... palette-border ...) → invalid →
property dropped, border falls through to base layer
- outline-on-bg = color-mix(... palette-element ...) was the one that
worked because palette-element forwarders DID exist for all 6 colors
Surfaced today while verifying the TSC v2.2 toggle-group color cascade
fix in the browser. The toggle-group composition correctly overrode
--toggle-palette-* on items, but the empty track/hover/border slots
collapsed ghost-state styling downstream.
Verified with browser probe (preview_inspect):
- ghost + affirm: paletteTrack #0d1514, paletteHover #023b37,
paletteBorder #145750 (was "")
- ghost + threat: paletteTrack #191111, paletteHover #500f1c,
paletteBorder #72232d (was "")
- outline + primary: paletteTrack #18111b, paletteHover #3d224e,
paletteBorder #54346b (was "")
Tests: 100/100 pass in src/uix/eidos. Recipe contract guard accepts
the new forwarders (kebab-case, valid var() values).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
9c6375c96e |
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage
Token Scope Contract universal — no más excepciones arquitectónicas.
Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar,
toggle-group) ahora están dentro del contrato via dos extensiones nuevas.
TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts):
- `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite
selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para
componentes con data-color cascadeado per-part. Consumer: select.
- `composition: { foreignRecipe: { targetSelector, tokens } }` sibling
key — overrides cross-recipe scoped a la cascade del host. Consumer:
toggle-group modifica `--toggle-palette-*` en sus items.
Migraciones:
- select: 3 private `_accent-{track,border,text}` con parts: ['trigger',
'content'] + 7 cascades color:X each. Removed orphan `_accent-solid`
(CSS no consumía).
- avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con
matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques
CSS × 2 partes.
- toggle-group: composition block con 8 palette tokens × 4 colors.
Reemplaza 4 bloques CSS per-color.
Bug toggle-group post-composition (encontrado y arreglado):
Tras la composition migration, el cascade del toggle-group seguía roto
porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`,
etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es
sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía
undefined. Fix: inlined derivation expressions directamente en
`[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost),
referenciando palette tokens en su propio scope local.
Validador + emisor + contract:
- `validateRecipeComposition` valida el shape `{ targetSelector, tokens }`
y rechaza composition entries con scope='root'.
- `stripCompositionKey` + `emitComposition` separan el pipeline.
- `appendRecipeContractTokens` skip-list para `composition` (no aparece
como fake `--{c}-composition` knob).
- `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts
filtran composition en todos los iteradores.
Documentación:
- THEMING.md §18 reescrito como "Cobertura universal de TSC". §7
extendido con subsecciones "Multi-part scope" y "Cross-recipe
composition" + ejemplos completos. TOC actualizado.
- eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18.
- CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal).
- CONTINUE.md reescrito al estado actual de la sesión.
Working tree también incluye sprint Words en paralelo (multiple authors):
slash menu, find/replace regex, code language picker, table audit,
toolbar family menu, code highlight engine.
Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`:
6 errores pre-existentes (lib/_demo, soma/components/internal,
web/routes/active) no relacionados.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
28bf7f8b49 |
feat(pickers): wire runtime.trigger('close') in 5 picker providers
After the 2026-05-27 picker family polymorphic refactor, the morfos
declared the polymorphic `close` event but the providers were inert —
they just toggled `opts.open = false` without firing the event. This
commit cables them, mirroring the Dialog/Drawer/Popover dismissWith
pattern (book §5.3).
**Affected providers** (5): color-picker, date-picker, date-range-picker,
time-picker, time-range-picker.
**Pattern** (consistent across all 5):
- Private `triggerClose(cause)` helper maps:
'committed' / 'range-committed' → commit.save + fulfill
'cancelled' → emerge.close
'dismissed' → emerge.dismiss
'dismissed-outside' → emerge.dismiss
- Writes `data-last-action` on the morfo's target part (calendar /
clock / content) imperatively before the trigger fires so eidos can
tint the exit animation per cause.
- Called from commit() / cancel() / handleSelect / handleDateSelect /
handleRangeSelect — the close paths the provider already had.
The morfo target varies per picker:
- color-picker → content
- date-picker → calendar
- date-range-picker → calendar
- time-picker → clock
- time-range-picker → clock
Internal state (`opts.open = false`) sequence preserved — the trigger
fires BEFORE the open flip so the perceptual signal stamps
`data-event-*` on a node that is still rendered.
**dismissed / dismissed-outside causes** are declared but not yet wired
to specific gestures — the picker shell delegates escape / outside-click
handling to its wrapping Popover, whose own polymorphic close fires
independently. The cause strings remain in the helper for future use
(e.g. if a picker grows its own escape handling).
**Result**:
- Pickers now emit perceptual signal on commit/cancel via sema engine.
- Eidos `[data-last-action='X']` selectors get exit-tint values to act
on (mirror of Dialog/Drawer/Popover).
- Picker tests: 18/18 still pass. `morfo:check`: 0 issues.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
312c8740ca |
refactor(date-picker): rename kind 'date' to 'day' for trio consistency
Audit caught `data-kind="date"` violation — the date-picker / date-range-picker morfos declare `data-kind ∈ [day, month, year]` but the public `kind` prop accepted `'date' | 'month' | 'year'` and defaulted to `'date'`. Rename prop value `'date' → 'day'` so the trio is consistent (all named after the grid cell granularity). **Rationale (book-pure)**: - The trio `day` / `month` / `year` is internally consistent — all three name the granularity of the grid cell the calendar view shows. - `'date'` was awkward in the trio: month and year pickers also pick "dates", just at coarser granularities. - The morfo enum was already canonical (`['day', 'month', 'year']`); this aligns the prop with the contract. **Surface touched** (all `'date' → 'day'` for the kind value): soma components: - date-picker / date-range-picker / date-field / date-range-field: provider type (DatePickerKind / DateRangePickerKind), default value, test fixtures, types.ts JSDoc. demos (web/routes/uix/components/): - date-picker / date-range-picker / date-range-field: kind state, selector chips, doc table. `'date'` literals that DON'T refer to kind (kept untouched): - ValueProfile = 'date' | 'datetime' | 'zoned' (different concept). - SegmentLock = 'none' | 'year' | 'monthYear' | 'date'. - CSS `input[type='date']` (HTML attribute). **Result**: - `npm run morfo:check`: 1 issue / 1 component → **0 issues**. All 107 routed morfos validate against their demo DOM ✓ - `npm run check`: 0 errors ✓ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
59c3bc96c4 |
fix(color-picker): emit data-state explicitly on Provider props
Audit caught 'data-state not emitted' on the ColorPicker Provider. The
morfo declares `data-state ∈ [open, closed]` with `value: v.stateRef('open')`
— the runtime's `syncAttrs` can sync this from `dom.apply`, but only if
the provider receives a `ref` on its opts (which the ColorPickerProvider
doesn't — `ColorPickerOpts` extends `ProviderOpts` + `StateProps` without
the ref slot).
Rather than re-shape the opts to add ref + bind it in the wrapper, just
emit `data-state` explicitly in the provider's props derivation, same
form as the other six declarative attrs that the provider already
materialises (`data-format`, `data-invalid`, `data-disabled`,
`data-readonly`, `data-required`, `aria-invalid`). Matches the
dialog/drawer/popover doctrinal pattern for disclosure attrs.
After this commit `morfo:check`: 1 issue remaining (date-picker
`data-kind="date"` — separate domain issue, addressed next).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
9f1466dc57 |
fix(avatar): data-color severity optional to acknowledge custom-color escape hatch
Audit caught 4 'missing data-color' violations on the Avatar Provider — the demo includes consumers passing non-canonical CSS color strings (`#7c3aed`, `oklch(70% 0.15 220)`, `rgb(245 158 11)`) which the wrapper correctly routes through `data-color-custom=""` + an inline `--avatar-color-custom` CSS variable instead of the enum attr. The morfo enum can't represent arbitrary CSS color strings, so the `data-color` attr is legitimately absent in those cases. The contract should reflect that: severity becomes `optional`. Symmetric with the `Avatar.Badge` part which already declared this attr as optional. Behaviour unchanged. Only the morfo's severity flag changes. After this fix `morfo:check`: 6 issues / 3 components → 2 issues / 2 components. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
3542f3a730 |
fix(button): remove redundant data-state enum (book §1 purity)
Audit 2026-05-27 caught 11 `data-state="idle"` violations across 3
disclosure trigger components (dropdown-menu, table.row-detail-trigger,
tree-grid.expand-trigger). Root cause was composition: each used
`<Button>` as its chrome via `{...props}` spread, and Button's morfo
declared a redundant `data-state ∈ [idle, loading]` enum that the
runtime emitted unconditionally — clobbering the outer trigger's
`data-state="open|closed"` via mergeProps order.
Doctrinal fix: REMOVE `data-state` from Button's morfo entirely.
Button's loading state is already fully expressed by the
`data-loading` presence flag (declared further down in the same morfo
and consumed by eidos as `[data-loading]`, not
`[data-state='loading']`). The 'idle' value was a redundant synonym
for "not loading" — and the book canon favours "presence = active
state, absence = rest" for binary states (cf. `data-disabled`,
`data-loading`).
**Book-pure rationale**:
- Disclosure (family `emerge`): `data-state ∈ [open, closed]` —
dialog, drawer, popover, accordion, dropdown-menu, etc.
- Lifecycle (family `sustain`): `data-state ∈ [idle, loading, ...]` —
image, avatar, button (until now), card, field.
- Mixing the two vocabularies in a single attr is the incoherence
this fix removes.
- Button is interactive surface, NOT lifecycle: its "rest" is
natural absence of `data-loading`, not a distinct `idle` state.
**Result**:
- `npm run morfo:check`: 16 issues / 6 components → **6 issues / 3
components**. Removed all 11 `'idle'` violations.
- Composition pattern restored: `<Button>` no longer clobbers
`data-state` on the host trigger.
- Eidos CSS unchanged (already uses `[data-loading]`).
- Tests: 803/801 unchanged (2 remaining failures are unrelated, in
the user's words editor WIP).
**Not fixed in this commit** (3 remaining `morfo:check` failures):
- `avatar.provider`: missing `data-color` emission (4×)
- `color-picker.provider`: missing `data-state` emission
- `date-picker.provider`: `data-kind="date"` not in declared
`[day, month, year]` enum.
Each requires a separate domain decision.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
681e056102 |
fix(uix-audit): close 4 pre-existing issues found in system audit
System-wide audit of `src/uix/*` + `src/arts/adom` (excluding demos and web/routes). Findings + fixes documented in `src/uix/AUDIT_REPORT_2026-05-27.md`. **Fixed**: 1. `src/uix/eidos/lint.test.ts`: "every eidos CSS component has a morfo file" test extended to walk `morfo/internal/` as well as `morfo/components/`. The picker-shell relocation (audit-codex P1 #5) moved picker-shell to internal but the test wasn't updated. 2. `src/uix/contracts.test.ts`: two pre-existing failures resolved. (a) Picker WithChild regex updated to accept the `WithChild<Omit<PrimitiveDivAttributes, ...> & {...}>` shape applied in the audit-codex P0/P1 picker ProviderProps refactor. (b) `collectPublicSomaComponentDirs` now skips `picker-shell` as an internal primitive (symmetric to `collectKnownMorfoDataAttrs` which now walks `morfo/internal/` to include the picker-shell data-attrs). 3. `src/uix/soma/components/command/command-provider.svelte.ts`: restored missing `createAttrs(commandMorfo)` import. Was the only ERROR in `npm run check` baseline. 4. `src/uix/soma/components/dialog/types.ts`: JSDoc comment updated to reflect polymorphic close shape (cosmetic alignment with the 2026-05-27 refactor). **Result**: - `npm run check`: 1 ERROR → **0 ERRORS** ✓ - `npx vitest run src/uix`: 5 failed → **2 failed** (both 100% in user's words/* WIP, not framework code) - Polymorphic close + persistence + a11ySemantic rollout: **zero drift detected** across morfos / providers / sema cascades / tests. **NOT actioned** (documented in report): - 6 pre-existing `morfo:check` failures (avatar / color-picker / date-picker / dropdown-menu / table / tree-grid). All require domain decisions (drop 'idle' state vs add it to morfo enum, etc.). - 1 navigation-menu data-state='visible' eidos selector drift. - ~14 README/`.md` files with stale Dialog/Drawer/Popover descriptions. - Words editor doctrinal cleanup (user's WIP). - Tooltip polymorphic alignment (optional, no consumer benefit today). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
f0440d647a |
docs(dialog-demo): update prose for polymorphic close
The dialog demo prose described "Six events" with the per-event close-*
shape that was collapsed into a single polymorphic close in commit
|
5 months ago |
|
|
34e0064f2c |
feat(toast): explicit persistence: 'transient' on signal-announce event
Documents the deliberate choice to keep the announce event's perceptual projection transient even though the canonical book §6.2 table would suggest `untilFix` for `signal + risk` or `untilAction` for `signal + threat`. Rationale: the Toast component owns its own lifecycle via `Toast.Provider`'s auto-dismiss timer + user dismiss. The `data-event-*` projection is a one-shot pulse when the toast enters. Persistence of the toast surface itself lives in `data-state` (open/closed) on the Item, not in the signal projection. Found via audit of all `verb: 'warn' | 'alert' | 'notify'` events across `src/uix/morfo/components/*`. Toast was the only one without an explicit persistence declaration — annotated now so the choice is intentional rather than implicit-default. (`words.signal-invalid-input` also lacks persistence but it belongs to the in-flight words editor work tracked separately by the user.) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
9ca8b4e30b |
docs(claude/libro): document persistence + a11ySemantic + polymorphic close rollout
LIBRO_VARIACIONES_Y_EXTENSIONES.md: - New section D.9 (persistence + holds-by-intent — book §6.1). - New section D.10 (a11ySemantic per event — book §9.1). - New section D.11 (polymorphic events — book §5.3) with the full rollout state across overlays + picker family. CLAUDE.md hand-offs: - 2026-05-27: initial sprint (persistence + a11ySemantic + polymorphic types). - 2026-05-27 #2: caller messages + persistence extension + Dialog refactor. - 2026-05-27 #3: Drawer + Popover polymorphic refactor. - 2026-05-27 #4: Picker family polymorphic refactor + test fixtures decoupling. Each hand-off records: scope, files touched, doctrinal decisions, and pending follow-ups. The whole rollout was verified on 680/680 tests across sema + morfo + soma + adom scopes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
8dce794ab4 |
refactor(pickers): collapse close-* into polymorphic close on 5 picker morfos
Mirror of the dialog/drawer/popover refactor (book §5.3) applied to the
picker family:
- color-picker
- date-picker
- date-range-picker (incl. close-range-commit cause)
- time-picker
- time-range-picker
Each had 4 close-* events with per-event prewrite; collapsed to a single
polymorphic `close` event:
semantic: {
family: 'emerge', verb: 'close', target: v.partRef('calendar'|'clock'|'content'),
sequence: 'pre', persistence: 'transient',
allowedFamilies: ['emerge', 'commit', 'signal']
}
**Behaviour preserved**: the picker providers don't currently fire any
`runtime.trigger('close*')` — they just toggle `opts.open = false` and
let the popover unmount. The previous `close-*` events were declared but
inert (only consumed by schema validator + compiler tests). Refactor is
doctrinal alignment, not behavioural change.
**Sema cascade**: only color-picker has a sema pack and it did not
reference any close-* event (only handle-pick / handle-drag /
commit-set). Zero cascade updates needed.
**Test fixtures**: compile.test.ts + runtime.svelte.test.ts already
migrated to the synthetic `prewriteFixtureMorfo` in the core commit,
so this refactor doesn't break them.
After this commit the only morfo with the pre-polymorphic per-event
prewrite shape is the synthetic test fixture itself — every production
morfo is on the polymorphic close.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
9f5fbfdb71 |
refactor(dialog/drawer/popover): collapse close-* into polymorphic close (book §5.3)
Three overlay components each had five `close-*` events (close-save /
close-cancel / close-dismiss / close-dismiss-outside / close-after-fail)
with per-event prewrite arrays setting `data-last-action`. Per the book
§5.3 polymorphism canon, collapse to a single `close` event:
semantic: {
family: 'emerge', verb: 'close', target: ...,
sequence: 'pre', persistence: 'transient',
allowedFamilies: ['emerge', 'commit', 'signal']
}
NO prewrite — the provider sets data-last-action imperatively before
trigger because polymorphic events can't bind a single prewrite per call.
**Provider pattern** (Dialog / Drawer / Popover):
- New `DISMISS_CAUSES` map: { save, cancel, dismiss, dismiss-outside, fail }
→ { lastAction, semantic: { family, verb, intent? } }.
- `dismissWith(action, opts?)` does `dom.apply({ 'data-last-action': cause.lastAction })`
then `runtime.trigger('close', { fallbackTarget, semantic: cause.semantic, message })`.
- `triggerClose` is now private; previously-public 2-arg signature collapsed
into the cause-driven internal helper.
- All internal callsites (escape, outside-click, hover-close, close button)
migrated to `dismissWith`.
- `opts.message` forwarded to runtime — when `a11ySemantic.requiresLiveRegion`
is set (today only for the 'fail' case), the live region announces it.
**Schema validator relaxed** (`src/uix/morfo/schema.ts`): the
"every declared value of `data-last-action.values[]` must be prewritten
by some event" check was DROPPED — provider-imperative writes can't
satisfy it. The reverse direction (`prewritten ⊆ declared`) stays strict.
**Cascade rewrite** (`src/uix/sema/components/{dialog,drawer,popover}.ts`):
- `eventNamePrefix: 'close-'` → `eventName: 'close', eventFamily: 'emerge'`
- `eventName: 'close-dismiss-outside'` → `eventName: 'close', state: {
attr: 'data-last-action', value: 'dismissed-outside' }`
**Eidos CSS**: not touched — already reads `data-last-action` directly.
**Public API preserved**: `dismissWith` signature + observable behaviour
identical. External consumers see no difference.
Tests updated for the new event name. 680/680 pass across sema + morfo +
soma + adom scopes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
c34fd1fd31 |
feat(uix-components): apply persistence + a11ySemantic + live-region messages to 6 morfos
Per the canonical book §6.2 + §9.2 mapping, six morfos with `signal.warn`,
`signal.alert` or `signal.notify` events get explicit `persistence` +
`a11ySemantic` annotations, and their providers wire up `clearTarget` +
caller-message integration with the shared live region.
**Morfo annotations**:
| Morfo / event | persistence | a11ySemantic |
|--------------------------------------------|----------------|---------------------------------------------------------|
| announce.signal-alert | untilAction | requiresPersistentTrace + requiresLiveRegion |
| form.signal-warn-invalid | untilFix | requiresPersistentTrace + requiresFocusMove + reducedMotionFallback='text' |
| file-upload.signal-warn-reject | untilFix | requiresPersistentTrace + reducedMotionFallback='text' |
| password-field.signal-notify-caps-state | stateBound | requiresLiveRegion + reducedMotionFallback='text' |
| tags-input.signal-warn-reject | untilFix | requiresPersistentTrace + reducedMotionFallback='text' |
| textarea.signal-warn-count-overflow | untilFix | requiresPersistentTrace + reducedMotionFallback='text' |
**Provider wiring**:
- form-provider: clearTarget on submit, message="{N} form errors..." via
FORM_LANGS.ERROR_SUMMARY_*.
- file-upload-provider: clearTarget before each accept/reject cycle, message
via new FILE_UPLOAD_LANGS.REJECT_SUMMARY_{SINGLE,MULTI}.
- password-field-provider: clearTarget on caps-off transition, message
via existing PASSWORD_FIELD_LANGS.CAPS_WARNING.
- tags-input-provider: helper `emitWarnReject` (clear + emit + message
via new TAGS_INPUT_LANGS.REJECT_WARNING), called from addTagValue,
addFromInput, handlePaste. Successful add also clears (the add is the fix).
- textarea-provider: emits only on transition INTO overflow, clears in
transition OUT (untilFix projection persists). Message via new
TEXTAREA_LANGS.OVERFLOW_WARNING with {{max}}.
Three new lang catalog entries (reject/overflow warnings) merged into
the existing components.{name}.* namespaces.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
70f6f3bf0d |
feat(uix): persistence + a11ySemantic + polymorphic core (book §5.3, §6, §9)
Three book canon items codified in code:
**Persistence (libro §6.1)** — separa `hold` (perceptual min) de
`persistence` (lifecycle real):
- New `SignalPersistence = 'transient' | 'untilAction' | 'untilFix' | 'stateBound'`
- New `SEMA_HOLDS_BY_INTENT` canonical lookup table (`src/uix/sema/holds.ts`)
- `EngineSemantic.emit()` returns the resolved signal id; keeps projection
alive past hold for non-transient. Exposes `clear(id)`, `clearTarget(target)`,
`hasActive(id)`.
- `SomaRuntime.trigger()` returns `TriggerResult { id?, persistence? }`.
Exposes `clearSignal(id)`, `clearTarget(target)`, `partRef(part)`.
**a11ySemantic (libro §9.1)**:
- New `MorfoA11ySemantic` (requiresPersistentTrace, requiresLiveRegion,
requiresFocusMove, keyboardEquivalent, reducedMotionFallback) on MorfoEvent.
- `ActiveDom.prefersReducedMotion`: reactive tracker via media query
(`src/arts/adom/reduced-motion.svelte.ts`).
- `ActiveUix.announce(msg, priority?, timeout?)`: lazy-created live region
via `dom.writeNode`.
- `Soma.runtime()` auto-wires `sources.announce`.
- `SomaRuntime.trigger()` honors a11ySemantic after emit (live region, focus,
reduced-motion fallback including `channels: []` for 'state').
**Polymorphic events (libro §5.3)** — ADITIVO sobre shape concreto:
- `allowedFamilies?: readonly SemaFamily[]` opcional en MorfoEventSemantic.
- Provider override via `runtime.trigger(name, { semantic })`.
- Default family of the morfo is implicitly allowed.
- New `SomaRuntimePolymorphicError`.
- New `isPolymorphicSemantic` type guard.
Synthetic `prewriteFixtureMorfo` (src/uix/morfo/test-fixtures.ts) decouples
compile + runtime tests from the production morfo catalogue.
671 tests pass in sema + morfo + soma + adom scopes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
e6eee766ee |
Improve words editor tooling
|
5 months ago |
|
|
5f7b2354a8 |
docs(claude): session hand-off + SEMA_FAMILY_POLICY refresh for Lectura C
- Replace outdated SEMA_FAMILY_POLICY section (single `intentPolicy`) with the two-axis form: `intentRequirement` (compile-time) + `intentGuidance` (doctrina). 8 families now including `delegate`. - New hand-off section "2026-05-26 (sema canon + soma emission + READMEs)" listing the 9 commits + architectural deltas of the sprint: `Morfo.expression` field, 38 packs over 9 tuning profiles, 18 components with soma emission cabled, `handle-scroll*` skipped by design, picker-shell internal relocation, D.4-D.8 doctrine in LIBRO_VARIACIONES, 23 component READMEs updated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
7afa7057d4 |
docs(soma): add Sema events section to 23 component READMEs
Documenta los cambios doctrinales del sprint Lectura C + cabling soma. Cada README añade una sección `## Sema events` con tabla (event / family / verb / target / intent / when) + referencia al pack si existe + notas doctrinales de las correcciones aplicadas. Componentes: - Packs creados este sprint (11): switch, toggle, toggle-group, menubar, navigation-menu, dropdown-menu, context-menu, tree-view, tree-grid, tooltip, collapsible. - Componentes con emisión cableada sin pack (8): listbox, grid-list, table, feed, command, carousel, announce, clipboard. - Componentes gestuales con notas sobre handle-scroll no-emitido (3): drag-drop, virtual-list, virtual-grid. - Checkbox: documenta la doctrina de dos eventos direccionales (commit-toggle-check/uncheck con intent affirm/neutral). Correcciones doctrinales documentadas: - announce: commit-announce-* → signal.announce / signal.alert - tree-view: target item → branch para emerge-expand/collapse - table: shift.sort → commit.set; shift.expand-row → emerge.expand - drag-drop: commit.drag-start/drop → handle.pick/drop - clipboard: commit.copy → commit.save; commit.copy-error + threat → commit.fail + risk - virtual-list/grid: handle-scroll declarado pero NO emitido (decisión consciente — buzz nonstop con family.handle haptic-only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
cbf76b66c8 |
feat(soma): wire runtime.trigger for batch C (gestural + wrappers)
Cierra el sprint de cabling. De los 6 originales del batch C, 3 son
wrappers sin events propios (time-range-field, link-preview,
alert-dialog) que delegan en su componente compuesto — zero wiring
necesario. Los 3 restantes:
- drag-drop:
- startDrag(...) → handle-pick en draggable con params.source
como fallbackTarget.
- commitDrop(target) → handle-drop en droppable con target.
- cancelDrag() → commit-cancel en draggable con drag.source.
- virtual-list:
- scrollToIndex(...) → shift-navigate-to-index en viewport
(programmatic discrete-navigation moment).
- observeResize callback → commit-set-resize en provider
cuando el clientWidth/Height realmente cambia (skip initial).
- NO emite handle-scroll: family.handle es haptic-only, el
scroll listener fires en cada píxel — emitiría buzz nonstop.
Documentado como decisión consciente; apps con scroll-feedback
wiran su propio throttle.
- virtual-grid: análogo virtual-list, con scrollToCell →
shift-navigate-to-cell. NO emite handle-scroll-row /
handle-scroll-column por la misma razón.
Verificación:
- npm run morfo:vocabulary → EXIT 0
- npx vitest run src/uix/sema src/uix/morfo → 195/195 pass
- npx vitest run src/uix/soma → 419/419 pass
Tras este commit: TODOS los morfos con scope:sema + events declarados
emiten desde soma (excepto los 4 `handle-scroll*` de virtual-list/grid
intencionalmente saltados). Cierra el caveat "soma no emite" del
proyecto memory.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
0784cae578 |
feat(soma): wire runtime.trigger for batch B (8 components)
Continúa batch A. Estos 8 componentes tienen scope:sema y events
declarados pero no usan pack propio — emiten sobre family base.
- listbox: select() central → commit-select / commit-unselect en item.
- grid-list: select() central → idem en row.
- table:
- SortTrigger.onclick/onkeydown → commit-set-sort en column-header.
- Row.onclick/onkeydown → commit-select en row.
- RowDetailTrigger → emerge-expand-row en row (solo transición
closed → open; el morfo no declara collapse counterpart).
- feed:
- focusAt() (centraliza navegación de teclado) → shift-navigate-focus-item
en article.
- PageDown-en-final + Sentinel.onIntersect → commit-submit-load-more
en provider.
- command:
- Item.onclick → commit-submit-invoke en item (path mouse).
- selectCurrent() (Enter key) → commit-submit-invoke vía lookup
por data-value en provider ref.
- carousel: commit() central (next/prev/goto/autoplay) →
shift-navigate-slide en item, lookup por data-index.
- announce: announce() → signal-alert (assertive) o signal-announce
(polite). clear() → commit-reset.
- clipboard: copy() acepta optional triggerEl → commit-save-copy
(success) / commit-fail-copy (catch). Trigger.onclick pasa
e.currentTarget.
Verificación:
- npm run morfo:vocabulary → EXIT 0
- npx vitest run src/uix/sema src/uix/morfo → 195/195 pass
- npx vitest run src/uix/soma → 419/419 pass
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
adf89a0811 |
feat(soma): wire runtime.trigger for batch A (7 components)
Los 11 packs sema shipped en commit |
5 months ago |
|
|
66318c4759 |
fix(tree-view): target branches for emerge-expand/collapse
Morfo declaraba target='item' para emerge-expand y emerge-collapse,
pero los items son hojas — no se expanden. El stamp del data-event-*
debe aterrizar en el branch (data-tree-view-branch), que es el part
que realmente revela/oculta contenido.
- Morfo: target: v.partRef('item') → v.partRef('branch') en ambos
events de emerge. commit-select sigue en item (selección aplica
tanto a leaves como a branches).
- Pack: selector `onItem(emerge-*)` → `onBranch(emerge-*)`. Las dos
cascadas de emerge ahora matchean el elemento correcto en DOM.
tree-grid no afectado: su Row es a la vez item y branch (data-has-
children + data-expanded en el mismo part), así que target='row'
ya estaba correcto.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
43b1537291 |
refactor(picker-shell): close as INTERNAL primitive (audit P1 #5)
picker-shell quedó suelto en el audit-codex: existía un morfo placeholder en src/uix/morfo/components/picker-shell.ts sin imports, sin events, sin texts, y los 5 composite pickers (date / date-range / time / time-range / color) consumían eidos parts via re-export bajo SU namespace (<DatePicker.Footer>, etc.) — apps nunca escribían <PickerShell> directo. Cierre como INTERNAL primitive per la propia recomendación del audit: - Movido src/uix/morfo/components/picker-shell.ts → src/uix/morfo/internal/picker-shell.ts. El audit walk de morfo/components/ ya no lo encuentra. Cero imports rotos (no había ninguno). - README en src/uix/eidos/components/picker-shell/README.md declara el status INTERNAL + diagrama de composición de los 5 pickers. - Docstring del morfo actualizado con STATUS: INTERNAL explícito. - audit-codex-full-1.md sección 5 marcada RESUELTO con verbatim del cierre. Verificación: - npm run morfo:vocabulary → EXIT 0 - npx vitest src/uix/sema src/uix/morfo → 195/195 pass - npm run component:audit → picker-shell ya no aparece (no era public component, no debe figurar en lista de NEEDS-WORK) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
826ca2bf46 |
docs(sema): add D.8 — channels: qué es canal y qué no
Doctrina arquitectural sobre límites de los canales runtime: - Canales built-in del framework: sound + haptic (los únicos). - ARIA estructural: declarativo en morfo, NO canal. - ARIA dinámico (live regions): soma escribe directo, NO canal. - Visual (motion / color / presence): eidos CSS via data-event-*, NO canal. - Regla operativa: algo es canal sólo si recibe SemanticSignal, acepta modulación por intent.deltas, y tiene signature paramétrica análoga a sound/haptic. ARIA dinámico falla la modulación. - Channels extensibles: declaration merging del registry; opt-in por app (voice, a11y formal, etc.). Framework no envía ninguno. Cierra una tentación que surgió en D.7 — convertir ARIA en canal era confusión categorial. Esta sección fija los límites para que nadie reincida. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
2d562f3788 |
feat(sema): Lectura C steps 5-8 — 11 sema packs + doctrina samples
Step 5 — 3 packs alta-frecuencia (silencio + tap leve):
switch, toggle, toggle-group
+ tuning `form.toggle.silent: { gain: { op: 'add', value: -0.3 } }`
Step 6 — 6 packs menús + árboles (emerge.soft / form.commit.subtle):
menubar, navigation-menu, dropdown-menu, context-menu,
tree-view, tree-grid
Step 7 — card scope: KEEP `sema` + `family-default` (sin código).
Step 8 — 2 packs (versión lean tras descartar 5 prematuros):
tooltip (silent-by-default)
collapsible (mirror accordion)
+ tuning `tooltip.silent: { gain: { op: 'add', value: -0.2 } }`
Doctrina codificada en LIBRO_VARIACIONES_Y_EXTENSIONES.md:
- D.5 — packs alta frecuencia (toggles)
- D.6 — packs superficies menú / árbol
- D.7 — sonido canónico vs samples
SOUND_LIBRARY = recursos | SOUND_TUNINGS = canon | packs = composición
samples NO sustituyen firma con intent.deltas
sampleOverlay descartado permanentemente (sobreingeniería)
family signal admite excepción para marcas culturales
Cierra Lectura C steps 5-8 del audit-codex P1 #3. Total: 11 packs
nuevos sobre 9 perfiles canónicos de tuning. 195/195 tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
504780ad8b |
feat(morfo/sema): add expression field + sema coverage check (P1 #3)
User doctrinal decision 2026-05-26: `scope: ['sema']` declares
**participation** in sema. It does NOT impose the obligation of
shipping a pack. The new `expression` field on Morfo declares HOW the
component contributes its perceptual signature.
Schema change (src/uix/morfo/types.ts + schema.ts):
type SemaExpressionMode = 'pack' | 'family-default' | 'delegated' | 'none';
interface Morfo {
// ...
expression?: SemaExpressionMode;
}
Semantics:
- 'pack' — sema/components/{kebab}.ts cascades the signature
- 'family-default' — use family base + intent deltas as-is (no override)
- 'delegated' — composite; perceptual expression lives in children's packs
- 'none' — explicit opt-out (rare; reserved for headless contracts)
Coverage check (scripts/morfo-vocabulary-check.ts §4):
FAIL events declared + no 'sema' in scope — incoherent
WARN scope:sema + events + no pack + no expression — ambiguous
OK scope:sema + events + (pack OR expression) — explicit
OK no events — coverage N/A
Pack discovery is automatic from filesystem; the morfo doesn't need to
declare `expression: 'pack'` redundantly. Lint passes when a pack file
exists at `src/uix/sema/components/{kebab}.ts`.
Applied to 5 morfos that had events but no sema scope (incoherent state):
- announce, clipboard, listbox, virtual-grid, virtual-list
now declare scope: ['soma', 'sema'] + expression: 'family-default'.
Applied to 28 morfos that had scope:sema + events but no pack:
- 4 composites (date-picker, date-range-picker, time-picker,
time-range-picker) → expression: 'delegated'
- 24 others (button, card, carousel, collapsible, color-field, command,
context-menu, drag-drop, dropdown-menu, feed, grid-list, menubar,
month-grid, navigation-menu, range-calendar, switch, table, time-field,
toggle-group, toggle, tooltip, tree-grid, tree-view, year-grid)
→ expression: 'family-default'
The 29 morfos that already have packs are not touched; the lint
discovers their packs from the filesystem.
Pending follow-up (separate commits per author's plan):
- Create packs for high-frequency toggles (switch, toggle, toggle-group)
- Create packs for menus / navigation / tree (menubar, navigation-menu,
context-menu, dropdown-menu, tree-view, tree-grid)
- Review card scope (may not need sema scope at all)
- Per-component review of the rest
Verification:
- npm run morfo:vocabulary: EXIT 0
- 0 coverage FAILs, 0 coverage WARNs
- vitest src/uix/sema src/uix/morfo: 195/195 pass
- 7 name-shape WARNs remaining are all words.* (separate dev track)
Closes P1 #3 from src/uix/audit-codex-full-1.md (Sema coverage policy).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
b0869878d8 |
docs(audit): mark P1 #4 (non-canonical event verbs) as RESUELTO
Updated section 4 of audit-codex-full-1.md to reflect the closure of P1 #4 in the sema canon sprint (commits |
5 months ago |
|
|
00f0b740e6 |
refactor(sema/docs): apply author's evaluation — status classification + intentRequirement/intentGuidance split
Two changes in one commit:
1. **Doctrina del autor sobre el documento de variaciones**
The author evaluated LIBRO_VARIACIONES_Y_EXTENSIONES.md and gave per-
entry verdicts. The doc is now rebranded, restructured, and classified:
- Renamed: "Registro de desviaciones entre implementación y canon
editorial". Disclaimer at top: not the book, internal registry only.
- Formal status classification (BOOK_CANON / PROJECT_CANON / CANDIDATE
/ LOCAL_EXTENSION / DEPRECATED / ALIAS / IMPLEMENTATION_CONTRACT).
- Per-entry verdicts assigned per author's evaluation. Author's
doctrinal texts included verbatim where given.
- A.6 split per verb (was a single bullet for apply/partial/block/
move/upload; now each has its own status: apply=BOOK_CANON,
move=BOOK_CANON, upload/partial/block=CANDIDATE).
- A.6/A.7 ordering fixed (commit.unselect now A.11 — terminal).
- C.3 split into C.3.a (renames mecánicos) y C.3.b (correcciones
doctrinales — command intent shift).
- E (resumen ejecutivo) actualizado: clear/unselect movidos a
resueltos (estaban contradiciéndose). Pendientes reales listados.
- F + new section G ("Anti-mezclas") con disciplina sobre el doc.
2. **Family policy: split `intentPolicy` en `intentRequirement` +
`intentGuidance`** (D.3 del doc).
La policy actual `'allowed' | 'expected' | 'optional'` mezclaba dos
ejes que el autor pidió separar:
- `intentRequirement: 'required' | 'optional' | 'forbidden'` —
compile-time type constraint.
- `intentGuidance: 'expected' | 'contextual' | 'discouraged'` —
guía doctrinal sin efecto en tipos.
Policy nueva:
| family | requirement | guidance |
|----------|-------------|--------------|
| contact | optional | discouraged | (Cap 22 §11)
| commit | required | expected |
| signal | required | expected |
| handle | optional | contextual |
| emerge | optional | contextual |
| shift | optional | contextual |
| sustain | optional | contextual |
| delegate | optional | contextual | (Cap 29 §4)
`IntentExpectedFamily` deprecated → `IntentRequiredFamily` (alias
mantenido). `IntentPolicy` type también deprecated.
Consumers actualizados: event.ts (isSemaEvent usa requirement),
validation.ts (validateSemaEvent usa requirement), event.test.ts
(tests usan ambos campos), dialog.ts (comment doc update).
3. **Bug menor encontrado en `SEMA_TRANSITIONAL_FAMILIES`**: faltaba
`delegate`. Añadido. (Era inconsistente con SemaTransitionalFamily
type que sí lo incluye.)
Verification:
- vitest src/uix/sema src/uix/morfo: 195/195 pass
- morfo:vocabulary: 7 warns (todos words/*, separate dev track)
- EXIT 0
Pendiente futuro (no en este commit):
- Añadir flag `emission` a MorfoEvent type per D.2 (eventos declarados
pero no emitidos)
- Re-evaluar verbos CANDIDATE (acknowledge/confirm/upload/partial/
block) cuando aparezcan más casos de uso
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
bbfefdb63d |
refactor(morfo): rename 12 events to {family}-{verb}-{variant} canonical shape (Capa 3 cluster otros)
Closes the last cluster of name-shape drift in the non-words morfos. 11
are pure cosmetic renames (declared verb already canonical); 1
includes a doctrinal correction.
Renames (verbo declarado canónico, solo el name):
- carousel.shift-slide → shift-navigate-slide
- feed.shift-focus-item → shift-navigate-focus-item
- feed.commit-load-more → commit-submit-load-more
- file-upload.commit-add → commit-set-add
- file-upload.signal-reject → signal-warn-reject
- form.signal-invalid → signal-warn-invalid
- number-field.handle-scrub → handle-drag-scrub
- range-calendar.commit-start → commit-select-start
- range-calendar.commit-range → commit-select-range
- tags-input.commit-add → commit-set-add
- tags-input.signal-reject → signal-warn-reject
Doctrinal correction:
- command.commit-invoke (declared submit + fulfill)
→ commit-submit-invoke + submit + AFFIRM (not fulfill)
Per book cap. 22 §8 ("Error típico: contact.press + fulfill — esto
hace que el sistema diga 'ya terminó' cuando solo ha empezado.
Contact inicia. Commit resuelve."), the user submitting a command
in the palette does NOT mean the command has completed. Same
precedent as Button: celebrate-before-time is wrong. Affirm
acknowledges the submission; the command's actual completion
(success/failure) fires downstream from whichever morfo owns it.
Consumers updated:
- sema/components/{file-upload,form,number-field,tags-input}.ts
- soma/components/{file-upload,form,number-field,range-calendar,tags-input}/*.ts
- sema/resolver.test.ts (event name + data-event selector)
Verification:
- morfo:vocabulary: warns 19 → 7 (-12), the 7 remaining are all words.*
(separate dev track, excluded). EXIT 0.
- vitest src/uix/sema src/uix/morfo: 195/195 pass
Doc src/docs/LIBRO_VARIACIONES_Y_EXTENSIONES.md C.3 marked RESUELTO
with full list of renames + the command intent correction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
f3641347de |
feat(sema/morfo): add commit.unselect to canon + apply to 6 morfos
User doctrinal decision (2026-05-25): add `unselect` as canonical verb of
the commit family. The pair `select` / `unselect` covers results applied
on the selection state of an item — distinct from `toggle` (binary
control flip) and `remove` (retirement from a collection).
Quoting the author:
"Seleccionar y deseleccionar son resultados aplicados sobre el estado
de selección de un elemento. Eso es commit, porque el resultado queda
aplicado.
- No es remove: no estás eliminando el item ni sacándolo de una
colección funcional; solo estás cambiando su estado de selección.
- No es necesariamente toggle: toggle describe mejor el mecanismo
binario o el control, pero no expresa tan bien el resultado
semántico concreto."
Changes:
- src/uix/sema/verbs.ts: add `unselect` to SEMA_VERBS.commit after
`select` (natural pair). Header comment documents the doctrinal
distinction with toggle and remove.
- 6 morfos updated: calendar, combobox, grid-list, listbox, select,
tag-group. Each changes `commit-unselect` from
`verb: 'remove' + intent: 'neutral'` to
`verb: 'unselect' + intent: 'affirm'`. Names preserved (already
canonical now). Intent `affirm` because "the action applied
correctly" (sobrio, not celebratory).
- src/docs/LIBRO_VARIACIONES_Y_EXTENSIONES.md: C.2 marked RESUELTO with
the full doctrinal reasoning verbatim. A.3 (remove vs delete) updated
to reflect the three-way distinction now formalized
(unselect/remove/delete). New A.7 entry for `unselect` itself with
"para el libro" note (add to Cap 23 §5).
Verification:
- morfo:vocabulary: warns 25 → 19 (-6 unselect cluster), EXIT 0
- vitest src/uix/sema src/uix/morfo: 195/195 pass
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |