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 }
663 Commits (670c54177d55df1bc814d2655536dcd14f511b44)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
670c54177d |
docs: document deviations from the book canon (LIBRO_VARIACIONES_Y_EXTENSIONES.md)
Inventory of every place the UIX project canon diverges from, extends, or interprets «Diseñando lo que ocurre» beyond its literal lists. For each entry: book reference, decision taken, argument, and a pending question for the author to evaluate. Six categories: - A. Verbs added to canon beyond book's literal lists (handle.scroll, commit.acknowledge, commit.remove, commit.confirm, commit.set, commit.apply/partial/block/move/upload) - B. Interpretive adoptions not literally in the book (drag cancellation, programmatic scroll, data resize, sort, auto-revert states, toggle phases, contact intent as visual-only) - C. Open doctrinal questions (clear, unselect, ~19 individual events) - D. Architectural decisions relevant to the book (composition over overload, declared-but-not-emitted events, family policy nuance) - E. Executive summary - F. Disposition options for the author (accept, reject, reformulate, keep as local extension) Author can use this to decide what passes to the book vs what stays as project extension. Until disposition, this doc is source of truth for deviations. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
103c49a7ab |
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>
|
5 months ago |
|
|
a74f26728c |
fix(sema/verbs): validateEventName longest-prefix-match for multi-word verbs
The canon includes two multi-word shift verbs: `enter-mode` and `exit-mode`. `validateEventName` was splitting at the first dash and checking the canon for `enter` / `exit` alone — those aren't canon, so legitimately canonical events like `shift-enter-mode` were being flagged as drift. Fixed by matching the longest canonical verb prefix against the tail (family-verb shape) or against the name (bare verb shape). Handles trailing variants too — `shift-enter-mode-edit` recognises `enter-mode` as the verb and `edit` as the variant. Verification: - 1 new test covering multi-word verb shapes; 19/19 pass - `npm run morfo:vocabulary`: 58 → 57 warns (editable.shift-enter-mode no longer falsely flagged), EXIT 0 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
23afeb6f86 |
refactor(button): align with book cap. 22 — contact.activate, intent visual-only (Plan B commit 2)
Button morfo declared `commit-action` with verb `action` (not in canon) and full intent range. Per «Diseñando lo que ocurre»: - Cap 22 §10: "Botón: contact.press" — the press is reception of the gesture, not the commit. - Cap 22 §11: "el intent fuerte no debería vivir en el contacto, sino en la señal o consecuencia posterior. La interfaz puede anticipar el peso mediante forma, color o señal previa." - Cap 22 §8: "Error típico: contact.press + fulfill — Esto hace que el sistema diga 'ya terminó' cuando solo ha empezado." Strict book reading applied: - Morfo event renamed: `commit-action` → `contact-activate` (family: `contact`, verb: `activate`, no intent binding) - Soma provider trigger string + comments updated - `intent` prop survives as VISUAL signal only — drives `data-color` and chip variant (anticipatory weight via form/color per cap. 22 §11) - README + types JSDoc updated with the book's prescription and the composition pattern: perceptual richness emerges from the OTHER morfos in the flow (Dialog warning, Item being deleted, etc.) firing their own commit/signal events at the actual moment of the consequence - `EVENT_NAME_ALLOWLIST` in morfo-vocabulary-check.ts emptied — no exceptions to the canon needed No `declaresOutcome` props, no imperative sema escape hatches: the morfo remains the single source of truth for events. If a flow loses perceptual differentiation, the answer is to model the missing morfo, not to overload the Button. Verification: - npm run morfo:vocabulary → EXIT 0 (zero allowlisted) - npx vitest src/uix/sema src/uix/morfo → 194/194 pass - npm run check → no new errors from these changes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
af6d6c762a |
refactor(sema): align verb canon with «Diseñando lo que ocurre» (Plan B commit 1)
Codifies the doctrinal source for the semantic system per the user's
book at src/docs/Disenando_lo_que_ocurre_manuscrito_completo_revisado_v2.docx.
Canon (book cap. 8 + cap. 22-29 + cap. 10):
- 8th family `delegate` added (offer/plan/authorize/act/review/escalate/return)
- `signal.inform` added (book cap. 24 listed 7 signal verbs; we had 6)
- Commit contextual verbs from case studies: apply, partial, block, move, upload
- `sustain.upload` added
- SemaFamily union, SEMA_FAMILY_POLICY, sema-map, visual chan, morfo schema
all extended to include delegate (intentPolicy: 'optional' — no intent by
default per cap. 29 §4)
Lint (scripts/morfo-vocabulary-check.ts):
- Hard-fails on declared `semantic.verb` not in family canon (the doctrinal
contract: subscribers query by family+verb)
- Soft-warns on event name shape drift (declared verb is the truth, name is
informational)
- Allowlists `button.commit-action` with TODO referencing Plan B commit 2
(Button's intent vs contact question per cap. 22 §11 needs design sign-off)
3 mechanical violation fixes:
- textarea: `shift-count-overflow` (family shift, verb limit) → `signal-warn-count-overflow`
(signal.warn + risk per cap. 24 — corregible warning, not context shift)
- password-field: `shift-toggle-visibility` → `commit-toggle-visibility`
(commit.toggle + affirm per cap. 23 — state fixation, not context shift)
- password-field: `shift-caps-state` → `signal-notify-caps-state`
(signal.notify + neutral per cap. 24 — system orients attention without urgency)
- month-grid + year-grid: `nav-step` → `shift-navigate-step` (family/verb
already canonical, name aligned to {family}-{verb}-{variant})
Verification:
- npm run morfo:vocabulary → EXIT 0 (1 allowlisted, 59 pre-existing name-shape
warns documenting visible drift for future sprints)
- npx vitest src/uix/sema src/uix/morfo → 194/194 pass
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
928b3c15b6 |
fix(eidos/command): route shortcut keyboard listener via ActiveDom
Cierra audit Codex P1 #2: «command-shortcut.svelte.ts registra window.addEventListener('keydown', ...) directamente». Eidos no debe gestionar DOM activity por su cuenta — todo binding pasa por ActiveDom (regla: «toda DOM activity via ActiveDom»). Cambios: - `command-shortcut.svelte.ts`: signature ahora es `bindCommandShortcut(dom: ActiveDom, shortcut, onTrigger)`. Usa `dom.getWindow()` + `dom.listen(win, 'keydown', handler)` en vez de `window.addEventListener('keydown', handler)`. Retorna la cleanup function de dom.listen (sigue siendo el contrato esperado por el $effect del consumer). - `command.svelte`: pasa `eidos.dom` como primer arg (ya tenia `const eidos = ActiveEidos.require()`). - `command-dialog.svelte`: anadido `import { ActiveEidos }` + `const eidos = ActiveEidos.require()` (no lo tenia). Mismo patron. Beneficios: - ActiveDom owns el lifecycle (cleanup tracked, pause-on-dispose, iframe/popup-aware via targetWindow). - Eidos visual layer queda sin DOM API directo (regla cumplida). - Tests pueden mockear `dom.listen` sin parchear window globalmente. Verificacion: - `grep window.addEventListener src/uix/eidos/components/`: cero matches (solo la mencion en JSDoc del cambio explicativo). - `npm run check`: 0 errors mantenido. - HTTP 200 en `/uix/components/command`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
c85c729dde |
fix(eidos): use ActiveEidos.require() instead of getActiveUix() in 15 components
Cierra P1 #1 del audit Codex: «Eidos no importa `\$active-uix` directamente — consume el runtime via `ActiveEidos.require()` que expone la surface bounded (langs / format / prefs / dom / uix)». `ActiveEidos` ya existe (src/uix/eidos/active-eidos.svelte.ts) y la mayoria de eidos components la usan (accordion, alert-dialog, banner, box, button, calendar, card, ~50 archivos). Estos 15 quedaban como strugglers/adiciones recientes que se saltaron el patron. Patron canonico aplicado en cada archivo: -import { getActiveUix } from '\$active-uix'; -const uix = getActiveUix(); -uix.langs.t(...) +import { ActiveEidos } from '\$uix/eidos'; +const eidos = ActiveEidos.require(); +eidos.langs.t(...) Archivos (15): - badge/badge.svelte - format-date/format-date.svelte - format-number/format-number.svelte - image/image-{error,fallback,_}.svelte (3) - password-field/password-field-caps-lock-indicator.svelte - picker-shell/picker-shell-{cancel,clear,close}.svelte (3) - relative-time/relative-time.svelte - s-text/s-text.svelte - skeleton/skeleton.svelte - spinner/spinner.svelte - trans/trans.svelte Sin nueva surface introducida — todas las usages (langs, format.dates, format.numbers, format.currency) ya estaban expuestas en `ActiveEidos`. Verificacion: - `grep getActiveUix src/uix/eidos/components/ --include='*.svelte'`: cero matches. Quedan referencias en comments/READMEs/types.ts (informativas, no imports). - `npm run check`: 0 errors mantenido. - HTTP 200 en /uix/components/{trans,format-date,format-number,relative-time,badge,skeleton,spinner,image}. Esto es el fix correcto del audit (no la migracion-a-soma del commit anterior que se hizo por mal entendimiento de la regla — revertida en el commit precedente «Revert ... service components from Eidos to Soma»). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
cd2c0326dc |
Revert "refactor(soma/eidos): migrate 4 service components from Eidos to Soma"
This reverts commit
|
5 months ago |
|
|
540abee026 |
refactor(soma/eidos): migrate 4 service components from Eidos to Soma
Architectural cleanup: Trans, FormatDate, FormatNumber, RelativeTime no son visuales — son behavior alrededor del runtime (`uix.langs.t/ts`, `uix.format.*`). Estaban en `eidos/components/` solo porque ahi se inventariaban los wrappers. Naturaleza real: Soma. Beneficio principal: el invariante "Eidos no importa `getActiveUix()`" recupera su pureza — los 4 service components ya no contaban como violación del audit Codex (P1 #1 reducido en 4 archivos + dependencias). Movimientos (4 componentes, 31 archivos staged): - src/uix/eidos/components/{kebab}/ → src/uix/soma/components/{kebab}/ - {kebab}.svelte → components/{kebab}.svelte (subdir convencion Soma) - types.ts → types.ts - README.md → README.md - index.ts → exports.ts (Soma pattern: index re-exports from exports) - + nuevo index.ts: `export * from './exports';` - Eidos dirs eliminados (4 directories). Morfos: scope `['eidos']` → scope `['soma']` (4 archivos): - morfo/components/trans.ts - morfo/components/format-date.ts - morfo/components/format-number.ts - morfo/components/relative-time.ts Demos (4 +page.svelte) updated: - import paths `$uix/eidos/components/{kebab}` → `$soma/components/{kebab}` - meta-pill `scope: eidos` → `scope: soma` Headers de los .svelte updated: "Eidos `<X>`" → "Soma `<X>` — service component" + parrafo explicando por que vive en Soma (behavior, no visual primitive). Verificación: - `npm run check`: 0 errors (mantenido). - HTTP probes /uix/components/{trans,format-date,format-number,relative-time}: todos 200. Sin barrel re-exports a limpiar (no existían). Sin consumers fuera de los 4 demos. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
546aa8d0ee |
fix(soma/eidos): close last 5 check errors — PassthroughProps helper + floating/shell cast
`npm run check`: 5 errors → 0 errors.
floating/shell.ts (Phase 1 — local fix):
- `buildFloatingShellWrapperProps` returned a type where TS couldn't
prove `transform` stayed required after the conditional-object
spread. Runtime preserves the key; we cast at the return boundary so
consumers downstream keep the strict shape.
soma/types/html.ts (Phase 2 — canonical pattern):
- Nuevo `PassthroughProps<T>` helper para resolver el drift Eidos→Soma
estructuralmente. Es `Omit<HTMLAttributes<T>, 'style' | 'id' |
'children' | 'dir' | 'value' | 'placeholder'>`. Las keys excluidas son
las que Soma narrowa en sus Provider types — incluirlas en Eidos
wrappers (via el plain `HTMLAttributes<T>`) producía "Expression
produces a union type that is too complex to represent" y errores de
incompatibilidad al hacer spread.
- Exportado por `soma/types/index.ts`.
eidos picker views (Phase 3 — adopción):
- `date-picker-year-view.svelte` y `date-picker-month-view.svelte`
tipados como `Props = PassthroughProps<HTMLDivElement>` en lugar del
plain `HTMLAttributes<HTMLDivElement>`. Conserva data-*, aria-*,
class, role, tabindex etc. — solo dropea las keys conflictivas.
Pattern reusable: cualquier futuro Eidos wrapper que envuelva un Soma
Provider via `{...props}` debe usar `PassthroughProps<T>` en vez de
`HTMLAttributes<T>`. Documentado en el JSDoc del helper con ejemplo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
ee1afdcfbb |
fix(langs): close P0 #3 — translations 58 errors + 8 warnings → 6 (all words) + 2
Catalogs creados (los 3 que faltaban): - src/uix/langs/components/month-grid.ts (label) - src/uix/langs/components/year-grid.ts (label) - src/uix/langs/components/textarea.ts (label + count) Catalogs con claves anadidas: - field.ts: required-indicator (*), optional-indicator ((opcional)) - time-picker.ts: clock + hour + minute + second + day-period + am + pm + clear + cancel + close - time-range-picker.ts: clock + day-period + clear + cancel + close Common keys anadidas (compartidas entre grid components y picker views): - common.month-grid.prev-year, next-year - common.year-grid.prev-page, next-page Barrel index.ts actualizado con los 3 nuevos catalogs: month-grid, textarea, year-grid. Cleanup de huerfanas que ningun morfo referenciaba (date-picker y date-range-picker tenian clear/cancel/close en el catalog pero el morfo los habia migrado a Button consumer via PickerShell): - date-picker.ts: drop clear, cancel, close - date-range-picker.ts: drop clear, cancel, close Errores restantes (6 + 2 warn) son TODOS de words — out of scope (en desarrollo aparte, no se toca esta sesion). Pre: 58 errors + 8 warnings (translations:check) Post: 6 errors (words) + 2 warnings (words) Net: 0 errores y 0 warnings de no-words. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
5256008700 |
fix(audit-codex): resolve P0 blockers + close most check errors (42 → 5)
Codex audit P0 items addressed: P0 #1 - color-picker morfo invariante (`morfo:vocabulary` failed): - close-commit / close-cancel / close-dismiss / dismiss-outside prewrites escribian `data-last-action` en Content sin declararlo. Anadido a la declaracion de `data` con values [committed/cancelled/dismissed/dismissed-outside], severity optional. P0 #2 - password-field `kind: 'private'` rechazado por tipo: - `MorfoPartKind` extendido de `'public' | 'virtual'` a `'public' | 'private' | 'virtual'`. Semantica: `private` = en contrato (eidos lo estila, validador lo chequea) pero no compuesto por consumer (oculto en docs). Justifica el StrengthMeterLabel sub-part. P0 #2 - Soma `defaultElement: 'time'` rechazado: - `MorfoElement` extendido con `'time'`. format-date y relative-time morfos lo usan. P0 #2 - Picker family drift (12 errors): - `DatePicker/DateRangePicker/TimePicker/TimeRangePicker.ProviderProps` pasaron de "virtual composition shell" (sin WithChild) a `WithChild<Omit<PrimitiveDivAttributes, 'value' | 'placeholder'> & {...}>` para alinear con la impl real que renderiza <div>, expone ref/child y acepta data-* attrs (mismo patron que date-field/time-field). - contracts.test.ts §1.1.C actualizado: ahora exige WithChild + DOM passthrough en lugar de prohibirlos. Coherente con date-field/time-field. P0 #2 - Eidos year/month-view en date-picker pasaba `size` a Soma: - Soma's DatePicker.YearView no modela `size` como prop (delega a YearGrid.Provider que tampoco). Cambiado a `data-size={...}` para que fluya por DOM passthrough hasta el recipe eidos que keys por `[data-size]`. P0 #2 - `KEYS.Enter` y `KEYS.Space` no existen: - table-provider usa `KEYS.ENTER` y `KEYS.SPACE` (ya correctos en consts). P0 #2 - `Snippet` import faltante en time-range-picker types. P0 #5 - tmp/lexical contaminaba `npm run check`: - `tsconfig.json` anadido `exclude: ['tmp/**', ...]`. svelte-check ya no carga los `svelte.config.js` de lexical demos (faltaban deps). Errores mios introducidos por el sprint reciente: - `DemoLocale` no asignable a `SupportedLocale`: cambiado tipo del prop `locale` en FormatDate/FormatNumber/RelativeTime a `string` (acepta cualquier BCP-47, Intl no necesita restricciones de catalogo). - `RelativeTimeUnit` con `'auto'` no asignable a `Intl.RelativeTimeFormatUnit`: `resolveUnit()` ahora tipa el retorno como `Exclude<RelativeTimeUnit, 'auto'>`. Errores pendientes (5): - `floating/shell.ts:92` - wrapperProps requiere `transform: string | undefined` (no optional) pero buildFloatingShellWrapperProps devuelve optional. - `date-picker-year-view.svelte` / `date-picker-month-view.svelte` x2: Soma YearView/MonthView accepta `dir: 'ltr' | 'rtl' | undefined` pero HTMLAttributes provee `'ltr' | 'rtl' | 'auto' | null | undefined`. Conflict en passthrough. Estos 3 son drift entre Soma type strict y HTML passthrough laxo. Necesitan diseno mas cuidadoso (audit recomienda "resolver como familia"). Estado: 42 errors → 5 errors. morfo-check pasa. morfo:vocabulary pasa (con warnings ya conocidos de words). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
9b809e4ecb |
chore(eidos): drop \$app/environment imports (decouple from SvelteKit)
Eidos es la capa visual y debe ser framework-agnostic. Tres componentes
metian `import { browser } from '\$app/environment'` por reflejo defensivo
SSR sin pensar la consecuencia.
relative-time.svelte:
- Drop completo del guard. El `\$effect` solo corre client-side en
Svelte 5; el `if (!browser) return` era codigo muerto.
s-text.svelte + s-text-virtual-list.svelte:
- Aqui si hace falta guard (los `\$derived` corren durante SSR para
inicializar valores; `getComputedStyle(el)` lanza si lo invocas en
server). Pero reemplazo `browser` con `const isBrowser = typeof
window !== 'undefined'` local — mismo comportamiento, cero coupling
con SvelteKit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
b54d42e230 |
feat(format/dates): add formatRelative to engine + route RelativeTime through runtime
$libs/days/format.ts:
- Nuevo `getCachedRelativeTimeFormat(locale, options)` — twin del cache
ya existente `getCachedDateFormat`. Key `${locale}:${JSON.stringify(opts)}`.
Sites que renderizan listas de timestamps relativos (feeds, activity
logs) hit identical pairs y mantienen el loop caliente.
arts/format/dates/engine-dates.ts + types.ts:
- Nuevo `formatRelative(value, unit, options?, locale?)` en EngineDates.
Defaults: `numeric: 'auto'` (colapsa 0/-1/+1 en "yesterday"/"today"),
`style: 'long'`. Per-call locale override no muta engine state.
arts/format/dates/active-dates.svelte.ts:
- formatRelative wired via `readFrom(runtime, engine.formatRelative)`
para que el $derived del componente sea reactivo al cambio de locale.
Componente relative-time.svelte:
- Drop del bypass directo a `new Intl.RelativeTimeFormat(locale, opts)`.
Ahora SIEMPRE rutea por `uix.format.dates.formatRelative(magnitude,
unit, { numeric, style }, locale)`. El componente sigue haciendo la
matematica del delta + auto-unit + magnitud — el engine solo expone
la operacion atomica.
- Fallback a Intl directo solo cuando no hay runtime (eidos fuera de
UixApp).
Tests: 17/17 en engine-dates.test.ts (+3 nuevos: formatRelative con
locale del engine, numeric/style options, per-call locale override sin
mutar state).
Docs: arts/format/README.md sub-seccion en Dates con la nueva API.
relative-time/README.md decisiones actualizadas (ya no es la excepcion).
Cierra el ultimo follow-up del sprint de service components — los 4
componentes (Trans, FormatNumber, FormatDate, RelativeTime) ahora
rutean uniformemente por el runtime UIX.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
7b01397aab |
feat(format/numbers): per-call locale override (parity with dates engine)
Engine (`arts/format/numbers/engine-numbers.ts` + `types.ts`): - format(value, options?, locale?) acepta tercer arg locale como en dates. Cache (getCachedNumberFormat) keyea por (locale x options). - formatPercent / formatCompact / formatCurrency / formatUnit propagan el locale al format() interno. - No muta engine state (getLocale() sigue devolviendo el activo). Component (`format-number.svelte`): - Drop del bypass `new Intl.NumberFormat(locale, options)` cuando habia locale prop. Ahora SIEMPRE rutea por numbers.format(inputValue, options, locale). Preserva cache + separadores activos + defaultFormat + reactividad del locale. - Fallback a Intl directo solo cuando no hay runtime (eidos fuera de UixApp). Tests: 9/9 en engine-numbers.test.ts (+4 cubriendo format/Percent/ Compact/Currency/Unit con locale override sin mutar state). Docs: arts/format/README.md sub-seccion "Override de locale por llamada" en Numbers (handoff 2026-05-25). format-number/README.md decisiones actualizadas. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
89f52c5e88 |
feat(eidos): Card, Avatar ext, Image, SText, Badge, service components + dates engine fixes
Sprint completo: - Card (eidos + sema): elevation/intent/interactive, eventos select+activate. - Avatar: ring, AvatarBadge, AvatarGroup separados; demo Avatar standalone. - Image: status surface (idle/loading/loaded/error) consumiendo soma ImageProvider. - SText + STextVirtualList: canvas-text engine portado de morfo-driven-soma como componentes separados para optimizar bundle (no se importa canvas si no se usa). - Badge: primitivo eidos puro. - Trans, FormatNumber, FormatDate, RelativeTime: 4 service components greppables alrededor de uix.langs y uix.format. Nav group "Utilities". Bugs fix arts/format/dates (engine-dates.ts): 1. Preset / per-axis conflict: Intl.DateTimeFormat rechaza dateStyle/timeStyle con cualquier per-axis (weekday/era/year/month/day/dayPeriod/hour/minute/ second/fractionalSecondDigits) o con timeZoneName. Engine drop-ea ambos presets por defecto cuando detecta conflict (regla estricta — un solo per-axis hace ilegales los dos presets). 2. Per-call locale override: formatDate/Time/DateTime aceptan tercer arg locale?: string. No muta engine state. Cache (getCachedDateFormat) keyea por (locale x options). resolveCallHourCycle: cuando hay locale override y pref auto, el ciclo horario se resuelve desde el locale OVERRIDE. 3. withHourCycle precedence: caller hourCycle/hour12 ahora gana sobre preferencia del engine. Antes spread mal ordenado sobrescribia 'h12' del caller con 'h23' del engine cuando locale resolvia a 24h. FormatDate component vuelve a rutear siempre por uix.format.dates (cache + hourCycle + locale reactivo), pasando locale como tercer arg. Demos service components autonomos del toolbar global: cada uno owns su demoLocale state con 7 chips (es-ES/en-US/en-GB/de-DE/fr-FR/ja-JP/ar-EG). FormatDate stage filtra dateStyle/timeStyle por mode + chip hourCycle anadido. Tests: 14/14 passing en engine-dates.test.ts (+6 nuevos: conflict handling, locale override, hourCycle precedence). Docs: arts/format/README.md (handoff 2026-05-25 con las 3 reglas), README.md por service component, project_service_components_sprint_2026-05-25.md, feedback_service_demos_own_locale.md (pattern reusable). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
0804749147 |
Words: text alignment, heading picker, ToolPopover cleanup, word boundary fixes
- Add textAlign to document model (paragraph, heading, quote) - Add setTextAlign engine operation with left/center/right/justify - Add alignment toolbar buttons with icons (TextAlignStart, Center, End, Justify) - Update formatting and full presets to include alignment - Fix sameBlockShape to compare textAlign correctly - Add heading levels h2/h3 with Words.HeadingPicker dropdown (Popover) - Remove Words.ToolPopover component; migrate LinkEditor to native Popover - Fix word boundary algorithm: Intl.Segmenter + CJK/punctuation fallback - Fix bubble menu isSelecting logic and pointer event forwarding - Add 5 engine tests for setTextAlign - Update demo presets for alignment - Words engine tests: 42/42 passing |
5 months ago |
|
|
cc0d38ec52 |
refactor(floating): extract createFloatingShellRoot + buildFloatingShellWrapperProps helpers (audit Round 3 §3 #1)
7 popover-based soma providers (popover, dropdown-menu, context-menu,
combobox, select, tooltip, link-preview) shared two byte-identical
blocks that an investigation report (delegated Plan agent) confirmed
as real duplication after looking carefully past the audit's headline
"600 lines across 15 providers" — the actual scope was ~230 lines
across 7 providers (the audit overcounted by ~2x, similar to the
BaseSegmentProvider case).
Two narrow helpers added in `src/uix/soma/layers/floating/shell.ts`:
- `createFloatingShellRoot({ dom, open, contentRef, onOpenChangeComplete })`
bundles `FloatingProvider.create({ dom })` + the `contentPresence`
constructor (which were verbatim across all 7 providers, byte for
byte). Returns `{ floatingProvider, contentPresence }` the consumer
assigns to `this.*` fields.
- `buildFloatingShellWrapperProps(floating, pointerEvents = 'auto')`
returns the canonical wrapperProps shape (spread floating's
wrapperProps + `style.pointer-events` forced to a value). Identical
in 6 of 7; tooltip parameterises `pointerEvents` to flip on
`hoverableDisabled`.
Explicitly NOT abstracted: FocusScope, Dismissal, ScrollLock,
TextSelection — these look similar but diverge per provider (modal-
derived `trap`, custom `isValidEvent` closures, different close
callbacks — see Round 3 audit analysis). Trying to hide them would
recreate the BaseSegmentProvider trap of flag bloat.
Notable variations preserved:
- popover keeps its `overlayPresence` (popover-only chrome) outside
the shell helper — only `contentPresence` is shared.
- context-menu's SubContent sub-provider also uses the same wrapper
helper (its FloatingProvider.create stays inline because it reads
`this.provider.soma.dom` not `this.soma.dom`).
- dropdown-menu has both a Content and SubContent wrapperProps —
both routed through the helper.
Test result: 2394/2399 passing — no regressions in the 27 tests
across the 7 affected providers. The 5 fails remain Words + cookie
infra (cookie is flaky; sometimes 6, sometimes 5).
Closes the last item of Kim audit Round 3 §3. Net code reduction in
the 7 consumer files is ~85 lines; helper file is 96 lines with
docblocks.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
bf7e116af9 |
refactor(soma): route FieldProvider through field/internals barrel + register DRP view sub-parts (audit Round 3 §3 #23 + P4 follow-up)
Two related housekeeping items in one commit: **1. FieldProvider via barrel (audit Round 3 §3 #23)** 37 consumer files across soma imported `FieldProvider` from the deep path `'../field/field-provider.svelte'`, mirroring the same pattern that motivated `internals.ts` indirection on the 5 pickers earlier. Adds `src/uix/soma/components/field/internals.ts` re-exporting `FieldProvider` and exposes it from the barrel `exports.ts`. The 37 imports become the canonical `from '../field'`. Consistency only — no behavioral change. **2. DateRangePicker view sub-parts (P4 follow-up)** The MonthView/YearView soma parts created in commit `e93c0c73` stamp internal layout data-attrs (`-header`, `-prev`, `-heading`, `-next`, `-grid`, `-cell`) that weren't declared in the morfo, surfacing as 12 hardcoded-data-attr violations once the contract suite caught up. Declares the 12 sub-parts in the morfo with the canonical kebab prefix (`month-view-*`, `year-view-*`). Each entry minimal (data: [], aria: []) except `*-prev` / `*-next` which stamp `type='button'` and `*-cell` which surfaces the `data-range-{start,end,in-range}` classification states used by the eidos range tinting CSS. Test result: 2393/2399 passing — back to the canonical 6 fails (Words + cookie infra). The 13 transient regressions are closed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
3a23fa239b |
docs(pendientes): downgrade BaseSegmentProvider from 'diferir' to 'descartar' — false positive
Deep re-analysis of the date-field (291 lines) and time-field (250
lines) abstract bases — claimed by Kim Round 3 as ~300 lines of
duplication — shows the duplication is superficial:
- ~50 lines structurally identical: constructor, announcer/getMin/
getMax/directionalKeys getters, keydown router skeleton.
- ~240 lines with same names + same general shape but genuinely
divergent IMPLEMENTATIONS:
* handleNumberKey: date has lastKeyZero, hasLeftFocus, special
cases for hour cycle 24, minute/second-zero (47 lines).
Time uses candidate-based prev*10+num (37 lines).
* handleArrowUp/Down: date uses cycleNumericSegment with
DateValue.cycle wraparound. Time uses direct arithmetic.
* handleBackspace: date has '0X' → null special case. Time
doesn't.
* formatValue: date has forDisplay flag. Time doesn't.
* getAnnouncement exists in date, not in time.
* getSegmentProps: date interplays placeholder.set + hour/
dayPeriod intermix. Time is a flat read.
Forcing a common abstract base would require configuration flags
(useLastKeyZero, useCycleHelper, usePlaceholderGuard,
useDayPeriodCascade, ...) that would create a God-Object worse than
the current state. The two classes have legitimately evolved to
distinct behaviors:
- DateField operates over DateValue (CalendarDate/DateTime) with
cycle wraparound, day clamping, dayPeriod intermix — complex
cross-segment state.
- TimeField operates over standalone Time values without DateValue,
without cascades — simple per-segment state.
Marking the item as **descartar** (discard, not just defer). If a
genuine sharing need emerges later, attack only the keydown router
skeleton (~50 lines real overlap, not the 300 the audit suggested).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
3c87993513 |
refactor(date-field): split updateSegment into pipeline helpers (audit Round 3 §3 #22)
The 96-line, 7+ branch updateSegment was doing three things in one
method: running the consumer callback, applying cross-segment
cascades (month→day, hour→dayPeriod, dayPeriod→hour), and committing
the new value with sema event dispatch. Reads were dense and the
cascade duplication between the DateAndTime branch and the Date-only
branch was hidden inside parallel if-trees.
Splits into 4 private helpers + a leaner orchestrator:
- `cascadeMonthToDay(prev, nextMonth, dateRef)` — when month
changes, clamp `prev.day` to the new month's length (e.g. day=31
on Feb → 28). Same helper used by both top-level branches.
- `cascadeHourToDayPeriod(prev, nextHour, dateRef)` — derive AM/PM
from the formatter when the hour crosses 12.
- `cascadeDayPeriodToHour(prev, nextDayPeriod)` — toggle the
displayed hour ±12 when AM/PM flips, using the *committed*
value's true hour as the reversible source.
- `setSegmentUpdating(part, next)` — stage in-flight value in
`this.states[part].updating`. No-op when the segment has no
staging slot (e.g. literals).
- `commitSegmentValues(newSegmentValues, previousValue)` — write
segmentValues, derive value from DOM order when all required
segments filled, emit `commit-set`/`commit-clear` only when the
stringified value actually changes.
Main `updateSegment` body now reads as a 3-phase pipeline (compute →
cascade → commit) instead of a 7-branch if-tree. Behavior is
preserved bit-for-bit — verified by date-field, date-picker,
date-range-picker test suites (15/15 passing).
Test result unchanged at the suite level: 2393/2399 passing (the 6
fails are Words + cookie infra).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
c1301fb550 |
refactor(soma): extract ListSelectionHelper for combobox/select (audit Round 3 §3 #21)
ComboboxProvider.selectItem and SelectProvider.selectItem were
structurally identical: both ran the same single/multi state machine
with allowDeselect semantics, the same resolve-DOM-element fallback,
and the same `commit-select`/`commit-unselect` event dispatch. The
only divergence: Combobox additionally syncs `inputValue` to the
selected label after the state mutation.
Extracts the shared logic into a new pure module
`src/uix/soma/layers/list-selection.ts`:
- `computeListSelection({ current, value, type, allowDeselect })`
→ `{ next, event, shouldClose, skipUpdate }`. Pure function, no
state writes, no DOM. The caller applies `next` to its own
`opts.value.current` after running any component-specific side
effects (Combobox: inputValue sync). `shouldClose`/`skipUpdate`
are decoupled so callers can compose their own order.
- `resolveListItemEl(root, itemAttr, value)` — DOM lookup helper
for the fallback event target. CSS.escape-safe.
Both providers now thin out to ~20 lines for selectItem (down from
~40-50). The single-mode no-op branch (re-select with deselect
disabled) and the early-return ordering are preserved exactly — close
fires once, value writes only when there's a real change.
Test result: 2393/2399 passing (2 extra from the new module's coverage,
6 same fails are Words + cookie infra).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
00cd3a2c61 |
docs(pendientes): close Kim Round 3 — item 28 done, items 19-24/27/29 deferred
Closes Kim Round 3 item 28: replace phantom `defaultCalculateStrength`
identifier in PasswordField docblocks with the actual export name
(`passwordStrength` from `$libs/secs`, aliased as `calculateStrength`
when the option is omitted). Docblocks in `password-field-provider.svelte.ts`
and `types.ts` no longer reference a non-existent symbol.
Documents the remaining Round 3 architectural debt as `diferir` in
`src/uix/PENDIENTES.md` under a new "Kim audit Round 3 — deuda
arquitectónica restante" section:
§3 Code duplication (no contract test catches these):
- FloatingShellProvider factory (~600 lines across 15 providers)
- BaseSegmentProvider (date-field 1674 + time-field 1072,
~300 duplicated)
- ListSelectionHelper (combobox + select, ~80 duplicated)
- DateFieldProvider.updateSegment (96-line monolith, 7+ branches)
- 37/42 cross-component soma deep imports of *-provider.svelte
- Redundant $effects in date-field, time-field, drawer, command
§6 Greenfield guardrails on brownfield code:
- Contract tests force final-state strictness on transitional code.
Three deferred options: EXEMPTIONS list, accelerate migration,
or split core/housekeeping tests.
§7 P4 items 27 & 29:
- data-last-action vocabulary divergence (saved vs committed) —
design decision, CSS impact in dialog/drawer.
- SEMA_VERBS expansion — needs prior audit of declared vs used.
Test result unchanged: 2391/2397 passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
d4e668b234 |
refactor(soma): revert context.set try/catch; mock pickerShellContext explicitly in picker tests
Per Kim audit Round 3 §2: the broad try/catch around `ctx.set` in
`src/uix/soma/provider/context.ts` (introduced in commit `12a7e5cf`)
suppressed Svelte's `lifecycle_outside_component` error globally —
fixing 5 picker test files at the cost of silently no-opping the same
error anywhere in production code that might violate component
lifecycle. Aggressive in scope for what was actually a test-harness
omission.
This commit:
- Reverts `context.ts` to the plain `ctx.set(value)` pattern. Other
error semantics (the typed `get()` throw + `getOr<U>` fallback)
stay intact. Adds a docblock noting that providers calling
`ctx.set(...)` in their constructor must be instantiated from a
Svelte component scope (or mocked equivalent in tests).
- Adds `vi.spyOn(pickerShellContext, 'set').mockImplementation((v) => v)`
to the 5 picker provider test harnesses (color, date, date-range,
time, time-range). Mirrors the pattern those tests already used
for their own XProvider.ctx — explicit, scoped to tests, visible.
Test result unchanged: 2391/2397 passing (the 6 remaining are Words +
cookie infra, both out of scope). No regressions in the picker tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
cfbe754299 |
docs(pendientes): add performance bottleneck analysis for docs site
User reported slow web load + Chrome `[Violation] 'setTimeout' handler
took 78ms`. Profiled the critical path of `/uix/*` layout and documented
4 ranked bottlenecks as **diferir** (deferred to pre-release polish
since UIX is still in framework-dev phase):
1. 744 KB render-blocking CSS — entire eidos recipe catalog `@import`-ed
in one bundle for every uix page.
2. `ActiveEidos.create({ applyDom: true })` re-generates the foundation
CSS at runtime, duplicating the bundled stylesheet. Likely source
of the 78ms violation.
3. 28 sema modules + 71 lang files eagerly imported at layout boot.
4. 100+ sidebar links with `data-sveltekit-preload-{code,data}="hover"`
flooding the network on rail mouseover.
Each entry documents the file:lines, why it's expensive, and the
recommended architectural fix. Ordered cost-to-impact for when polish
sprint starts.
Also includes the pre-existing Soma/Popover audit deferral notes that
were sitting uncommitted in the working tree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
91ecf87cd5 |
fix(pendientes): tooltip Play buttons + range view-cell tint differentiation
Two items from src/uix/PENDIENTES.md:
- Tooltip D-4.3: demo now exposes ▶ play buttons in both the Morfo
events table and the Sema tab. Each row dispatches the declared
event (open / close / close-dismiss) against the live stage via
`uix.events.emit({ name, family, target })`. Updated stale
"0 events" / "Presence-only — no sema events" copy to reflect the
actual 3 events from the morfo.
- DateRangePicker view-cell tint: `[data-in-range]` cells in the
year-view / month-view grids were using
`color-mix(in srgb, var(--color-primary-solid) 22%, transparent)`
(a saturated wash from scale-9). Switched to
`var(--color-primary-track)` + `var(--color-primary-text)` — the
canonical scale-3 "track" tone the day-grid range already uses.
Endpoints (start / end) keep their solid-primary fill so the
contrast with the in-range middle is now significantly higher.
Test result unchanged: 2391/2397 passing. No regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
e93c0c7337 |
refactor(date pickers): promote MonthView/YearView to soma parts (audit 5.3 P4)
Eliminates `Provider.require()` from 4 eidos date-picker views by
promoting them to canonical soma parts. Architectural 3-layer split:
$libs/days → pure date math
soma → state machine + behavior
eidos → paint (pure passthrough)
Pure-math helpers added to $libs/days (queries.ts):
- startOfDecadeYear(year): year - (year % 10)
- monthOrdinal(year, month): year * 12 + (month - 1)
Both used to be reinvented inline in the eidos date-range-picker views.
New soma parts:
- <DatePicker.MonthView> bridges CalendarDate ↔ MonthValue, calls
handleDateSelect(), renders default MonthGrid composition.
- <DatePicker.YearView> symmetric bridge to YearGrid.
- <DateRangePicker.MonthView> owns the empty/pending/complete range
state machine, year stepper, classify(). Endpoints normalise to
day=1 (start) and endOfMonth(...) (end).
- <DateRangePicker.YearView> owns the range state machine with
decade stepper. Year endpoints normalise to Jan 1 / Dec 31.
All four parts declared in their morfos with `kind: 'public', optional`.
The range views stamp `role='grid'` + canonical aria-label.
Eidos becomes pure passthrough: 4 files, each ~15 lines, no JS reads
from soma providers. Visual context still resolves `size` for the
date-picker variants (forwarded to MonthGrid/YearGrid).
Test result unchanged: 2391/2397 passing. No regressions.
This closes audit 5.3 Option C — all 8 sites refactored.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
a5243168e5 |
refactor(time-picker): promote HourScale ruler to soma part (audit 5.3 P2)
Eliminates `Provider.require()` from eidos hour-sliders by promoting
the tick scale to canonical soma parts:
- `<TimePicker.HourScale>` — exposes `{ ticks, percent, label, cycle,
min, max }` via a snippet so eidos can paint the ruler without
importing TimePickerProvider.
- `<TimeRangePicker.HourScale>` — same shape for the range variant.
Scale is global (both endpoints share the cycle), so no endpoint
prop.
Eidos hour-sliders become pure composition + paint via snippet props.
No JS coupling between eidos and the soma provider class.
Both new parts declared in their morfos as `kind: public, optional`,
with `kebab: 'hour-scale'`. Stamp `data-time-picker-hour-scale` /
`data-time-range-picker-hour-scale` plus `aria-hidden="true"`.
Test result unchanged: 2391/2397 passing. No regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
74ccfead70 |
refactor(color-picker): stamp current-color in soma trigger (audit 5.3 P1)
Eliminates `Provider.require()` from eidos `<ColorPicker.Trigger>` by
having soma's TriggerProvider stamp `--cp-current-color: {hex}` as
inline style on the trigger element. Eidos paint becomes pure CSS —
reads `var(--cp-current-color)` from CSS, no JS coupling.
Also unifies the CSS var name: the eidos trigger dot now reads
`--cp-current-color` (matching the ValueSwatch which already used this
name) instead of the eidos-defined `--cp-trigger-color`. One canonical
"current colour" var across the component.
Test result unchanged: 2391/2397 passing. No regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
a15b785cc1 |
refactor(table): promote sort affordance from eidos to soma part (audit 5.3 P3)
Eliminates `Provider.require()` from the eidos `Table.SortButton` by
promoting the affordance (canSort guard + click/keyboard handlers +
data-sortable/data-sorted attrs) to a new soma part `<Table.SortTrigger>`.
Architecture:
- Soma owns interaction: TableSortTriggerProvider reads canSort/sorted
via the existing TableProvider context, wires onclick + Enter/Space
to call `table.toggleSort`, and stamps the canonical data-attrs.
- Eidos becomes pure paint: SortButton renders chrome (label + chevron)
via the `children` snippet that receives `{ canSort, sorted }`.
Public API of eidos `<Table.SortButton>` is unchanged — consumers continue
to write `<Table.SortButton {header}>{header.label}</Table.SortButton>`.
CSS selectors in eidos table.css updated from `[data-table-sort-button]`
to the canonical `[data-table-sort-trigger]` stamped by the morfo runtime.
Test result unchanged: 2391/2397 passing. No regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
f13103e9a9 |
fix(uix audit): align morfo sema scope + remove Provider leak on 3 eidos namespaces
Section 4.1 — Add 'sema' to scope of 13 morfos that already emit events but were registered as soma-only: command, table, grid-list, tree-view, tree-grid, navigation-menu, menubar, carousel, feed, drag-drop, color-field, time-field, tooltip. Section 4.2 — Remove 'sema' from scope of 3 morfos that declared no events: alert-dialog, pin-input, date-range-field. Section 5.1 — Drop `.Provider` namespace member from table, virtual-list, virtual-grid eidos public surfaces. The eidos Root itself IS the provider wrapper, so re-exposing `Provider = Root` violates `eidos/components/README.md` rule 3. Internal usages of `<Table.Provider>` in the eidos wrappers refer to the SOMA import, not the eidos namespace — verified no external consumers. Test result unchanged: 2391/2397 passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
12a7e5cfd3 |
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
bb56e94864 |
fix(password-field): chrome layout + meter visibility + form field styling
Three real visual bugs caught after browser verification:
1. **No visible border around input + meter row.** The CSS used
`:has([data-password-field-strength-meter])::before` to lift the
border onto a pseudo-element that spans grid row 1. The pseudo
collapsed to 2px tall because it had no `align-self: stretch` and no
explicit `min-block-size`. Added both — the pseudo now fills its
grid cell at the full `--_password-field-height`. Plus the same
`data-focused` / `data-invalid` state propagation already in place.
2. **Strength meter segments invisible.** Default
`--password-field-strength-segment-bg` resolved to
`var(--color-neutral-track)` which is `rgb(17,17,17)` — identical to
the surface in dark mode. Changed to `var(--color-border-default)`
so inactive segments read as visible grey rails. Also bumped segment
height from 4 → 6 px so the bar is clearly readable.
3. **Eye icon too small inside a thin trigger.** `trigger-size`
resolved to `--icon-size-md` (≈18 px) which made the touch target
too small and the SVG cramped. Promoted trigger-size to use
`--control-height-{xs..md}` so it matches the input row height per
size. SVG sizing switched from `calc(size - 8px)` (which clipped at
small sizes) to `65%` of trigger.
4. **Stage wrapper collapsed to content width.** The inner
`<div style="max-inline-size: 24rem; margin-inline: auto">` lacked
`inline-size: 100%`, so inside the stage's `display: grid` it
shrank to its content. Added the explicit `inline-size: 100%`.
5. **Username/Email rendered as native unstyled inputs** in the
Form/SIUM demo because they wrapped raw `<input>` instead of
`<Field.Input>` (Field.Control only styles its dedicated Input
subpart, not arbitrary children). Swapped to `<Field.Input>`.
6. **SIUM error refs (`#?sium.errors.min_length|...`) rendered
literal.** `Field.ErrorText` just renders the string it receives.
Added a local `issueText(form, field)` helper that calls
`uix.langs.ts(raw)` to resolve the langref and then interpolates
`{{param}}` from `form.issues[field][0].params`. Now errors render
as proper Spanish/English text from the registered siumLangs.
Verified visually via Claude_Preview: input row has a clean border with
the eye icon at the right edge, strength meter sits below with 4 visible
segments + label, all three form columns render with consistent chrome.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
90afe27d81 |
fix(password-field): visual issues — double border, broken layout, stale lang refs
Three visual bugs spotted on /uix/components/password-field:
1. **Double-bordered inputs.** The demo wrapped `<PasswordField>` inside
`<Field.Control>`, which adds its own border + padding. PasswordField
IS the field's input shell — it ships its own chrome. Removed the
five offending Field.Control wrappers from login/registration/change
forms; PasswordField now sits directly inside `<Field>`.
2. **Icon wrapping to a new row + StrengthMeter sharing the bordered
box.** Replaced `display: inline-flex; flex-wrap: wrap` on the root
with `display: grid; grid-template-columns: minmax(0, 1fr) auto auto`
so Input + CapsLockIndicator + VisibilityTrigger stay on the first
row. When StrengthMeter is composed, `:has()` switches the root to a
2-row grid where the bordered chrome lifts onto a `::before`
pseudo-element that only spans the first row, and the meter sits
below as a sibling. State chrome (`data-focused`, `data-invalid`)
propagates to the pseudo via the same `:has()` selectors.
3. **CapsLock pill claiming space even when inactive.** Was
`color: transparent; background: transparent` which still consumed
width in the grid. Now `display: none` by default and
`display: inline-flex` only when `[data-active]` is set.
Also fixed: `#?sium.errors.min_length|…` and friends rendered literal
in the demo because the docs shell didn't register `$sium/langs/langs`.
Registered `siumLangs` under the `sium` namespace in the layout's
`createActiveUix({ langs: { schema: { sium: siumLangs } } })`.
svelte-check: 0 errors. Page now loads at 200.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
314f1cd523 |
fix(eidos): rename `children` prop in 4 wrappers to avoid snippet self-shadow
Critical SSR crash — Maximum call stack size exceeded on
/uix/components/password-field and /uix/components/textarea.
Root cause: DEMO_AUTHORING_GUIDE §12.4 pitfall.
When a wrapper does:
let { children, ...rest } = $props();
<Inner>
{#snippet children(snippetProps)}
{#if children}
{@render children(snippetProps)} ← recurses
{:else}...{/if}
{/snippet}
</Inner>
The inner `{#snippet children}` SHADOWS the `children` prop. The
`{#if children}` check then references the new (always-truthy) snippet,
and `{@render children(...)}` recursively renders itself → infinite
loop, RangeError at SSR.
Fix: destructure the prop as `children: outerChildren` so the inner
snippet doesn't shadow it. Same pattern used in drawer wrappers per
the guide.
Files patched:
- src/uix/eidos/components/password-field/password-field-caps-lock-indicator.svelte
- src/uix/eidos/components/password-field/password-field-visibility-trigger.svelte
- src/uix/eidos/components/password-field/password-field-strength-meter.svelte
- src/uix/eidos/components/textarea/textarea-count.svelte
Also restored the Form + SIUM integration section in the
password-field demo that was removed temporarily for debugging.
Verified:
- /uix/components/password-field → 200
- /uix/components/textarea → 200
- npx svelte-check → 0 errors
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
8d277fe929 |
fix(password-field): align input chrome with system + add Form/SIUM demo
Style fix — input chrome was hand-rolled with raw color tokens (`--color-neutral-border`, `--ring-width`) instead of consuming the recipe layer like every other text input. Now matches search-field and number-field exactly: - Added `password-field` recipe to `src/uix/eidos/lib/recipes/base.ts` with the same surface as `search-field` (height, px, gap, font-size per size; border, focus, invalid, bg, color, focus-shadow tokens). - Regenerated `src/uix/eidos/generated/base.css`. - Rewrote `password-field.css` to consume the new tokens — same focus ring (`focus-shadow`), same border colors, same disabled opacity, same transition tokens. VisibilityTrigger gets its own border/bg/ color-on-hover scheme aligned with search-field's clear button. - Added per-size icon + caps font-size tokens so the trigger and caps pill scale with the input. Form + SIUM demo — answers "how does PasswordField validate?": Added a new subsection in the Live tab with three working forms wired through `<Form>` + `<Field>` and SIUM schemas: 1. Login (purpose='current', non-empty password) 2. Registration (purpose='new', complexity rules via regex pipes — uppercase + digit + symbol + 8+ chars, plus the visual StrengthMeter as a companion to the schema) 3. Password change (3 fields with a cross-field `refine()` so new === confirm; demonstrates SIUM's refine pattern) The section explicitly documents that PasswordField has no built-in complexity rules — validation is schema-first. The schema decides what's "correct"; PasswordField's flags propagate via Field's OR-merge of `formFieldState.isInvalid`. svelte-check: 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
da3e5026bb |
feat: build PasswordField (full stack)
Dedicated password input component — matches the Ark UI / Chakra v3 /
Mantine / PrimeReact / Ant Design pattern of treating PasswordField as
its own first-class component (not a TextField variant). Includes one
feature that NO surveyed library ships: a CapsLock indicator driven by
KeyboardEvent.getModifierState('CapsLock').
Anatomy (5 parts, 4 events):
- Provider (root, value + visibility state)
- Input (the textbox; type flips between password / text)
- VisibilityTrigger (eye toggle button; aria-pressed + aria-controls)
- CapsLockIndicator (status live region; data-active toggles via soma
listening to keydown/keyup on Input)
- StrengthMeter (composable; consumer provides 0-4 score; UIX bundles
no zxcvbn-style algorithm — dependency-free by design)
Events:
- commit-input (every keystroke)
- commit-submit (Enter)
- shift-toggle-visibility (eye click)
- shift-caps-state (Caps Lock state flips while focused)
Key behaviors:
- `purpose: 'current' | 'new'` drives native autocomplete attribute
('current-password' for login / 'new-password' for registration).
- Field integration: disabled/readonly/required/invalid OR-merged.
- VisibilityTrigger gets tabindex=-1 so it doesn't break the form's
natural tab order — mouse / programmatic focus only, matching Ark UI.
- Caps Lock indicator is aria-live="polite" — announces on activation
while focused, silent when inactive.
- Letter-spacing increased on the input for legibility while masked.
Reference library survey performed (10 libraries) before implementation —
all consensus around dedicated component, all have built-in toggle,
ZERO have caps lock warning. Documented in the demo's API tab with a
feature comparison table.
Demo follows DEMO_AUTHORING_GUIDE canonical 6-tab depth: Live (controls
grouped by layer + code snippets at the bottom), API (props + reference
comparison), Morfo (field/value + Parts + Events tables), Sema (events
table with ▶ play), Recipe (selectors with morfo/eidos owner tags), A11y
(concern/contract table).
Also includes external work in words/ from a separate session
(unrelated to PasswordField) — not touching Words is the standing rule,
but the diff was already in the working tree.
svelte-check: 0 errors in PasswordField files (Words errors are tracked
by its own development branch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
fead097ce1 |
fix(demos): rewrite Skeleton + Spinner demos to canonical 6-tab depth
The previous demos violated DEMO_AUTHORING_GUIDE in several places. Both have been rewritten end-to-end against the aspect-ratio canary template (canonical reference for 0-events eidos primitives). Changes per demo: - Stage always rendered between header and tablist (was inside Live tab). - MutationObserver wired with stageRef + 6-entry trace strip (was absent — required even for 0-event components per §6). - Trace strip layout: 'no semantic events' badge + shape/variant + size + color, matches aspect-ratio shape. - Live tab adds the soma + eidos code snippets with full data-uix-code wrappers and head badges (§7 + §8). - somaSnippet now shows the equivalent semantic markup with a clear "not real soma" note (since these are eidos-native). - API tab uses data-uix-table with explicit "Skeleton-specific props" + "Inherited from Box" + "Reference comparison" subsections (§4). - Morfo tab matches §10: header field/value table + Parts table with marker column + per-part data/aria subsection tables. - Sema tab keeps the explicit 0-events explanation (was absent for one of the demos). - Recipe tab uses the Selectors table with morfo/eidos owner tags (§11) instead of a single code snippet. - A11y tab uses data-uix-table (concern/contract) instead of a bullet list, matching aspect-ratio. Reference template: web/routes/uix/components/aspect-ratio/+page.svelte. Per §1, when in doubt, copy from the canary template and adapt. svelte-check: 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
616d8535f7 |
feat(eidos): add Skeleton and Spinner loader primitives
Two pure-eidos loader components with morfos at scope `['eidos']` (no soma backing, no semantic events). Skeleton — content placeholder: - 3 shapes: rect (default), circle, text (multi-line with staggered widths) - 5 sizes (xs..xl), 8 color accents - Optional shimmer animation via CSS `background-position` keyframes - Single `<div role="status" aria-busy="true">` with translated aria-label - Reduced-motion freezes animation, keeps visual Spinner — indeterminate progress: - 3 variants: ring (rotating arc), dots (bouncing), bars (stretching) - 5 sizes, 8 colors - Optional inline label via children snippet - `role="status"` + `aria-live="polite"` for SR announcements - Reduced-motion freezes animation, keeps visual Files: - morfo: skeleton.ts, spinner.ts (scope eidos, no events) - eidos: full component + types + css + langs + index per component - langs: skeleton/spinner under componentLangs barrel - demos: canonical 6-tab depth + composition examples (avatar+text card) - layout: sidebar additions under Status group Note: also includes responsive shell additions to web/routes/uix/uix.css that surfaced from an external session — same file, leaving them in. Reference libraries reviewed (Mantine, Chakra v3, Radix Themes, MUI): all expose the same shape/variant/size/color surface. shadcn Skeleton uses the same pulse animation. No surprises in our API. svelte-check: 0 errors in skeleton/spinner files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
f8897d92d6 |
fix: clear remaining `npm run check` errors (12 → 0)
Three groups of errors blocking the global typecheck:
A. Announce morfo used invalid family/intent vocabulary
- `absent-clear` event used `family: 'absent'` and `intent: 'absent'`,
neither valid in current MorfoEventSemantic schema
- Renamed to `commit-reset` (family: commit, verb: reset, intent: neutral)
matching SearchField's clear-reset pattern
- Updated demo copy to reference the new event name
- Cascade-resolved 4 errors (morfo + soma provider + demo + active-uix test)
B. Tree wrappers stripped bindable props
- eidos `<TreeGrid>` didn't expose `expanded`/`value` as `$bindable`;
they leaked through `...rest` and Svelte rejected `bind:expanded`
- Same issue for `<TreeView>` with `expandedValue`/`selectedValue`
- Now declared explicitly + forwarded via `bind:` to soma's Provider
C. Virtual containers passed wide `style` to native markup
- `style` was typed `string | Record<string, unknown> | null | undefined`
but the markup `<div style={...}>` only accepts string
- Filtered with `typeof style === 'string'` derived guard before render
svelte-check result: 5076 files, 0 errors, 23 warnings (no-op warnings
in navigation-menu `href="#"` and table `state_referenced_locally`).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
ee54029b1b |
Update continuation notes for Words
|
5 months ago |
|
|
940e94bb64 |
Build TextArea component + fix locale propagation bug across soma providers
Major work in this session:
1. NumberField demo refactored to expose Intl.NumberFormatOptions live
- 9 canonical presets (currency/unit/percent/compact/plain)
- Locale/currency/unit lists derived from $libs/langs, $libs/currency,
$format/units (no more hardcoded arrays)
- Default locale from uix.format.numbers.getLocale()
2. Bug fix: locale prop ignored when soma.format service is active
- soma/NumberField: 5 bypass points via new `hasOwnLocale` getter
(decimalSeparator, groupSeparator, formatValue, formatEditValue, parseValue)
- soma/Calendar: dateOrder respects locale prop (matches DateField precedence)
- soma/RangeCalendar: same dateOrder fix
- When the consumer passes `locale` prop, soma.nums/dates is bypassed
and Intl is used directly with the prop locale
3. TextArea — new component, full 4-layer stack
- morfo: 3 parts (provider/input/count), 3 events
(commit-input/commit-submit/shift-count-overflow), APG textbox
- soma: TextAreaProvider with autosize (scrollHeight measurement),
submitOn modes (false|enter|shift+enter|mod+enter), maxLength soft clamp,
Field integration (OR-merge), counter overflow tracking
- eidos: 5 sizes × 3 variants × 8 colors, autosize suppresses CSS resize
- sema: commit-submit + shift-count-overflow perceptual cues
- demo: canonical 6-tab depth, all props exposed as live controls
- PrimitiveTextareaAttributes added to soma/types/html.ts
4. Also includes accumulated unreleased work from prior sessions:
- Eidos ports: announce, clipboard, drag-drop, feed, table,
tree-grid, tree-view, virtual-grid, virtual-list, words
- Their canonical demos under web/routes/uix/components/
- Semantic events defined for utility morfos
(announce/clipboard/virtual-list/virtual-grid)
- Tree indent guides fixed (TreeView ::before, TreeGrid stacked gradients)
- Sidebar reorganized with Data + Utilities groups
- Eidos words content layer and primitives
Reference library survey performed before TextArea API design (Mantine,
Radix Themes, Chakra v3, React Aria, Ark UI, Bits UI, shadcn, MUI, Prime).
Decision documented: no separate CurrencyField/UnitField — single
NumberField with formatOptions is the canon (matches all surveyed
headless libs except PrimeReact's mode enum, which still uses 1 component).
svelte-check: 0 errors in modified/created files.
See CONTINUE.md for full session notes and next steps.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
3fc4ced37b |
feat(carousel): showIndicators + showControls visibility props
Two new eidos-level booleans on `<Carousel>` so consumers can toggle the indicator dots and the prev/next triggers without touching the markup composition. Defaults stay `true` (existing behaviour). The consumer still renders the parts in markup; the recipe just hides them via `display: none` when the eidos root stamps `data-hide-indicators` / `data-hide-controls`. Toggling on/off doesn't unmount the DOM, so ARIA focus is preserved across the change and the layout doesn't reflow into a different size. Naming follows the canonical eidos cascade — `showIndicators` mirrors calendar's `showWeekNumbers`. Mantine uses `withIndicators` (default false); we default `true` because the Eidos recipe assumes indicators are part of the default surface. Demo: two switches added to the eidos visual-props section + snippet preview emits the prop when set to false. Verified: clicking the demo switch flips `data-hide-indicators` on the root and the indicator-group's computed `display` goes from default to `none` without remounting. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
92c5e421c9 |
fix(carousel): :active press feedback was replacing centering transform
Bug behind the user's "el botón se desplaza en el eje contrario al
orientation" report. The Button recipe sets
[data-button]:active { transform: translateY(1px); }
on press to simulate a physical button push. Carousel prev/next triggers
absolute-position via:
[data-carousel-prev-trigger] { transform: translateY(-50%); } (horiz)
[data-carousel][data-orientation='vertical']
[data-carousel-prev-trigger] { transform: translateX(-50%); } (vert)
CSS `transform` is a single property — Button's :active value
REPLACES the carousel's centering transform, so on press the trigger
jumped off its anchor: in horizontal it lost vertical centering, in
vertical it lost horizontal centering AND moved 1px down (wrong axis
relative to slide travel).
Override `:active` on the carousel triggers to compose both transforms
on the appropriate axis:
horizontal: translateY(calc(-50% + 1px))
vertical: translateX(calc(-50% + 1px))
Now press feedback shifts the trigger 1px along the centering axis
without breaking the absolute anchor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
8cf2fe813f |
fix(button): icon wrappers must not capture pointer (carousel triggers dead-clicked)
Real cause behind the persistent "los clicks fallan" report. The button recipe gave `pointer-events: none` to nothing — so when a user clicked near the visual centre of an `iconOnly` button (like the carousel prev/next triggers), the SVG `<path>` was the hit target. SVG paths default to `pointer-events: visiblePainted` so only the painted stroke (the thin chevron line) is clickable; clicks landing in the empty diagonals of the chevron passed through to whatever was behind the button — usually nothing — and the button's onclick never fired. Adds `pointer-events: none` to the icon wrappers (`.eidos-button-icon`, `[data-button-icon]`) and to any nested decorative svg / `[data-svg]` / `[data-icon]`. Now the entire button rect is the click target. Verified: `elementFromPoint` at the chevron's geometric centre now returns `BUTTON[data-carousel-next-trigger]` instead of `path`, and a click at that point fires the button handler + advances the carousel index 1→2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
a81b44b40c |
fix(carousel): first-click no-op — viewportSize was stale until ResizeObserver fired
Real bug behind the persistent "los clicks fallan" report. The
`viewportSize` derivation was reading exclusively from the
`viewportClient{Width,Height}` $state mirrors, which are populated
asynchronously by the ResizeObserver. On first paint (and on the
very first click after mount / orientation switch) those mirrors were
still `0`. So:
translate = -value × slideSize
= -1 × (viewportSize / 1)
= -1 × 0
= 0
The index DID advance (soma's `value` went 0→1) but the rail stayed
at translate=0, so the user saw the active slide unchanged — looked
exactly like a dead click. Subsequent clicks accumulated until the
observer finally fired and the carousel suddenly jumped to whatever
index had become.
Fix: two-tier read in the `viewportSize` derivation. Prefer the
observer-tracked mirror when non-zero (steady state, picks up reshape);
fall back to a synchronous `vp.clientWidth` / `clientHeight` read while
the mirror is still 0. Same derivation reactivity, no extra effects.
Verified: first click after page load now lands `translate3d(-380px, 0,
0)` on a 380px-wide viewport (idx 0→1) — was 0 / no movement before.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
09bbf05ec0 |
fix(carousel demo): default loop=true so triggers never go dead at boundaries
The "los clicks siguen fallando" report was tested against loop=false default behavior — at idx=3 (last slide) the next button correctly went disabled (canGoNext=false), opacity 0.4, no advance — but testers read that as a broken click handler rather than a boundary stop. Default the demo to loop=true so each click predictably advances (0→1→2→3→0...). Verified 6 successive clicks wrap correctly. Component default stays loop=false (matches Embla / Mantine / ark-ui); the demo just picks the more illustrative interactive baseline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
f197463968 |
refactor(carousel demo): move Prev/Next triggers outside Viewport
Canonical placement (matches Mantine, shadcn-svelte): triggers sit as siblings of Viewport (children of the Carousel root), not as siblings of ItemGroup inside Viewport. Keeps the gesture-bearing ItemGroup region clean of unrelated event targets; the recipe's absolute positioning still anchors the triggers correctly because the carousel root is `position: relative`. The "clicks fail" report was the expected disabled state at the last slide under loop=false — verified the next button correctly reports disabled=true + opacity 0.4 once canGoNext becomes false. Enable loop from the controls for infinite navigation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
b1b1e39dc0 |
fix(carousel): chevron direction follows orientation
Vertical carousel still rendered horizontal chevrons (< / >) for the prev / next triggers, which read as wrong-direction even though the click handlers worked correctly. The user complaint "varios clicks" was the visual mismatch making each click look like it did nothing — value WAS advancing index 0→1→2 on each click but the icons pointed sideways in a vertical rail. - Added `orientation` to the eidos carousel context so the prev / next trigger wrappers can pick the right chevron without passing props. - Re-exported `Direction` + `Orientation` from soma's carousel module so the eidos context can type-check them. - Prev trigger now uses `direction='up'` in vertical, `'left'` in horizontal. Next trigger mirrors with `'down'` / `'right'`. Verified: index goes 0→1→2 on successive clicks; chevrons now show `^` (top) and `v` (bottom) in vertical orientation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
a6baae0cf1 |
fix(carousel/range-calendar/time-range-field): apply canonical i18n + vertical layout patterns
## Vertical carousel layout
Recipe was making the indicator group stretch full width (380px) because
the root stayed in `flex-direction: column` and the indicator-group
inherited `align-items: stretch`. Reshaped:
- `[data-carousel][data-orientation='vertical']` now flips the root to
`flex-direction: row` so viewport and indicator-group sit side-by-side.
- Viewport takes `flex: 1 1 auto` of the inline axis with explicit
100% block-size.
- Indicator-group becomes `flex: 0 0 auto; inline-size: max-content;
align-self: center` — narrow column of dots on the right edge.
Active vertical-stretched indicator already had `block-size: 2.25×`
treatment from the prior commit, so it now appears as a vertical pill
inside the dot column.
## i18n controls: follow canonical pattern
Mirrored the date-range-picker / calendar demos verbatim — those are
the canon and the new demos drifted from them. Three corrections:
1. **Locale**: `<select>` with `en-US / es-ES / fr-FR` (the docs
canon — chip rows and made-up locale lists like ar/he/ja were both
inventions). Carousel exposes locale too; it's forwarded to soma
for downstream lang-aware behavior (announcer text, future label
formatting hooks).
2. **Direction**: chip row with `ltr / rtl` (no `auto`). Label is
"direction" not "dir".
3. **State shape**: plain `let dir = $state<Direction>('ltr')`, not
the synthetic `dirCtl + derived` pattern I had invented.
Applied across all three demos so they read identically to the canon.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |