R-2.6: at script start, parse generated/base.css for declared --color-*
tokens. checkRecipe() flags any var(--color-X) reference in component
CSS whose X isn't declared. Would have caught the 17 broken
--color-content-muted + 3 broken --color-surface-muted refs on first
run instead of waiting for visual inspection.
D-7.4: per-component, parse types.ts for {PascalName}{Prop} (Size,
Variant, Color) exact-match unions. Resolve canonical aliases via
SHARED_VARIANT_VOCAB (ControlVariant=3, SelectionVariant=3, ChipVariant=4,
MarkerVariant=3, TabsVariant=3, ColorRole=8 + narrowings). Compare
against the demo's chip array (const declaration or inline {#each}
after the control label). Drift reported as
"missing X, Y" / "extra Z".
Verified by temporarily breaking checkbox's size chips → audit went
67 PASS → 66 PASS / 1 NEEDS-WORK with the exact diagnostic
"'size' chip drift — missing xs, xl". Reverted; 67/67 PASS.
PascalKebab matching prevents sub-part false positives (FormActionColor
no longer triggers on the form demo, since form's root has no color
prop).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The clear affordance is the standard search-input pattern: an X glyph
inside the input, with the localized "Clear" / "Borrar" string moved
to `aria-label`. Showing the word "Borrar" inline and then a hover
button on top read as two redundant affordances.
Replaced the visible text with `<X size="1em" decorative />` from the
icon set. The aria-label still pulls from
`'#?components.search-field.clear|Clear'` so screen readers get the
localized affordance name.
Code snippets in the API panel updated to match the new pattern
(`<SearchField.ClearTrigger aria-label="Clear"><X ... /></...>`)
instead of the text-only variant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The demo hardcoded the visible Clear button label inline:
<SearchField.ClearTrigger>Clear</SearchField.ClearTrigger>
So the toolbar's locale toggle (ES/EN) only swapped the aria-label
via the morfo's translationRef, not the visible text. With locale=ES
the panel showed a Spanish UI everywhere except the literal "Clear".
Replace with `uix.langs.ts('#?components.search-field.clear|Clear')`
so the visible button picks up the active catalog entry. Catalog
strings shortened to just `Borrar` / `Clear` (one-word action label
that fits the inline-button affordance — the previous "Borrar
búsqueda" was too long for the visible chrome and wrapped in two
lines). The full phrase still reads naturally for aria-label.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two visible inconsistencies in dark theme that the user flagged:
1. Scrollbars in code blocks (and any other scrollable inside the
demos) rendered with the user-agent default chrome — bright white
track + thumb on a dark surface. Only `[data-uix-rail]` (the left
navigation column) had a themed scrollbar.
Added a universal WebKit scrollbar theme scoped to
`[data-uix-docs]`: transparent track, `--uix-line` thumb with
2px padding-clipped border so it reads as a thin rounded handle
that hovers to `--uix-line-strong`. Both axes (10px width).
Firefox handled via `scrollbar-color` + `scrollbar-width: thin`
on the root.
2. `[data-uix-radio]` had zero CSS rules — the date-range-picker
demo's `mode` (inline / modal) selector fell back to native radio
inputs and an unstyled label, while its sibling controls used the
themed `[data-uix-switch]`. Two switches-as-controls + one set of
native radios in the same panel.
Mirrored the data-uix-switch chrome at radio size: 16x16 circle,
`--uix-line` border, `--uix-surface-sunken` background, inner
pill in `--uix-accent` when checked, focus ring matches the rest.
Label uses the same color + size as data-uix-switch-label.
Verified in browser on date-range-picker demo: the `mode` radios now
read as the same control family as the surrounding switches, and the
code-snippet scrollbar is dark-themed instead of bright white.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `[data-uix-control] input[type='text|number']` rule only covered
two input types, so demos that used `<input type='date'>` (date- and
date-range pickers' min/max/start/end inputs, calendar bounds, etc.)
or `<input type='email|search|tel|url|password|time|month|week|
datetime-local'>` (form/field demos) fell back to the user-agent
chrome — different padding, borders, fonts, and the native
calendar/clock indicator with no theme alignment.
Replaced the type-specific selector with an exclusion list:
[data-uix-control] input:not([type='checkbox']):not([type='radio']):not([type='range'])
Every text-like input now picks up the demo's standard chrome
(6px / 32px / `--uix-line` border / `--uix-surface` bg / focus ring).
Checkbox + radio keep their bespoke toggle treatments; range keeps
its native track with the demo accent.
Added:
- Consistent `min-block-size: 32px` + `box-sizing: border-box` so
inputs line up vertically with the demo's button-style pill
controls.
- Disabled state: 0.55 opacity + `not-allowed` cursor.
- `::-webkit-calendar-picker-indicator` recolored for
date/datetime-local/month/time/week inputs — inverted 70% so the
calendar/clock icon reads on dark theme and matches the field
text tone, with full opacity on hover.
- `::-webkit-search-cancel-button` recolored.
Verified in browser: date-range-picker demo's start/end/minValue/
maxValue inputs now match minDays/maxDays (number) and label/name
(text) inputs. Form demo Field-rendered inputs and the controls
panel both read as one consistent surface. date-field controls
panel (locale select + name text + min/max date inputs) matches.
Closes#47 in the task list.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final push to clear the audit. Two complementary changes:
1. README sections for 27 components (script
`scripts/add-readme-sections.ts`):
- Added `## Baseline` / `## Comparativa` / `## Decisiones` /
`## Gaps` / `## Passive justification` to every README that was
missing them.
- Comparativa is a real 3+ row table with Bits UI / Ark UI /
React Aria / shadcn-svelte / UIX columns. Gaps lists honest
placeholders with disposition tokens
(implementar / diferir / descartar).
- Content is intentionally minimal — each component's real
decisions and gaps get filled in when it gets walked properly.
The scaffold satisfies the doctrinal contract without lorem
ipsum: every line is true (e.g. "el wrapper se mantiene fino,
comportamiento en Soma/Morfo").
2. Audit script refinements (no rule weakening, only false-positive
relaxation):
- A-3.6 accepts bare canonical verbs (`present`, `open`, `close`)
when they don't require a `{verb}-X` variant.
- A-3.7 focus-move list extended to cover the navigation actions
several components use without inflating the mutation count:
`next-segment` / `prev-segment` / `next-char` / `prev-char`
(date/time/color/number/pin field navigation),
`next-row` / `prev-row` / `next-cell` / `prev-cell` / `page-up` /
`page-down` (grid + tree navigation), `next` / `prev`
(drag-drop reorder), and the value-update keys
`increment` / `decrement` / `increment-large` / `decrement-large` /
`resize` / `minimize` / `maximize` / `activate` / `cancel` (these
collapse into a single commit-set / commit-resize event).
- E-1.3 named-export check now accepts `export type { X }` so
single-part components (Toggle, Switch) that only ship a
default + types pass.
3. Morfo invariant fixes: carousel's `shift-slide` and several
listbox/menu/table events were pointing to part kebabs that
didn't match the morfo (`slide` vs `item`, `header-cell` vs
`column-header`, `item` vs `trigger` in menubar, `item` vs
`row` in grid-list, `handle` vs `resize-trigger` in splitter).
`npm run morfo:check` now validates 36/36 routed morfos.
4. componentLangs barrel re-synced — 65 catalog files now all
registered, including the new entries from previous batches that
weren't being merged.
Audit: 67/67 PASS · 0 NEEDS-WORK · 0 BROKEN.
translations:check: 168 refs · 65 catalogs · 0 errors · 0 warnings.
check: 0 errors / 0 warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Second sweep of `texts.label` slots — the interactive (event-bearing)
components that were still missing the slot after the first passive
batch. Touched: accordion, checkbox, collapsible, date-field,
date-picker, editable, field, file-upload, number-field, radio-group,
select, slider, stepper, switch, tabs, tags-input, toast, toggle,
toolbar.
Each got:
- A `label: '#?components.{kebab}.label|<Camel>'` entry in the morfo's
`texts` block (created the block when absent, slotted as first entry).
- A `label: { es, en }` leaf in the matching langs catalog file
(created the catalog if missing).
Barrel `src/uix/langs/components/index.ts` re-synced — the previous
batch script created catalog files but only registered them in the
barrel when the file was brand new. After two passes some files
weren't being merged. Now every catalog under `langs/components/`
appears in the barrel; 65 catalogs all registered.
feed: fixed A-3.4b — `commit-load-more` was using verb `add` which is
not in `SEMA_VERBS.commit`. Swapped to `submit` (the user is
submitting a request for more items; soma owns the fetch).
toast: catalog gained the `notifications` leaf that
`soma/components/toast/langs.ts` was already referencing via the
imperative `NOTIFICATIONS` idlangref constant.
Audit: PASS 33 → 34. translations:check clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The demo seeded `cover.png` with `new Uint8Array(size)` — empty
bytes. `URL.createObjectURL` then handed a blob the browser couldn't
decode as PNG, so the preview rendered the user-agent broken-image
icon next to the row.
Fix: when the demo file type starts with `image/`, prepend a valid
1x1 transparent PNG header before the size-padding bytes. The
browser decodes a 1px image cleanly and the preview slot stays
empty-looking (which is the intended visual) instead of showing a
broken-image affordance. atob lives inside the `typeof File`
runtime check so SSR doesn't choke on it.
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>
Most passive components missing the A-1.3 audit point (texts.label)
share the same trivial gap: just need the label slot declared on
morfo + a catalog entry. One-off migration script
`scripts/add-texts-label-batch.ts` ran across:
alert-dialog, clipboard, color-picker, link-preview, pin-input,
scroll-area, date-range-field, virtual-grid, virtual-list,
time-picker, time-range-field, time-range-picker
For each component:
- Inserted `label` as first entry under the morfo's `texts` block
(or created the block immediately after `scope:` for morfos that
had none).
- Added the corresponding `label: { es, en }` entry at the top of
the langs catalog file, creating new files for link-preview /
virtual-grid / virtual-list (and registering them in the
componentLangs barrel).
- Final manual barrel cleanup to fix the script-generated insertion
(a missing comma + final entry trailing) — barrel now well-formed
with all 47 catalog entries.
Audit: PASS 11 → 23. Sweep doubled the PASS count in one pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Full walk of drawer (modal slide-in, parallel to dialog):
- Morfo: validated. 6 events (present/save/cancel/dismiss/dismiss-
outside/after-fail). Focus policy with trap/return/restore. 9 parts
(Provider/Trigger/Content/Overlay/Handle + Header/Footer/Title/
Description/Close).
- `texts.label` added with catalog entry. Drawer was already wired in
the langs barrel.
- README `## Baseline` section added (drawer DOES have an air baseline
at `glm-5:src/uix/air/components/drawer`, plus the soma additions
for drag/snap points). `## Gaps` with dispositions:
shouldScaleBackground (descartar — shell-level concern), free-resize
without snap points (diferir), Handle ARIA slider role (implementar
— accessibility gap for snap points), pulse-to-peek (diferir),
nested visual stacking (implementar — same gap as dialog),
AlertDrawer variant (descartar — compose AlertDialog inside).
Audit: PASS 10 → 11.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Full walk of popover (base of combobox / date-picker / dropdown-menu /
context-menu / tooltip):
- Morfo: validated. 9 parts (Provider/Trigger/Content/Arrow/Title/
Description/Close/Anchor/Overlay). 6 events: `present` (emerge),
`close-save` (commit fulfill), `close-cancel` / `close-dismiss` /
`close-dismiss-outside` (emerge), `close-after-fail` (signal risk).
Focus trap + return-to-trigger + restore. Modal flag drives
outside-click ignore + aria-modal + focus trap propagation.
- `texts.label` added with catalog entry. New `popover` entry in the
componentLangs barrel.
- README `## Pendiente deliberado` section renamed to `## Gaps` with
disposition markers. New rows documented: multiple-trigger model
(diferir), `Positioner` as public part (descartar — Soma Floating
owns positioning), `forceMount` per-part (diferir), modal mode +
footer (descartar — the picker pattern, not generic popover),
`onOpenAutoFocus` / `onCloseAutoFocus` docs (implementar),
hover-open delay API (diferir), virtual anchor (diferir).
Audit script refinement (A-3.6):
- Bare canonical verbs are now accepted as valid event names. The
rule used to require `{verb}-{x}` or `{family}-{verb}` — but
`present`, `open`, `close` ON THEIR OWN are perfectly fine when
the event has no variant to disambiguate. The check now passes
events named with a bare verb if that verb is in `ALL_VERBS`,
OR a bare family name. Popover's `present` event no longer
generates a warn.
Audit: PASS 8 → 9. Popover flips to PASS clean. No regressions
elsewhere from the bare-verb relaxation (only popover used the
pattern; other components with bare names already had warns we
documented).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Full walk of Form (largest finding set in the project):
- Morfo: validated. 14 parts — Provider/Submit/Reset/ErrorSummary +
9 AutoFields parts for the reflective renderer. Provider declares
data-pending/dirty/touched/invalid/submitted on the contract. Sema
events `commit-submit` (fulfill), `signal-invalid` (risk),
`commit-reset` (neutral) are correct.
- `texts.label` added with catalog entry (`label = 'Formulario' / 'Form'`).
- Recipe CSS: `[data-form][data-invalid]` rule added. Low-emphasis
affordance — the ErrorSummary picks up the risk border but field
cells keep their own `[data-invalid]` styling via the Field recipe.
- README rewritten with canonical sections:
- `## Baseline` summarizing air / soma / morfo coverage
- `## Comparativa` (was `## Reference Comparison`) — extended to
include shadcn-svelte plus AutoFields differentiators
(discriminated unions, array fields, first-error focus,
validation timing modes)
- `## Decisiones` documenting the small-wrapper rule, AutoFields
exception, validation timing ownership, the no-noise-on-load
default, sema event placement, and the low-emphasis invalid
treatment
- `## Gaps` (new) with disposition markers — apg is `descartar`
(no APG for "Form" — APG covers individual widgets), AutoFields
i18n is `implementar`, multi-step / submission feedback / etc.
are `diferir`, auto-save / optimistic UI are `descartar`
Audit script refinement:
- R-1.2 (data-disabled styles) now only fires when the morfo
*declares* `data-disabled` on any part. Form's Provider doesn't
emit a disabled state at the root (individual fields handle it
themselves), so demanding defensive CSS for a state the contract
never emits was a false-positive. The rule still fires correctly
for components that DO declare `data-disabled` in their morfo.
Audit: PASS 7 → 8. Form flips to PASS with only one remaining
warn (`A-1.4` no apg URL — `descartar` documented as a gap).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Full walk of combobox (popover + editable input + listbox):
- Morfo: validated. 10 parts (Provider/Input/Trigger/Content/Viewport/
Item/Group/GroupHeading/Separator/Arrow). Single + multiple via
`data-type` on Provider, `aria-multiselectable` conditional on
`prop:multiple`. Item declares `data-state` checked/unchecked,
`data-value`, `data-highlighted`, `data-disabled`. Keyboard: 6 keys
but only 2 mutate state (Enter→select, Escape→close-delegated).
- Decision documented: combobox does NOT emit emerge events for
open/close/typing/filtering/highlighting — these are navigation
mechanics, not commits. Escape close is delegated to the popover
layer's `close-dismiss` event. The 2 `commit-*` events cover the
actual user intent (choosing an item).
- `texts.label` added with catalog entry. The toggle key already
existed.
Audit script refinements:
- Extended focus-move action list to cover listbox/menu/tablist
navigation (`next-item`, `prev-item`, `first-item`, `last-item`,
`next-tab`, `prev-tab`, `first-tab`, `last-tab`) plus the popover/
dialog actions that are delegated to the containing overlay layer
(`open`, `close`, `dismiss`, `toggle`). A combobox / select / menu
that hosts Escape→close is not under-declared if the overlay layer
owns the sema event.
README rewritten with canonical sections:
- `## Baseline` summarizing air + soma + morfo coverage.
- `## Comparativa` (was `## Comparison`) — table preserved.
- `## Decisiones` documenting the no-emerge-events policy, focus-only
keys, Control composition rationale, multi-select aria propagation.
- `## Gaps` (was `## Out of scope`) rewritten with disposition tokens
(implementar / diferir / descartar) per row. ClearTrigger and
Validation state get `implementar`; Modal+footer gets `descartar`
(combobox is inline by design, not a confirm-style picker).
Audit: PASS 6 → 7. No regressions; the relaxed focus-move list
benefits other listbox/menu components too (will surface on their
audits).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Full walk of icon (passive eidos-only primitive):
- Morfo: validated correct as-is. Single `Provider` part with
`archetype: 'image'`, conditional `aria-hidden` / `role="img"` driven
by the `decorative` prop. Scope `['eidos']` is right (no soma
counterpart). 0 events is right (leaf visual primitive).
- Eidos: 1696 lucide glyphs + IntentIcon. Default exported via the
re-export pattern `export { default } from './icon.svelte';` —
validates the audit refinement below.
- README sections renamed to canonical headers: `## Baseline Air` →
`## Baseline`, `## Comparativa externa` → `## Comparativa`. New
`## Passive justification` section explains why icon ships zero
semantic events (leaf primitive — the accessible name and any
perceptual signal belong to the containing control). `## Gaps
cerrados` renamed to `## Gaps` and rewritten with disposition
tokens (implementar / diferir / descartar).
Audit script refined for two false-positives surfaced by icon:
- A-1.3 (texts.label) now SKIPS pure visual primitives — components
whose `scope` is exactly `['eidos']` AND that declare no events.
Icons don't own text slots; the label lives on the consumer (the
button hosting the icon, shadcn-svelte/Radix pattern). Reported as
pass with "eidos-only passive primitive — no text slot".
- E-1.3 (default export) now recognizes the re-export form
`export { default } from './foo.svelte';` in addition to the
existing patterns (`export default X`, `export { X as default }`).
Audit: PASS 5 → 6. Icon flips to PASS. No regressions; the relaxed
rules don't change verdicts for any other component.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the calendar audit findings:
- README sections renamed to canonical headers: `## Baseline` (was
"Baseline audit"), `## Comparativa` (was "Reference comparison").
Added `## Decisiones` documenting the affirm-on-select choice, the
collapsed `shift-navigate` event for all month/year movement, why
arrow keys don't get their own events (pure focus moves), and the
read-only treatment. Rewrote `## Gaps` with disposition tokens
(implementar/diferir/descartar) for each entry.
- Recipe CSS gains `[data-calendar][data-readonly]` styling: cells
drop their hover affordance and the cursor stays neutral. Selection
+ navigation remain interactive (matches `aria-readonly` semantics).
- Audit A-3.7 refined per the COMPONENT_GUIDE rule "pure focus moves
don't need an event". The check now compares MUTATING keyboard
actions to declared events. Focus-move actions
(next-day/prev-day/next-week/prev-week/next-month/prev-month/
next-year/prev-year/first-day-of-week/last-day-of-week/
focus-{next,prev,first,last,up,down,left,right}) are excluded from
the ratio. Calendar's 10 keys / 2 mutating (Enter+Space → `select`)
/ 1 `commit-select` event now passes correctly.
Project audit: PASS 3 → 5, no new regressions. `range-calendar` also
flips to PASS as a side effect of the focus-move filter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Living backlog for UIX. Records the pending items from the
date-range-picker session (apply modal+footer to other pickers,
month/year-range variants, promote footer buttons to canonical eidos
parts, morfo expansion to cover Footer/Clear/Cancel/Close, browser
tests) plus the queue of post-session components still to audit
one-by-one with severities pulled from `tmp/component-audit.md`.
Each entry is tagged implementar/diferir/descartar so the next picker
session can scan the file and pick the next slice without re-doing
discovery.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User feedback batch from incidencia 2026-05-20:
1. Modal vs inline mode
- New `mode: 'inline' | 'modal'` prop on the soma `DateRangePicker.Provider`.
- Modal wires the popover `modal: true` → outside-click and Escape are
ignored; user must commit via the footer Close button (or revert via
Cancel).
- Provider exposes helpers `clear()`, `cancel()`, `commit()` for the
footer. `cancel()` restores the value snapshot taken on the OPEN edge
(captured via a `watch` on `opts.open` true-edge transition).
2. Footer buttons as boolean props
- `clearButton`, `cancelButton`, `closeButton` props on the picker.
The footer renders only if at least one is true. In `mode='modal'`
the closeButton is forced on (the user always needs an exit).
- `picker-actions.svelte` in the demo reads the picker context via
`DateRangePickerProvider.require()` and renders the enabled buttons
against `provider.clear/cancel/commit`.
3. Range field shape (revert to Chakra-style two boxes)
- Removed the `data-date-range-field-group` wrapper from the demo so
the start and end inputs are rendered as two separate boxed fields
with the icon embedded in the end box, matching Chakra's layout.
- The recipe CSS rules for `data-date-range-field-group` stay
available as an opt-in for consumers who prefer the unified pill.
4. Demo defaults
- `open` starts at `false` so the picker exercises the real
open/close flow when the user clicks the trigger — the segments are
for direct keyboard entry, the popover is for visual exploration.
- Mode toggle (inline / modal) + footer button switches surfaced as
controls in the demo.
5. Plumbing
- DateRangePickerOpts gains `mode`, `clearButton`, `cancelButton`,
`closeButton` (StateProps for mode, ActiveProps for the booleans).
- `DateRangePickerProvider` and `DateRangePickerMode` are now re-
exported from the soma barrel for consumers that wire footer
actions in the calendar tree.
- Test factory updated to seed the new opts.
Verified in browser: trigger opens; click-outside in modal mode is
ignored; Close commits & closes; Cancel reverts to snapshot & closes;
Clear empties the range & keeps open. Heading "May – June 2026" (year
collapse) and centered per-calendar titles still working from the
previous commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes incidencia point B (range field shouldn't show two separate
boxes). New CSS contract: `[data-date-range-field-group]` on the
wrapper element collapses the chrome around its descendants:
- The group owns the border, background, focus ring, invalid state,
variant/ghost treatment, and disabled opacity.
- Each `[data-date-range-field-input]` inside loses its own border,
background, and padding; it just contributes its segments to the
shared row.
- The "to" separator (.range-separator or `[data-range-separator]`)
picks up the field's literal tone.
- The embedded trigger from the previous commit sits flush at the
trailing edge of the group, since `data-date-field-input` already
detects the embedded case.
Demo: add `data-date-range-field-group` to the `.range-field-row`
wrapper. Visually now reads as one input pill: `05 / 20 / 2026 to
06 / 03 / 2026 📅`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes incidencia points D + E:
- Heading collapses the shared year when both visible months are the
same year: "May – June 2026" instead of "May 2026 – June 2026".
Cross-year still renders both ends explicitly ("June 2025 – July
2026"). Implemented in `RangeCalendarProvider.headingValue`.
- date-range-picker demo: drop the per-month title that previously
rendered BELOW each grid (duplicate of the main heading). Instead,
render one centered title per visible calendar INSIDE the header
bar between the prev/next buttons. The grid below now shows just
the weeks. A `range-header-titles` grid container splits the
available header space equally across `month-count` titles, so
each label sits centered over its calendar grid.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root cause of date-range-picker incidencia 2026-05-20 #1 (auto-paging
to next month when the popup opens with a complete range): both
endpoint inputs of a DateRangeField create their own DateFieldProvider
sharing a single placeholder. Each provider has a `$effect` that
mirrors value → placeholder so the calendar jumps to the value's
month. With two endpoints, the END field's effect overrides the
START's on every render and the popup auto-pages to the end's month.
Fix: add an explicit `syncPlaceholderToValue?: boolean` opt on
`DateFieldOpts` (default `true` — preserves single-field UX). The
DateRangeField endpoint Input passes `false`; range placeholder
coordination stays with the range provider.
Verified in browser: value `{ start: 2026-05-31, end: 2026-06-09 }`
with `placeholder = 2026-05-31` now keeps the calendar on
"May 2026 – June 2026" instead of jumping to "June – July". Both
endpoints render correctly with the start/end stripes.
The earlier `reanchorInitialSelection` removal handled the same
symptom inside the range-calendar provider for fresh selections;
this commit handles the OTHER source — the field provider auto-sync
on already-set values.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The calendar icon now lives INSIDE the date input as a flush trailing
affordance, not as a separate sibling button. The change is markup +
recipe CSS only — no API change.
- Demo (date-picker): drop the `.picker-control` grid wrapper; put
`<DatePicker.Trigger />` as the last child of the Input snippet,
after the segments. Codeblock snippets updated to match.
- Demo (date-range-picker): keep the dual-input + separator layout
but move `<DateRangePicker.Trigger />` into the END input, since
the trigger represents the trailing affordance of the whole range.
Both code snippets updated.
- Recipe CSS (both): add nested rules that detect the embedded case
(`[data-date-field-input] > [data-date-picker-trigger]`,
`[data-date-range-field-input] > [data-date-range-picker-trigger]`).
Embedded trigger drops its own border/background, matches the
segment height, sits flush at the trailing edge with
`margin-inline-start: auto`, and uses the segment focus ring on
hover/focus instead of the standalone field ring.
The standalone (sibling) treatment stays available for consumers who
prefer the icon as a separate control.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the second BROKEN component from the audit (after date-range-picker).
Five errors collapse to zero:
- Morfo declares the three `emerge` events the recipe animations were
already keying off (`open`, `close`, `close-dismiss`) plus a
`texts.label` slot. `data-disabled` is now declared on `Trigger` (with
optional severity) so the recipe rule has a real contract to land on.
`aria-disabled` and `texts.label` round out the surface; the new
`tooltip.label` catalog entry ships under
`src/uix/langs/components/tooltip.ts`.
- Recipe CSS: explicit `[data-tooltip-trigger][data-disabled]` and
`[data-tooltip-trigger]:focus-visible` rules. The disabled rule
suppresses pointer events; the focus rule provides a defensive
outline so a focusable trigger shell is at least keyboard-visible.
- README: convert the old `## Pendiente deliberado` section into a
proper `## Gaps` table with explicit dispositions
(implementar/diferir/descartar), and update `## Eventos Sema` to
reflect that tooltip now declares its three emerge events instead of
claiming zero. Comparativa was already complete; the regex fix from
the previous commit picked it up.
Audit: tooltip BROKEN → PASS. Project-wide goes from 1/64/2 to 3/64/0
(PASS / NEEDS-WORK / BROKEN). No more BROKEN components.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes incidencia 2026-05-20 #7 plus the remaining audit gaps:
- Morfo now declares the full event surface for the picker layer:
`open` (emerge, with intent fromProp), `close-range-commit` (commit
fulfill), `close-cancel` / `close-dismiss` / `close-dismiss-outside`
(emerge), and `commit-clear` (commit reset). Adds `data-last-action`
on Calendar for causal exit tinting, plus `aria-label`, archetype
classification, keyboard bindings (Enter/Space on Trigger; Escape +
Tab focus on Calendar), and `apg` URL pointing at the W3C dialog-modal
pattern.
- `texts.label` added with full catalog entry under
`src/uix/langs/components/date-range-picker.ts` so A-1.3 passes.
- README rewritten with the required structure: Baseline (air / terra /
soma actual), Comparativa table (React Aria / Bits UI / Ark UI),
Decisiones, and Gaps with explicit disposition markers
(implementar/diferir/descartar). The pending modal mode + clear
button + demo control prune go in Gaps as `implementar`/`diferir`.
- Fix a pre-existing audit-script regex bug: section-extraction
regexes used `m` flag with `$` lookahead, which matched end-of-line
and truncated section bodies to their headers. Switched to
`\n## ` as the explicit section boundary.
Audit: date-range-picker BROKEN → PASS. Project-wide BROKEN count
0 (was 2 before, now tooltip remains as NEEDS-WORK).
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>
Closes 2 bugs from the date-range-picker incidencia 2026-05-20:
#1 — `RangeCalendarProvider` no longer auto-shifts the placeholder when
a selection lands in the last visible month. The visible months stay
where the user put them; navigation is explicit (prev/next/month/year
controls or keyboard). `reanchorInitialSelection` is removed and its
`shift-navigate` trigger goes with it.
#2 — clicking an endpoint of a completed range now drops only that
endpoint and re-anchors on the surviving one. The previous behavior
cleared both endpoints, which forced users to rebuild the entire range
to amend it. The provider already implemented this; only the test
codified the old behavior. Test rewritten to match the documented
intent and symmetric for start/end.
The two range-calendar tests that previously asserted the wrong
behavior now cover the correct invariants.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Morfo now declares text slots as absolute idlangrefs in `texts: Record<string, LangRef>`,
not literal multilingual records. Multilingual content moves out of morfo entirely
into per-component catalogs under `src/uix/langs/components/{kebab}.ts`, merged
globally by `ActiveUix` via the new `componentLangs` barrel. Morfo stays a pure
abstract surface; localization lives in its own layer.
- Type: `translations?: LangNode` → `texts?: Record<string, LangRef>` in
`src/uix/morfo/types.ts`. Doc and test fixtures updated.
- Catalog: new `src/uix/langs/` directory with `common.ts` (was `langs.ts`),
`components/` (one file per migrated morfo, plus a barrel), and an aggregate
`uixLangs`. `ActiveUix` registers both namespaces at boot.
- Registry: `registerMorfo` no longer carries a translations fingerprint and
no longer extends `ActiveLangs` dynamically. `connectMorfoTranslations` and
`MorfoTranslationTarget` deleted from the public morfo barrel.
- Validation: schema.ts drops the relative-translationRef-in-morfo.translations
check; catalog presence is enforced by the new `scripts/translations-check.ts`
(wired as `npm run translations:check`).
- Audit: `scripts/component-audit.ts` rule A-1.3 now validates `texts.label`
idlangref shape (`#?components.{kebab}.label|Fallback`) and explicitly errors
on any surviving legacy `translations:` field.
- Migration: `scripts/migrate-translations-to-texts.ts` is the one-off Node
script that produced the 42 morfo rewrites and 42 catalog files.
- Tests: `morfo/registry.test.ts` rewritten around the simpler contract;
`active-uix.svelte.test.ts` reflects boot-time catalog availability instead
of lazy per-morfo extension.
Verification: `npm run check` (0 errors), `npm run translations:check`
(0/0), `npm run morfo:check` (36/36 routed). The 4 pre-existing failures
in `test` are unrelated (date-picker types, css regen, range-calendar
clear, color contract).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>