Relocate the motion runtime out of eidos into a runtime art (src/arts/motion,
$motion), exposed as uix.motion and consumed by BOTH soma (Presence.motion ->
motion.run) and eidos (delegates + registers presets) -- dissolving the
soma->eidos coupling. Remove DialogProps.runMotion / eidos.motionRunner; the
bridge is now EngineMotion.run (reads data-animation-style). Delete the 4
relocated dead files (lib/motion/{types,runtime,runner,presets/js}.ts); the
preset DATA (presets/css.ts) stays in eidos. Regenerate generated/base.css.
F6 - token rigor (Carbon): tokenize the raw firma durations (slower/deliberate/
emphatic/sustained holds, escalating by announce intent severity), add
--motion-distance-xl (30px shared-axis), --motion-scale-through (0.92), the
emphasized easing, and productive/expressive sets ([data-motion-set=expressive]).
F7 - extensibility + typegen: app-extensible, type-safe preset-name registry
(EidosMotionPresets, mirroring SemaChannelSignatures); MotionPresetName =
keyof EidosMotionPresets | none | (string & {}).
Also sweeps other in-progress working-tree edits (web/routes/temas/grafito).
Verify: npm run check -> 1 pre-existing error (grafito), 0 new; motion 22/22,
active-uix 25/25, Presence 2/2, Dialog 3/3. Pre-existing words-track failures
unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bleed-through: the portaled [data-dropdown-menu-content] had z-index:auto, so
any positioned page element with a positive z-index (e.g. a selected
[data-toggle-group-item], z-index 1) painted THROUGH it. In the Words inspector
the Add-block menu showed the Typography toggle chips ("Predeterminada" /
"Normal") bleeding through. Added `content-z: 80` to the dropdown-menu recipe
(matches `select`, its sibling click-triggered overlay) + `z-index:
var(--dropdown-menu-content-z, 80)` on content + sub-content; regenerated
base.css. The soma floating layer reads this computed z-index and mirrors it
onto the positioner wrapper. The rest of the menu family (context-menu /
menubar / navigation-menu) shares the same latent gap — deferred.
Canonization (finishes the dom.raf rollout — zero raw requestAnimationFrame
left in src/uix):
- tabs-indicator.svelte: requestAnimationFrame/cancelAnimationFrame +
new MutationObserver + new ResizeObserver -> eidos.dom.raf /
observeMutation / observeResize (disposer pattern via ActiveEidos.require).
- tabs.svelte: new ResizeObserver -> eidos.dom.observeResize.
- resize-observer.svelte.ts: requestFrame/cancelFrame (raw handle) ->
dom.raf (disposer); fixed the stale "Debounces via requestAnimationFrame"
comment.
Browser-verified in the Words demo: column Add-block menu is opaque and on top
(elementFromPoint at the chip centers returns menu items, inMenu:true); the
menu options are collapsed + localized (one Encabezado, one Lista con viñetas);
empty-column delete works (column persists addressable with 0 children).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two audit P2 quality defects, fixed at engine level.
P2-2 - on-solid text illegible on light solids:
The `contrast` slot defaulted to `--color-content-on-solid` (white) for every
role. On light solids (amber/yellow, risk=orange ~2.3:1) white is sub-AA. The
engine now picks by WCAG contrast (gamma-linearized) of the role step-9: when
white fails (<3:1) it uses `--color-content-on-solid-contrast` (a dark, new
OPTIONAL `content.onSolidContrast` semantic, #1c1917 in base). Only risk flips
to dark in base (5.89:1); purple/red/teal/green keep white (convention, >=3:1).
An explicit `slots.contrast` override is still honored verbatim.
P2-4 - opaque tinted soft surfaces:
The soft variant tint (Button + Badge `{role}-soft-bg`) was opaque (step-1 track
+ opaque color-mix hover) so it did not composite over non-uniform backgrounds.
New derived tokens `--color-{role}-surface` (= a2) + `--color-{role}-surface-hover`
(= a3) are translucent by construction (compositing-inverse alpha). Button/Badge
soft consume them. Toast/Tabs untouched - they are cards, opacity is correct.
- config-types: optional onSolidContrast + CONTENT_COLOR_OPTIONAL_KEYS.
- config: content keySet allows the optional key; validator value-checks it.
- render-css: wcagContrastRatio/wcagRelativeLuminance; luminance pick; emit
on-solid-contrast + surface/surface-hover.
- contract: on-solid-contrast + surface tokens per role.
- themes/base: onSolidContrast #1c1917 (light + dark).
- recipes/base: Button + Badge soft-bg -> surface, soft-bg-hover -> surface-hover.
- docs: THEMING.md section 24 + section 22 item 2; audit P2-2/P2-4 resolved.
Verified: npm run check (0 new errors), vitest eidos (0 new regressions),
browser runtime (risk 5.89:1 dark text, surfaces translucent rgba).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the synthetic step-9-at-opacity alpha ramp with a real compositing inverse: each --scale-X-aN is the translucent color that, painted over the scale's background (white for light scales, black for dark, by step-1 luminance), reproduces the opaque solid step N (Radix model). Computed in render-css.ts for ANY theme (base + custom), so alpha is consistent-by-construction with the solid scale and cannot drift. alphaScales opt-in still wins verbatim; non-hex solids fall back to the legacy color-mix ramp. Verified: --scale-purple-a9 = rgb(92 0 173 / 0.6941) over white = #8e4ec6.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add 40 recipes.banner.{intent}-{slot} forwarders (= var(--color-{intent}-{slot}))
- banner.css reads --banner-{intent}-{slot} instead of foundation --color-{intent}-* directly
- Banner is now retintable per-component via recipes.banner.* like button/badge; keeps data-intent (no API change)
- Regenerated base.css; recipe contract balanced (40 declared = 40 consumed), no visual regression
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The editor used UI-cramped metrics (14px / 1.25 line-height / 8px block
gap) which read as a form field, not a document. Give the CONTENT (not
the chrome) a reading scale: body 14→16px, line-height 1.25→1.6, block
gap space-2→space-3 (headings derive 1.5x for top margin). Chrome keeps
its smaller --_words-font-size.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Decouple band width from text inset: `--_words-rail-width` (1.25rem,
the thin band) + `--_words-rail-gap` (1.25rem, breathing room) →
text is no longer glued to the band edge.
- Narrow the band: 2.5rem → 1.25rem (20px).
- Lighter tones: rail-bg #ededed → #f5f5f5, rail-border #cfcfcf →
#e3e3e3. Eidos CSS regenerated.
Verified: band 20px + 1px #e3e3e3 edge, then 20px air before text at 40px.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User-directed editor rework, step 1 (left gutter):
- Remove the "+" inserter button from the block-handle gutter. Its
action moves into the drag-handle menu as a new "Insert below" item
(folded into `act('insertBelow')`); the standalone `insertBelow`
helper is gone. The separate seam inserter (words-block-inserter)
is untouched.
- Turn the left gutter into a soft silver margin band: narrow it
(`--_words-gutter-size` 3.5rem→2.5rem) and paint it via a
background-gradient on [data-words-content] — `--words-rail-bg`
#ededed band with a 1px, slightly stronger `--words-rail-border`
#cfcfcf right edge. Background only, so the contenteditable text /
selection is undisturbed. Recipe tokens softened + eidos CSS
regenerated.
Verified in browser: gutter shows only the grip on hover (no "+");
the menu reads Insert below / Move up / Move down / Duplicate / Delete
and "Insert below" inserts a paragraph (7→8 blocks) and closes the
menu; the silver band renders at 40px with the 1px border. npm run
check: 0 errors.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
EV-G final pass + EOD documentation.
Words editor:
- Stop emitting `contact-focus` from soma. Removing the per-component
cascade rule earlier didn't fully silence the editor — the `contact`
family carries a BASE sound signature in SEMA_MAP (pitch 800, gain
0.25) that fires even without a cascade rule. The trigger call is
commented out (event no longer emitted; telemetry empty). Per spec:
"sigue apareciendo el puto sonido, el ring".
- Drag handle + inserter now cover lists AND tables. Selectors
expanded from `[data-words-node="block"][data-words-path]` to also
match `[data-words-node="list"]` and `[data-words-node="table"]`.
Top-level lists / tables get the grip + the "+" seam under them.
- Removed :hover from `[data-words-block-handle]` and
`[data-words-block-inserter-button]`. Gutter overlays are AMBIENT —
visual state via data-attrs only (`data-open`, `data-grabbed`,
`data-dragging`). Per spec: "quítale el hover al botón de añadir
bloque, si el handle no tiene este tampoco".
- Rail tokens hoisted to recipe: `words.rail-bg = #d4d4d4`,
`words.rail-border = #9a9a9a`. Fixed-tone (not theme-aware) — the
rail emulates the physical margin of a notebook; same look in
light/dark. Recipe-css-contract test was rejecting raw hex in
words.css.
Docs:
- CLAUDE.md hand-off 2026-05-28 #2 documenting the 6 EV-* Words
sprints + 5 architectural decisions with lasting effect (canvas
silent, fixed-tone tokens for physical metaphors, drag predicate
covers list/table, ambient overlays, insertParagraph empty-block
guard).
- continue.md (CONTINUE.md) rewritten for tomorrow's pickup:
TL;DR of the two halves (toggle-group structural + Words UX),
remaining bugs (drag handle outside rail on code blocks, heading
inline level change), and the heredados (POLISH-1b, F2.3-F2.6,
DRAGDROP-SOMA).
Verification: 467/467 morfo + sema + eidos + soma/words tests pass.
85/85 engine pass. `npm run check`: 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The editor body was unbounded vertically — long documents pushed the
whole page instead of confining the scroll to the editor surface.
Per spec: "la parte de la edicion deberia tener un scroll".
Recipe:
- Three new tokens `content-max-block-size-sm/md/lg` (50vh / 60vh /
70vh) mirror the existing `content-min-block-size-*`. Generated CSS
regenerated.
CSS:
- `[data-words-content]` now carries `max-block-size:
var(--_words-content-max-block-size)` and `overflow-y: auto`. The
min-block-size baseline stays as the starting height for empty
editors. When content grows past the cap, the body scrolls
internally — the host card no longer balloons.
- `scroll-padding-block: 1rem` hint so caret-into-view nudges leave
breathing room.
Block-handle / block-inserter / image-float-bar:
- Switch scroll listeners from `window.addEventListener('scroll')` to
`document.addEventListener('scroll', { capture: true })`. Scroll
events don't bubble; the previous wiring only caught window scrolls.
Now the gutter overlays (handle position, "+" snap target, image
float bar anchor) re-measure when the user scrolls INSIDE the
content area too.
Verification: dev probe at /uix/components/words confirms
`max-block-size: 600px`, `overflow-y: auto`, content scrollHeight
(615px) > clientHeight (600px), `isInternallyScrollable: true` on a
1000px viewport (60vh of 1000 = 600). 467/467 tests pass across
eidos + morfo + sema + soma/components/words.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
`STATIC_TYPOGRAPHY.styles` already defined 11 named typography styles
(hero, h1..h6, body, prose, label, caption, code) but the renderer
only emitted families / sizes / weights — the style entries lived
as data with no CSS reflection.
Extend `appendTypographyDeclarations` so each named style emits:
- `--style-{name}-font-family` → `var(--font-family-{family})`
- `--style-{name}-font-size` → `var(--font-size-{size})`
- `--style-{name}-line-height` → literal or `var(--font-line-height-{size})`
- `--style-{name}-letter-spacing` → literal or `var(--font-letter-spacing-{size})`
- `--style-{name}-font-weight` → `var(--font-weight-{weight})` (or numeric)
- `--style-{name}-color` → `var(--color-{dot.path → dash-path})`
Responsive sizes (e.g. `hero.size = { base: 'xxl', md: 'xxxl' }`)
emit the base value into `:root` and per-breakpoint
`@media (min-width: …)` blocks that override the same variables.
Breakpoint thresholds match `$libs/dom/responsive` (sm: 480, md: 768,
lg: 1024, xl: 1280, xxl: 1536).
`renderStaticCss` now iterates `STYLE_BREAKPOINT_ORDER` after the
main `:root` block and appends one media-query block per breakpoint
that has at least one responsive override. The `indentBlock` helper
preserves indentation inside the wrapper.
`base.css` regenerated. svelte-check 0 errors, `npm run component:audit`
81 / 81 PASS unchanged. This is foundation-only; no components consume
the new tokens yet — that lands in the typography port (Text + Heading
+ Display + Code + CodeBlock + Kbd + Mark + Highlight + Link).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three components closing the 6-component audit batch.
number-field
- N1 RTL scrubber: invert horizontal delta sign when dir='rtl' so
dragging right decrements (visually backward) in RTL layouts. Vertical
unaffected. Added 2 RTL scrub tests; existing scrub test pinned to ltr.
- N2 stepper height: Increment/Decrement now use --_number-field-height
for inline-size/block-size/min-inline-size so they always match the
input row at every size token (xs/sm/md/lg/xl).
- N3 input focus ring: explicit [data-number-field-input]:focus-visible
with --_number-field-accent-border box-shadow, alongside the existing
Provider-level ring.
search-field
- S1 icon tokens: added --search-field-icon-size-{xs..xl} + icon-color +
icon-margin-inline-end + a Morfo Icon part + soma/eidos wrappers.
Demo uses <SearchField.Icon><Search /></SearchField.Icon>.
- S2 debounceMs prop: soma wraps onValueChange in a timers.schedule with
the provided delay. clear() cancels, submit() flushes synchronously.
- S3 loading slot: new LoadingIndicator part (morfo + soma + eidos) +
data-loading on Provider + CSS spinner via @keyframes search-field-spin
honoring prefers-reduced-motion.
combobox
- C1 ClearButton part: new morfo trigger + soma provider + eidos wrapper
with X icon. Hidden via [data-empty] (opacity + pointer-events:none).
Click clears value + inputValue and refocuses input.
- C2 allowsCustomValue: when true and no highlighted item, Enter commits
the current inputValue as the value. Single mode closes after commit;
multi appends + clears input + stays open.
- C4 Backspace removes last tag in multi mode when input is empty.
- C5 Sticky group headers: CSS position:sticky with content-overlay bg.
Plus: fixed search-field demo's brittle parts[1] index reference to a
named lookup (parts.find(p => p.kebab === 'input')).
Verified: svelte-check --threshold error → 0 errors / 0 warnings /
4584 files. All component tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops ColorPickerChannelSliderProvider / TrackProvider / ThumbProvider
(~200 lines of duplicated pointer/keyboard/ARIA handling) and rewrites
`components/color-picker-channel-slider.svelte` to compose
SliderProvider underneath. The channel slider now keeps only the
channel-aware bridge: setChannel writes the value, channelGradient
paints the track.
What the picker gains by reusing Slider
---------------------------------------
- sema events: `slider:handle-pick`, `slider:handle-drag`,
`slider:commit-set` now flow through the channel sliders (they were
silent before — the Sema layer had no perceptual hook on channel
drag/commit).
- single pointer/keyboard implementation: Arrow/Home/End/PageUp/PageDown,
multi-touch, focus trap, RTL, step-snap — all from SliderProvider.
- ARIA: role=slider, aria-valuenow/min/max, aria-orientation, tabindex
managed by the slider's thumb provider.
- recipe tokens: channel sliders consume `--slider-*` (sizes, radii,
shadows) so they stay in lockstep with the standalone Slider and
with TimePicker.HourSlider / .MinuteSlider / .SecondSlider. The
`--color-picker-channel-slider-*` tokens are removed.
Public API impact
-----------------
- `<ColorPicker.ChannelSliderTrack>` and `<ColorPicker.ChannelSliderThumb>`
remain as part names but now re-export `Slider.Range` /
`Slider.Thumb`. Consumers that compose them inline keep working.
- The inner DOM changes: `[data-slider]` / `[data-slider-thumb]` /
`[data-slider-range]` (governed by the slider morfo) replace
`[data-color-picker-channel-slider-track]` /
`[data-color-picker-channel-slider-thumb]`. The wrapper still emits
`[data-color-picker-channel-slider][data-channel]` so per-channel
CSS targeting works; the gradient is exposed via
`--cp-channel-gradient` and routed onto the slider's track ::before.
- Morfo: drops `channel-slider-track` and `channel-slider-thumb` parts
(the slider morfo owns those contracts now). `channel-slider`
remains as the wrapper marker with `data-channel`.
- Eidos: deletes `color-picker-channel-slider-track.svelte` and
`color-picker-channel-slider-thumb.svelte`; `index.ts` re-points
`ChannelSliderTrack` / `ChannelSliderThumb` at the slider eidos
components.
New norm N-8 added to PENDIENTES.md
-----------------------------------
"Reutilizar componentes ya definidos en componentes complejos siempre
que no supongan una pérdida de características demostrable." Documented
with this refactor as the worked example.
Checks
------
- 67/67 component-audit PASS
- 40/40 morfo:check PASS
- svelte-check 0 errors / 0 warnings
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Composite popover: 2D saturation/brightness area, hue + alpha channel
sliders, preset swatches, eye-dropper, and a segmented channel input
composed from ColorField. Builds on the existing soma color-picker
(23 parts) — eidos layer is mostly thin pass-throughs adding visual
chrome.
What landed
-----------
- New `src/uix/eidos/components/color-picker/`:
- Root visual (Provider) + visual-context (size/variant/color)
- Trigger with a default swatch dot tinted from
`--cp-trigger-color` (the air pattern from morfo-runtime)
- Content (popover wrapper), Area (+ Background + Thumb),
TransparencyGrid
- ChannelSlider (+ Track + Thumb) — generic per-channel slider;
default-composes Track+Thumb when no children
- SwatchGroup + SwatchTrigger + Swatch + SwatchIndicator
- EyeDropper (Pipette icon by default)
- ChannelInput + ChannelSegment + FormatSelect (composed from
ColorField parts; same provider injected automatically)
- Label, Control, ValueSwatch, ValueText, HiddenInput
- Recipe section `color-picker` in `recipes/base.ts` (regenerated
`generated/base.css`) — trigger, content, area, channel slider,
swatches, eye-dropper tokens.
- CSS reuses color-field tokens for the segmented input portion and
adds picker-specific visuals. Transparency checker on trigger swatch
+ value swatch + alpha slider track via conic-gradient pattern.
- Morfo updates: scope `['soma','sema']`, `apg: dialog-modal`, added
`commit-set` event so audit classifies it as interactive.
- Soma exports: ColorPickerProvider class so eidos Trigger can read
the current value for the swatch tint.
- Demo at `/uix/components/color-picker` with the standard 6 tabs;
live stage exercises area drag + channel sliders + preset swatches
+ eye-dropper + format switch.
- README documents the surface, comparativa vs air/Ark/Chakra, and
flags the picker contract (mode/Footer/commit/cancel) as gap to
align with date/time-picker in a follow-up.
Checks
------
- 67/67 component-audit PASS
- 40/40 morfo:check PASS (new color-picker demo validates against
morfo DOM)
- svelte-check 0 errors / 0 warnings
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the date-field pattern (Provider visual root + Label / Input /
Segment / HiddenInput parts; ColorField also exposes FormatSelect).
Soma owns segment editing, ARIA spinbuttons, validation, format
switching and form submission; Eidos adds size (xs..xl), control
variant (surface/outline/ghost) and intent color recipes.
Adds time-field and color-field recipe sections to recipes/base.ts
(regenerated generated/base.css) and registers both routes in the
sidebar nav. apg=spinbutton declared on both morfos. READMEs follow
the Baseline / Superficie / Comparativa / Decisiones / Gaps /
Referencias template required by audit F-1.x.
All 67 component-audit components PASS, 38/38 morfo:check PASS,
svelte-check 0 errors/0 warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User report: setting startReadonlySegments/endReadonlySegments in the
date-range-picker demo didn't change anything visible, and the
date-picker demo had no equivalent control.
Root cause #1 (visible): the readonly segment styling was
background: var(--color-neutral-track) — primitive-neutral-3 — too
close to the surface background in both themes. Mechanically the lock
was working (data-readonly stamped, keyboard updates blocked) but the
visual delta was invisible.
Fix: add segment-readonly-color + segment-readonly-decoration tokens
to the recipe. Read-only segments now render with
- background: var(--color-surface-overlay) (clearer step from default)
- color: var(--color-content-secondary) (slightly faded)
- text-decoration: underline dotted var(--color-content-muted)
- cursor: default
both in date-field.css and the date-range-picker per-component
override.
Root cause #2 (consistency): date-picker demo lacked the segment-lock
chip group that the range demo has. Added segmentLock state +
readonly-segments derived array (year / monthYear / date presets) +
chip control + snippet integration. Now both pickers expose the prop
identically.
Verification (browser): clicking the 'monthYear' chip in date-picker
demo stamps data-readonly on the month + year segments; the dotted
underline + lighter color are visible. Range demo: clicking 'year'
under 'start segments' affects only the start year segment, end stays
editable.
0 type errors, 67/67 component:audit PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The open accordion trigger was rendering in `--color-primary-text`
(pink/magenta in dark theme), which read as a loud accent on the
heading — visually noisy and inconsistent with shadcn-svelte / Radix
/ Ark UI, which all keep the trigger text in the regular content
tone and let the rotated chevron + revealed content signal "open".
Recipe tokens updated:
- `accordion.trigger-color-open`: `--color-primary-text` →
`--color-content-primary` (no recolor on open).
- `accordion.indicator-color-open`: `--color-primary-text` →
`--color-content-primary` (subtle emphasis without the loud
accent).
`generated/base.css` regenerated. Verified in browser: open trigger
heading now matches the closed triggers' tone; the chevron rotates
upward and the content reveals as the only open affordances.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes incidencia 2026-05-20 #4 plus the R-2.1 audit finding:
- Remove raw-hex fallbacks (`#16845f`, `#106d4e`, `#2ba878`, `#ffffff`)
from `date-range-picker.css`. The `--color-affirm-*` and
`--calendar-day-selected-color` tokens are guaranteed by the
generated base.css; the safety hex hid that they weren't being
refreshed.
- Regenerate `eidos/generated/base.css` so `--calendar-day-*` tokens
(color, selected-color, today-border, etc.) materialize from the
recipe definitions in `lib/recipes/base.ts`. Fixes the stale
`keeps generated/base.css in sync` contract test.
- Start endpoint now reads `--color-affirm-*` cleanly; end follows the
picker's `data-color` accent. When the picker's accent IS in the
affirm family (`data-color="affirm"` or `"fulfill"`), the start
swaps to `secondary` so the two endpoints stay perceptually distinct.
- Both endpoints get an inset edge stripe + semibold weight so the
start vs end direction reads at a glance — addresses the user's
"indistinguible o transparente" complaint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>