Clean-room fixes (2026-07-11, sobre los P1 de la auditoría):
- EID-1: el anillo de foco de la fundación migra a `outline` (§32) — el último
box-shadow del catálogo; :where()-wrapped para que las recetas siempre ganen;
doctrina actualizada en theming/reference + changelog.
- EID-2: opacidades literales de archetypes.css → var(--opacity-disabled/hover).
- SEM-1: política de live-region unificada — la urgencia va SOLO por intent
(threat/loss → assertive), runtime y AnnounceChannel ya coinciden.
- MOR-4: card-group con morfo propio (scope eidos) + guard de enumeración.
- THM-1: component-audit + recipe-css-contract escanean los *.css hermanos
(calendar-select, color-picker-spectrum, date-range-picker-time…); R-2.1
caza también hsl()/oklch(); violaciones de los secundarios corregidas.
- THM-3: canario de censo de toggle volteado (lint.test); commit-block promovido
a firma global block-shake; regen de generated/base.css.
Docs nuevos:
- docs/audit-new-10-july/audit-active-uix.md — auditoría clean-room 2026-07-10
del ecosistema (hallazgos P1–P3 con evidencia ruta:línea, matriz y gap analysis).
- docs/process/PLAN-scene-ambient-pack.md — plan por fases: motor arts/scene +
pack Ambient (45 animaciones de demos) + vía de promoción agéntica (Aura);
gate de decisiones D1–D9.
- docs/Disenando_lo_que_ocurre_FINAL.{docx,pdf} — edición FINAL del libro.
- README.md raíz (esqueleto de paquete).
Excluidos deliberadamente de este commit: words/palabras/chronos (pista
separada), web/routes/alpha (terminado, sin commitear por decisión), rt.css
(volcado de referencia Radix Themes).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- docs/audit/components/_cierre.md — the programme closure: final gates
(matrix 134/0/0, contracts 37/37, vocab 0, baseline 57), the full package
journey with hashes, the canonized method (dossier template, evidence-only
exceptions, no-cascada, machine valves), the findings that outlive the
programme, and what moved to new owners (demo frame, active tracks,
follow-ups).
- docs/process/continue-cleanroom-fixes-2026-07.md — the LIVE turnkey for
the next sessions: the clean-room severity table as the queue, the state
reconciliation (SOM-5 P1 already closed post-snapshot; SOM-3 partial), the
proposed P1 order (machine-extending THM-1/MOR-4 first), the WIDENED
verification protocol (134/0/0 and 37/37 must HOLD), exclusions and the
side queue.
- The old audit-fixes handoff is sealed as historical with a redirect header.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
color-swatch (the S7 passive exemplar gains its Baseline/Comparativa — the
primitive extracted in the ColorPicker redesign; React Aria is the only
headless ref shipping it) · onion-menu (nobody ships a headless radial menu;
Menu Button semantics + depth-tinted drill rings as the differentials; the
English "## Decisions" heading renamed to the canonical "## Decisiones") ·
radio-cards (the S11c delegation exemplar: reuses RadioGroup soma wholesale —
zero duplicated behaviour; multi-select variant DISCARDED, CardGroup+
ToggleGroup own cardinality per the 2026-06-27 decision) · scroll-frames
(+ the missing Passive justification: the frame is a pure function of scroll
progress — the gesture belongs to the page) · timeline (the two-axes
differential documented: data-status drives marker geometry, data-intent
drives valence — decoupled, unlike Ant/MUI/Flowbite; apg declared in C5
none-rationale form; the two 1em literals annotated — they inherit the
item-scaled font, the trf-separator precedent).
Machine: 129 -> **134 PASS / 0 NEEDS-WORK / 0 BROKEN** — the audit
programme runs the full catalogue green. contracts 37/37 · vocab exit 0 ·
type baseline 57.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
badge: the Remove hover joins the MD3 state layer (§38) — background-image
veil with var(--state-hover); the hand-rolled 15% currentColor mix predated
the rollout. Verified in the demo (CSSOM rule + live removable badges).
The other 8 were exceptions WITH READ EVIDENCE, not fixes — each README
documents where the treatment actually paints:
- alert-dialog / fab / split-button / qr-code: every interactive part
composes the canonical <Button> (Action/Cancel wrappers, the fab itself,
both split halves, the DownloadTrigger) — focus + disabled in button.css.
- date-picker: the eidos root overlays data-field and the trigger input is a
composed DateField.Input carrying data-field-control — disabled/readonly
paint via the shared field.css chrome (R-1.2 + R-1.3).
- date-range-field: LAYOUT-ONLY recipe; each endpoint is a real DateField
whose [data-field] gets field.css :focus-within — mirror of the trf twin.
- mask-field: IS-A Field (fieldRole provider/control/input) — the whole
control chrome incl. the focus ring lives in field.css (R-1.2/3/4/5).
- pin-input: the ring ALREADY paints on [data-pin-input-cell][data-active]
(canonical --focus-ring-* tokens) — the soma-written focused-cell flag,
a state-attr vehicle the R-1.5 heuristic does not recognise by name.
Matrix: 120 -> 129 PASS / 5 NEEDS-WORK / 0 BROKEN. Type baseline 57.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The convergence session's second half (with field commit-submit landed in
33f346ed): the three remaining contracts failures are gone and the full
catalogue-guards suite is green for the first time.
float-panel (A-3.7 + the runtime-discipline tail):
- Keyboard move/resize modes now sound through the SAME handle events as the
pointer gesture (the knob doctrine): entering the mode fires drag-start /
resize-start (the pickup cue), committing or cancelling fires drag-end /
resize-end (the release cue lands even on revert, like dragging back to
origin). The kb path had rich AT announcements but zero perceptual events.
- Every direct style write migrated to ActiveDom's per-element
`writeProperty` / `removeProperty` — a SYNC setProperty delegate, so the
deliberately same-task hot path (drag glued to the cursor, no re-render)
keeps its exact timing. The layout effect's empty-string resets became
removeProperty; comments updated where they cited `el.style.*` directly.
- The kb-readout debounce timer moved from `win.setTimeout` to the injected
`uix.timers.schedule` (announce house pattern: keyed + replace + meta).
component-audit A-3.7 refined to count DISTINCT mutating actions (Set) — the
rule's own stated semantics ("Enter and Space both map to the same action"):
float-panel's kb-move rides 13 keys but is ONE gesture; occurrence-counting
flagged complete wiring as under-declared. A-3.7 failures catalog-wide: 0.
media-player: the raw listeners in media-provider.ts are the pluggable
playback-engine PORT's deliberate design (header doctrine: Svelte-agnostic,
"no runes, no $adom" — third-party hls.js/embed engines implement the same
contract). The listeners guard gains the documented port exemption (the same
valve the timers guard already had for announce's global fallback).
Verified live in the float-panel demo (pointer + keyboard): drag follows the
cursor pixel-perfect through the new write path (24,24 -> +60,+40 -> 84,64),
release commits, drag-start stamps on both pointer and keyboard grabs, arrow
nudges snap to the device-pixel grid, Enter commits.
Gates: contracts 37/37 (first full green) · float-panel suite 8/8 · machine
120 PASS / 14 NEEDS-WORK / 0 BROKEN · vocab exit 0 · type baseline 57.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adoption, not design (audit ficha field F-2, refined against password-field):
the family doctrine is WRITTEN in the password-field morfo — per-keystroke
typing is plain data flow; Enter is the terminal gesture. textarea already
had commit-submit; the base Field.Input did not — the text-input pair was
asymmetric, and the machine flagged the shell as interactive-with-0-events
(A-3.1), converging with the clean-room audit finding "field infra-declarado".
- morfo: scope gains sema; expression family-default (the base shell speaks
with the family voice — the concrete fields carry the packs); apg textbox
(the password/textarea cite); commit-submit event (commit.submit, neutral,
post, target input) + Enter in the Input keyboard contract.
- soma FieldInputProvider: onkeydown — plain Enter (no modifiers), gated on
disabled/readonly, triggers commit-submit through the runtime.
Verified live in the field demo: Enter stamps data-event="commit-submit" /
family=commit / intent=neutral / phase=active on the input and the demo trace
logs the event. Machine: field -> PASS (A-3.1 + A-1.4 cleared). Field suite
3/3, vocab exit 0, type baseline 57.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The demos will be exposed through a NEW frame for the whole system, so drift
against the CURRENT page template is not fix-work. checkDemo results are
downgraded to warn at the call site and D-* rules are excluded from the
verdict count entirely — the data stays visible in the report, the component
matrix stops being painted by it. The D-* rules get rewritten against the new
frame when it lands.
Matrix: 102 -> 118 PASS / 16 NEEDS-WORK / 0 BROKEN. The 16 now decompose into
exactly three real families: the R-1.x focus/disabled census (9), the old
dossier-form READMEs (6, the reduced lote 6), and two contract items — field
A-3.1 and float-panel A-3.7 — which converge with the new clean-room audit
finding "field/float-panel infra-declarados".
Type baseline 57.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The "3 ajenos" census. What the 12 pre-existing failures really were: a few
genuine violations + several guards CRASHING on menu-dial's missing
exports.ts (ENOENT) and hiding their true violation lists. All addressed
except the two runtime-discipline files (see tail).
- knob: soma barrel export + the missing soma README (headless contract —
parts/ARIA/absolute angular keyboard/props/published CSS vars/sema table).
- menu-dial + onion-menu: joined SOMA_INTERNAL_PRIMITIVES — both are
documented provider-less bridges (keyboard dispatch + nav maths + emission;
the eidos root owns state and render). The scope guard now subtracts the
internal set from BOTH sides so a bridge may truthfully keep 'soma' in its
morfo scope without being a public namespace. Self-alias imports fixed;
the two morfo docblocks realigned to the implementation ("soma owns open"
was drift — the bridge files own dispatch/emission only; onion-menu's
stale "family-default, no pack" tail also corrected, S3b shipped its pack).
- Public barrels decoupled from provider implementation filenames (the
picker `internals.ts` house pattern) for the 6 offenders: calendar,
cropper, field-langs, gradient-builder, gradient-picker, image-adjustments.
- listSourceFiles now skips ACTIVE_DEV_TRACK paths (words/palabras/chronos)
— the track doctrine already said catalogue guards skip them; the
file-scanning guards just didn't.
- data-archetype added to the known morfo attrs (compiler-emitted from part
archetype declarations); the four field-overlay picker triggers
(color-picker, date-range-picker, time-picker, time-range-picker) declared
archetype 'trigger' while stamping 'field-trigger' at runtime — morfos
aligned to the date-picker/ntp precedent (contract now matches the DOM;
zero runtime change, the stamp already won).
- float-panel: the consumer-placed data-float-panel-no-drag opt-out marker
is now declared in the morfo (presence entry with the consumer-marker
note) — the NON_DRAG_SELECTOR contract stops being invisible.
Remaining tail (3 failures, deliberately deferred to a dedicated
runtime-discipline session with browser gesture verification):
float-panel-provider direct style writes + raw timer (drag/resize hot path
-> dom.apply/uix.timers migration) and media-player media-provider raw
listeners (-> dom.listen).
Gates: contracts 34/37 (was 25/37) · component-audit 102 PASS · vocab exit 0
· type baseline 57 · eidos-lint invalid 0 on the four touched pickers ·
compile + eidos contract tests 42/42.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- contracts.test: metrics joins SOMA_INTERNAL_PRIMITIVES (with picker-shell) —
its soma layer is a deliberate provider-less runtime bridge (documented
in-place: "no provider/state class"; morfo scope ['sema','eidos'] on
purpose), NOT a public component namespace. Classification fix, not an
exemption of debt: the barrel/exports/README guards stop mis-demanding a
canonical Provider shape it was designed not to have. Also fixes the bridge's
$soma self-alias import (the internals guard). Contracts: 13 -> 12 failures,
ZERO metrics mentions (the remaining 12 belong to knob/menu-dial/onion-menu).
- morfo: apg 'none — data display readout' (C5 form) — clears A-1.4; the
README's stale "A-1.4 N/A" audit note updated to point at the declaration.
- README upgraded to dossier form (Baseline Untitled-UI/Tremor/Chakra Stat,
Comparativa with the goodTrend decoupling as the differentiator, Decisiones
incl. the internal-bridge classification, Gaps with the D-1.2 demo union
routed to the cross-catalog demo census).
- Suite (the ficha's F-2): resolveDeltaIntent extracted to a pure helper
(delta-intent.ts, consumed by Metrics.Delta) + delta-intent.test.ts 4/4
(direction/valence decoupling, goodTrend flip, flat neutral, explicit
override) + metrics.svelte.test.ts (the live-update bridge: signal.notify
carries the prop-source intent; the morfo's requiresLiveRegion contract).
Gates: component-audit metrics clean except D-1.2 (demo census) · eidos-lint
invalid 0/class-hooks 0 · type baseline 57 · contracts 25 pass/12 pre-existing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The package's headline item — the generic Picker's transactional events —
turned out ALREADY PAID at checkpoint S3b (commit-set fulfill / commit-cancel
/ commit-reset declared, pack shipped, soma triggers wired, suite green): the
2026-07-07 ficha claim was stale and the ntp dossier had propagated it; the
dossier row is now corrected with the verification.
What this session actually adds:
- component-audit R-2.1 learns the `/* literal: <reason> */` escape valve
(same declarationWindow mechanism the R-4.x rules already use) — the tokens
doctrine's physically-fixed exception finally has its machine valve. Full
run confirms no other component silently passes (same 102/32).
- natural-time-picker.css: all 9 raw colors annotated line-by-line (5 skies +
knob ink + line/ticks/shadow — sky ARTWORK, not theme roles; identical in
light and dark). NOT tokenized, per the ficha's F-2 disposition. R-2.1
clears for ntp.
- soma/components/natural-time-picker/README.md: the headless contract — the
domain coordinator's state surface (minutes bridge, sliderValue, isDay,
moments, steppers, display) + the fully-delegated perceptual chain table
(TimeField / Slider / generic Picker / Popover).
- natural-time-picker-provider.svelte.test.ts: 12 tests green (band bridge
with wrap + seconds preservation, steppers incl. second non-rolling wrap,
moment jumps + exact-match chips, readonly lock, daylight window bounds,
12/24h display incl. the no-soma.dates fallback, granularity displayTime).
Gates: component-audit 102 PASS (ntp R-2.1 cleared) · type baseline 57 ·
new suite 12/12.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The initiative's real remaining scope (verified 2026-07-10 against the repo;
the "uncommitted work" memory was stale — builder landed in b3d0e290, picker
in f5eb8acb):
- contracts.test: remove gradient-builder/gradient-picker from
ACTIVE_DEV_TRACK. The catalogue guards found exactly ONE violation — the
missing soma barrel exports — now added (GradientBuilder, GradientPicker).
Post-graduation: same 13 pre-existing failures (knob/menu-dial/metrics/
onion-menu debris from other WIP), zero gradient mentions.
- apg declared on both morfos: builder cites slider (every stop + the angle
dial IS a slider thumb — the keyboard-a11y differentiator); picker cites
dialog-modal via the composed Popover (same rationale as date-picker/ntp).
- Both eidos READMEs upgraded to dossier form (Baseline / Comparativa /
Decisiones / Gaps) transplanting the CONTINUE.md competitive research
(OKLCH model shared with the token engine; keyboard-editable stops — the
field is mouse-only RGBA exporters). Stale "active dev track" recipe notes
corrected.
- gradient-builder demo: D-1.2 was a false negative — the tab union was the
correct v2 9-tab set, just formatted multi-line; joined to one line
(canary format). SSR smoke: 200 + 40 data-gradient-builder attrs.
Remaining on both: D-1.5/D-3.1/D-4.3 — the catalog-wide demo-census (fails
on 10/7 components INCLUDING the button canary), tracked in the dossiers'
Gaps, not gradient-specific.
Gates: component-audit 102 PASS · contracts 24 pass/13 pre-existing ·
vocab exit 0 · type baseline 57.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dialog-001 a11y-race fix (53b6f629) removed the literal role from dialog
Content on purpose — variant-dependent dialog/alertdialog, DialogContentProvider
is the sole authority — but the compile test kept asserting the static hoist on
that part, red on HEAD since then. Adjudicated: stale test, not a compiler
regression (mechanism verified intact via Title, which does declare a literal
role: heading).
- Re-point the hoist mechanism test at Title (heading).
- New guard: Content emits NO static role, locking dialog-001 against a
re-declared literal reintroducing the syncAttrs overwrite race.
Suite 35/35 (was 33+1 failed); type baseline 57.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Option (a) with riders: the ::before slop stands for bare markers (the
Material mdc-touch-target / Android touch-delegate / iOS hit-inset lineage).
Rider 1: dense coarse stacks must keep pitch >= --touch-target (WCAG excludes
overlapped area; bare boxes still hold AA via 2.5.8 Spacing worst-case).
Rider 2: the Field labeled-row task stays open as the ADDITIVE improvement —
the row becomes the real AAA target (2.5.5 Equivalent) and the slop remains a
harmless net. Chronicle updated in changelog §37; standing in reference §37;
handoff block resolved.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- natural-time-picker: expression 'delegated' (canon S11; composed children
own every event — clears A-3.1 truthfully; the Picker-generico event debt
stays tracked in its dossier Gaps for the S9 package).
- s-text-virtual-list: drop archetype 'item' from Row (0-event display
surface; item pulled cursor/user-select/hover + a height floor fighting
the canvas-computed row heights, and flipped the classifier to
interactive -> spurious R-1.5). Verified live: rows keep canvas heights,
no overlap, text selectable. Machine now classifies passive (F-1.5
justification already in the dossier).
- time-range-field / field-langs / link-preview: apg declared (spinbutton /
C5 'none — rationale' x2) — clears the three A-1.4 warns.
- button: soma README realigned to the real types (loading/intent/color —
the old pending/pressed/expanded/href surface no longer exists) + the two
stale docblocks from the audit ficha (types aria-label layer leak, wrapper
data-state mention).
- password-field: morfo StrengthMeter comment aligned with types (default
$libs/secs scoring, overridable) + index.ts example updated to the
post-S6 Control composition.
- Affected dossiers' Gaps tables updated (implemented rows removed).
Verified: component-audit 102 PASS (A-3.1/R-1.5-artifact/A-1.4 x3 cleared),
vocab guard exit 0, type baseline 57, eidos contract tests 7/7, stvl checked
in browser. Note: compile.test.ts 'hoists role into staticAttrs' (dialog)
fails on HEAD pre-existing — unrelated to this pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The S7 doctrine already landed in P0/P1 (guide §5 classes, machine classifier
via morfo evidence, F-1.5 marker rule, canonical exemplars cited). This closes
the two missing artifacts:
- color-swatch: re-anchor the existing "Why a primitive" rationale under the
canonical ## Passive justification heading (machine rule F-1.5) with the
formal classification paragraph. Verified against the machine: the F-1.5
failure is gone from the audit report (remaining F-1.1..1.4 warnings are the
grandfathered dossier — the S4 pass).
- spin-field: layer README formalizing the shared-layer contract per guide §5
(consumer-stamped selectors, sizing sourced from Field's resolved control
privates, stepper-only recipe vocabulary, the two compound-specificity box
overrides, foundation loading, consumer list). radio-cards needs no F-1.5
(machine classifies it interactive); its canonical citation lives in the
morfo header (verified).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both were type-only: the assertions read morfo-declared keys (data-editing,
role) that DO reach the provider props at runtime through the renderProps()
spread, but the static type of runtimePart.assert erases them. Read them
through an explicit indexed cast instead; suites stay green (6/6). Baseline
drops 59 -> 57 — protocol number updated in the handoff, with the remaining
out-of-mandate errors inventoried (9 src/: file-upload x4, navigation-menu,
arts/connection x4; ~48 web/ demos).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The audit's directed verification confirmed the gap: panel sizes were
internal-only state (per-panel defaultSize seed + onResize/onResizeEnd
callbacks), so a consumer could not control or persist the layout. Add the
framework-norm bindable pair:
- `sizes` (bindable, % by panel registration order): seeds the initial
layout when provided, stays in sync while the user resizes, and an
external write re-applies the layout clamped to each panel's min/max
(collapse bookkeeping is not re-derived — parity with reference
setLayout APIs). Plumbed through the eidos wrapper (a spread can't
two-way bind, so it re-binds explicitly).
- `onResize` renamed to `onSizesChange` — with the bindable it forms the
norm pair (X + onXChange); keeping both names would alias one concept.
`onResizeEnd` stays (distinct commit moment, fires with sema
commit-set). Internal mutations funnel through emitSizesChange, which
syncs the binding and fires the callback at the same moment.
READMEs + demo updated (live sizes readout + programmatic 50/50 write,
API table). Provider test covers the pair: binding seeds over
defaultSize, drag syncs binding + callback, external write applies
clamped. Suite 4/4; browser-verified both directions (drag 50/50 -> 80/20
syncs the readout; the 50/50 chip re-applies the layout). Baseline 59.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
textarea (the audit-CONFIRMED violation): measureAutosize wrote
height:auto then synchronously read scrollHeight + getComputedStyle — a
forced reflow on EVERY keystroke (input / clear / mount). The read block
now defers through the context ActiveDom's dom.measure: write stays in
the event turn, the read runs post-layout in the coalesced pre-paint
frame (typing bursts coalesce to one measurement; stale pending reads
cancel), and the clamped height is written back inside the frame
(read->write, no mid-turn reflow). Verified live: style.height is 'auto'
synchronously after input (the old code showed px immediately) and the
grown px lands next frame.
Directed census over the audit candidates, violations fixed:
- scroll-area: the thumb's size/offset getters read the track's
clientWidth/Height inside the thumb props $derived — a forced reflow
per scroll tick. The scrollbar now mirrors its track size into $state
fed by a ResizeObserver; thumb math + drag read the mirrors. The
initial viewport measurement defers through dom.measure.
- virtual-list (both viewports) / virtual-grid: the initial
updateSize/updateScroll ran sync at ref-attach right after DOM
insertion — deferred through dom.measure (observers keep them fresh).
- carousel: the mount seed defers through dom.measure (lands pre-paint,
before any interaction), which closes the async-observer gap the
viewportSize $derived's live clientWidth fallback guarded — that
fallback (a render-time layout read) is removed.
Classified compliant (event-time reads before writes — D13 is a timing
rule): float-panel (drag already caches bounds; open seed is one-shot),
cropper, splitter.
Suites updated to the deferred contract and green (scroll-area 3/3,
carousel 4/4, virtual-list, virtual-grid); baseline 59.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add getWeekInfo(locale) -> { firstDay, weekend } to $libs/days (Intl
convention, 1 = Monday ... 7 = Sunday): firstDay mirrors the internal
getWeekStart (Intl weekInfo + region-table fallback — one source of truth,
now exported from the vendored port), weekend comes from Intl.Locale
weekInfo (getWeekInfo() method or the Safari weekInfo accessor) with the
DOCUMENTED Saturday/Sunday fallback. Cached per locale.
Calendar + RangeCalendar derive their weekend flag from it: the shared
helpers.isWeekend(date) hardcoded Sat/Sun against a fixed 'en-US' — wrong
for e.g. ar-SA (Fri/Sat) — and now takes the provider's effectiveLocale.
Verified live: data-weekend moves to Fri+Sat under ar-SA, stays Sat+Sun
for en-US / es-ES; firstDay verified (es-ES Monday-first, en-US
Sunday-first grids; explicit weekStartsOn still overrides).
Directed firstDay verification also caught getSimpleWeekNumber mixing
frames: an ABSOLUTE weekStartsOn (0 = Sunday) was passed to
getLastFirstDayOfWeek with the display locale, which re-interprets the
index relative to that locale's own week start — es-ES 'simple' week
numbers anchored the year on a Tuesday and ran one week behind. Anchor in
the fixed en-US frame (same convention as buildMonth's explicit branch);
the dead locale param is removed from both call sites.
Tests: getWeekInfo (es-ES / en-US / ar-SA / cache identity) + new calendar
helpers suite (locale weekend, grid week-start per locale, week-number
regression). Suites 15/15, baseline 59.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The range endpoint colors lived as private `--_calendar-range-*` tokens declared
in range-calendar.css AND redundantly re-tuned by date-range-picker (reaching into
a sibling's privates). Promote them to the calendar-surface PUBLIC API: 8
`--calendar-range-{start,end}-{solid,solid-hover,border,text}` tokens in the
`calendar` recipe (`:root` defaults), consumed by range-calendar via the public
name. DateRangePicker drops its 6 `--_calendar-range-*` blocks — the composed
range calendar already paints from range-calendar.css, so the drp declarations
were redundant.
Deliberate asymmetry (documented in range-calendar.css): START defaults live in
the recipe `:root` so a theme can override them via config; END binds to the live
`--_calendar-accent-*` on the host (the accent is a per-color private that only
cascades there), so its `:root` defaults are the nominal public default. Both stay
public per the S8 verdict, preserving the original "end may differ from accent"
capability.
Value-preserving: recipe-css-contract 25/25, baseline 59, eidos-lint invalid 0,
browser-verified identical across default/affirm/secondary/fulfill on the
standalone RangeCalendar and the DateRangePicker popover.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mark S6 done (16 fields compose field.css + 199 orphaned recipe tokens pruned +
date/time pickers de-aliased onto the Field public tokens, commits 5eb92132..
d1670caa). Rewrite "lo que queda" to start at S8 calendar-surface; record the
two non-blocking follow-ups (TimeRangePicker endpoint data-size threading
task_d66356de, PickerProvider deferValue migration) and the S6 method lessons
(run the recipe-css-contract vitest scope after CSS refactors; --field-segment-
height only exists under [data-field][data-size]).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The date-range / time / time-range pickers referenced --date-field-* /
--time-field-* recipe tokens to paint their field-shaped parts (standalone
trigger box, wrap gap/font, embedded trigger). That is a cross-component alias
— the exact drift the S6 Field composition eliminates. Point every one at the
FIELD FAMILY public tokens instead (--field-control-* / --field-* / the B.1
--field-segment-height), or at the underlying primitive where Field has no
matching token (the wrap column-gap keeps the tighter segment rhythm). Each
substitution is computed-value-identical (both sides resolve to the same
primitive) — adversarially verified + browser-confirmed on all four pickers.
Also drop dead private declarations the compositions left behind
(--_time-field-padding-inline / --_time-field-segment-padding-inline, and
--_time-field-gap in time-picker) and, now that nothing aliases them, prune the
32 newly-orphaned date-field/time-field chrome tokens from the recipe +
generated CSS (recipe-css-contract orphan guard green, 25/25).
TimeRangePicker's embedded clock-trigger keeps sizing from the picker-local
--_time-field-height (not --field-segment-height): its composed endpoints don't
carry their own [data-size], so the field-family segment-height would pin to md.
That endpoint size-threading gap is pre-existing (DateRangePicker threads it,
TimeRangePicker doesn't) and tracked separately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The S6 Field composition moved each field's chrome (border / radius / bg /
height / padding / font / label) into field.css, leaving the per-component
recipe tokens still emitting but unconsumed. recipe-css-contract.test.ts's
"does not leave declared public recipe variables orphaned" test went red with
167 tokens across spin/date/time/color/search/mask/password-field.
Remove the confirmed-orphaned keys from each recipe (mask-field emptied →
entry dropped entirely); regenerate generated/base.css. Value-preserving:
the removed tokens had zero consumers (the test is the oracle), browser-
confirmed identical rendering on search-field + password-field.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Password is the one field with content BELOW the control (the strength meter), so
unlike the collapsed text fields it needs the full canonical Field structure:
provider STACK > control BOX + supporting content. Added a `<PasswordField.Control>`
part (morfo + soma provider class + component + exports/types + eidos wrapper/index)
that carries `fieldRole('control')`; the Provider takes `fieldRole('provider')` (the
stack) and the input `fieldRole('input')`.
- field.css owns the control box chrome (border/radius/bg/height/padding/focus-within/
invalid/disabled) + the input reset. password-field.css keeps ONLY the affixes:
visibility trigger, caps-lock indicator, strength meter, and the input's mask-tracking.
- REMOVED the 2-row grid + `::before` chrome hack (it existed only to avoid `:has()`
layout flicker without a control element). Now the strength meter is a plain stack
sibling BELOW the control — appears/disappears with no `:has()`, no flicker.
- No tokens aliasing the Field base.
This is the canonical composition (why A over the de-alias compromise: composability
IS the S6 canon; the "API change" — wrapping row 1 in <Control> — aligns password to
how Field itself works, it doesn't invent API). Contained: the demo is the consumer,
updated here.
Baseline unchanged (59). Verified: Provider stack (flex column), Control box paints
from field.css (36px/border/pad/radius), input + eye affix in a row, strength meter
8px below as a sibling, :focus-within ring. Closes S6 field composition — all 16
fields now compose field.css.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Converts the shared `spin-field.css` layer (NumberField + CssField) to the
collapsed text-field pattern. Both morfos add `fieldRole('provider')` +
`fieldRole('control')` to the spin-field box (data-field + data-field-control
alongside the existing data-spin-field marker) and `fieldRole('input')` to the
bare input; both eidos wrappers import field.css.
spin-field.css drops its ENTIRE parallel control chrome (border/radius/bg/height/
padding/font/focus/invalid/disabled/variant/readonly + all `--spin-field-*` chrome
tokens) — field.css `[data-field-control]` owns it now. It keeps ONLY the stepper
affordances (increment/decrement triggers, scrubber, split + stacked layouts,
themeable glyphs), re-sourced off field's OWN resolved control tokens
(`--_field-control-height` / `--_field-control-padding-inline` / `--_field-control-gap`)
so the steppers always match the input row — no parallel size axis. Two box overrides
survive at `[data-spin-field][data-field-control]` (0,2,0, beats field.css regardless
of the global-vs-per-mount load order): `overflow: hidden` (edge steppers break out
of the padding then clip to the corners) and `gap: 0` (steppers butt flush via dividers).
Baseline unchanged (59). Verified in browser: number-field + css-field paint from
field.css (36px/border/radius/overflow), steppers 36px flush at the edges with
dividers, split layout intact.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Same collapsed text-field pattern as the search-field pilot: mask-field is a single
inline-flex row box (input only, no affixes), so its Provider part collapses
`fieldRole('provider')` + `fieldRole('control')` and the `<input>` gets
`fieldRole('input')`. field.css owns the entire box chrome (border/radius/bg/height/
padding/hover/focus/invalid/disabled/variant/size) + the bare-input reset; mask-field.css
drops from 143 lines to a single rule — the input's `tabular-nums` (masked patterns are
digit-heavy, keep glyph columns aligned so fixed literals don't shift as the mask fills).
Baseline unchanged (59). Verified: box paints from field.css (36px/border/pad/radius,
flex row), input reset + tabular figures.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pilot for the TEXT-field pattern (distinct from the segmented fields): a text field
is a single inline-flex ROW box (input + affixes), with no label/segment stack — it
IS-A control. So its morfo collapses BOTH Field roles onto the one box element:
`fieldRole('provider')` + `fieldRole('control')` (data-field + data-field-control),
and the `<input>` gets `fieldRole('input')`.
- field.css owns the box chrome (border/radius/bg/height/padding/gap), the input
reset, focus (`:focus-within`), invalid and disabled. search-field.css keeps ONLY
the search affixes (leading icon, loading spinner, clear-trigger empty-state) and
the native `<input type=search>` clear-button suppression. No tokens aliasing Field.
- field.css `[data-field-control]` now sets `flex-direction: row` explicitly: on a
collapsed box the provider's `flex-direction: column` (label/control stack) leaked
in and stacked the affixes vertically. A control is always a prefix/input/suffix
row, so this is a no-op for non-collapsed controls (verified date-field unaffected).
Baseline unchanged (59). Verified: box paints from field.css (36px/border/pad/radius),
input reset, `:focus-within` ring, icon + clear in a row.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Applies the field-composition pattern to color-field (the last segmented field):
- color-field: chrome / label / invalid / disabled / focus come from field.css via
the fieldRole identity (data-field / data-field-control); color-field.css keeps
only its GENUINE specifics — the channel segments (3ch / hex 6ch), the vertical
divider separators, the leading value swatch, the format <Select>, and the
`gap: 0` control override (per-element spacing, so a flex gap would double the
dividers). No tokens aliasing the Field base. Imports field.css.
- ColorPicker: the in-popover value row IS a composed ColorField (it already renders
a ColorField provider element carrying data-field + threaded data-size), so it
only needed field.css imported for the chrome to resolve inside the portal.
Baseline unchanged (59). Verified in browser: color-field paint + label + swatch
(18px) + format select + 5 segments; picker value row paints with field chrome.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Applies the proven date-* pattern to the time family (exact structural parallel):
- time-field: chrome / label / invalid / disabled / focus come from field.css via
the fieldRole identity (data-field / data-field-control); time-field.css keeps
only segment specifics. No tokens aliasing the Field base. Imports field.css.
- time-range-field: COMPOSES two real <TimeField>s (soma TimeRangeField.Input →
TimeField.Provider > TimeField.Input per endpoint) bound to the range slice;
size/variant/color thread through a visual context; CSS is layout-only. (No
syncPlaceholderToValue opt-out needed — TimeField has no calendar to auto-page,
and the controlled placeholder keeps the endpoints from mutating each other.)
- TimePicker (single field): root carries data-field/data-time-field (A27);
imports field.css.
- TimeRangePicker: inherits the composed endpoints; imports field.css (no
double-box — its recipe never re-declared the endpoint box).
Baseline unchanged (59). Verified in browser: time-field paint + label, range
endpoints = composed TimeFields (nested provider>control, size threaded, value
renders), both pickers paint, no double-box.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes the two remaining date-range-picker cleanups after the composition refactor:
- Size threading (functional): the eidos `<DateRangePicker.Input>` now reads the
picker's visual context and forwards data-size/variant/color to soma's Input
(= the composed DateRangeField.Input), so each endpoint's composed <DateField>
self-themes at the picker size. Verified reactive (md → lg resizes both endpoints).
- De-alias / prune (no-token-alias hygiene): removed the orphaned
`--_date-field-padding-inline` / `--_date-field-segment-padding-inline`
re-declarations (their consumers — the endpoint box + segment rules — are gone);
reduced the shared label rule to layout-only (colour/size come from
date-range-field.css); RETARGETED the endpoint-invalid rule from the now-borderless
endpoint wrapper to each inner `[data-field-control]` with Field's
`--field-control-border-invalid` (it was a no-op post-composition — invalid now
paints again); and re-based the embedded trigger on the Field `--field-segment-height`
token + shared segment-active tokens + foundation radius (no date-field aliases).
Residual (coupled to the pending date-field recipe-token pruning): the UNUSED
standalone trigger + the root wrap-stack still reference date-field recipe tokens
(height/gap/font-size/accent); they resolve correctly and retire with that pass.
Baseline unchanged (59). Verified: two boxes, embedded trigger sized/rounded,
invalid paints, size reactive.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Design decision (2026-07-08): the range picker keeps TWO separate composed
<DateField> boxes side by side — the border stays on each DateField, never on a
wrapper enclosing both. This overrides the reference-frameworks default (a
web-sourced sweep of React Aria / MUI X / Ark / Mantine / Bits UI found single-box
is the norm 5/5), which is the user's call.
The former `[data-date-range-field-group]` treatment moved the chrome onto a
consumer-supplied wrapper div to collapse the two endpoints into one box. It was
CSS-only, unused (no component part, no demo), and — after the composition
refactor moved each endpoint's box to its inner [data-field-control] — it targeted
the wrong element anyway. Removed outright. Verified: two endpoint boxes intact,
no enclosing box.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The range picker's segments are composed DateField segments (they carry
data-date-field-segment + the shared data-field-segment marker and read their
tokens from each endpoint's own [data-date-field]). The per-picker copy only
duplicated the Field base and re-added a focus font-weight the shared
field-segment-state layer deliberately drops (bold widens the ch-based segment,
jittering it on every increment). Removing it defers to date-field.css +
field-segment-state.css. Verified: segment box / padding / min-width / highlight
unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The date-* family now IS-a-Field then evolves, per the S6 mandate + the
no-token-alias doctrine — no more marker-overlay borrowing of date-field.css.
- date-field: chrome / label / invalid / disabled / focus come from field.css via
the fieldRole identity (data-field / data-field-control); date-field.css keeps
only segment specifics. No tokens aliasing the Field base.
- Field recipe: add --field-segment-height (size-axis, family level).
- DateField.Provider: expose syncPlaceholderToValue for controlled composition.
- date-range-field: COMPOSES two real <DateField>s (DateField.Provider >
DateField.Input per endpoint) bound to the range slice; size/variant/color thread
through a visual context; CSS is layout-only. Fixes the field.css-not-loaded
chrome regression.
- DatePicker (single field): root carries data-field (A27); imports field.css;
drops the dead date-field token re-declarations.
- DateRangePicker: inherits the composed endpoints; imports field.css; removes the
endpoint box that double-boxed the composed DateField.
Baseline unchanged (59). Verified in browser: paint, edit coordination, size
reactivity, no double-box.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to the FieldLangs component: export it from the soma components
barrel and add its demo route to the docs sidebar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FieldLangs holds one value per language and switches the active editing language
through an adaptive in-control switcher, composing the shipped Field / Popover /
TextArea instead of reinventing them (picker composition pattern, A27).
- Coordinator (soma): values map + activeLang + required-per-language validity;
exposes the value/selection proxies the composed controls bind to; fires
shift-navigate (context change) as its only semantic event.
- Switcher (eidos, FieldLangs' own -- deliberately NOT a composed ToggleGroup,
whose commit-toggle is the wrong semantic for a language navigation and would
leak into the field): a segmented radiogroup for 3 or fewer languages, an own
Popover dropdown for more; per-language set/required dot; roving keyboard, RTL.
- Single-line Input and multiline Textarea (corner switcher, the field's default
floating label untouched); floatingLabel passthrough; switcher font one
typographic step below the control (label doctrine).
- morfo (provider/switcher/lang-option/menu/textarea parts + shift-navigate
event), i18n catalog, and an interactive demo.
Guards: svelte-check clean, morfo test, eidos-lint 0 invalid, component:audit PASS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the static `lazy` boolean with `showMask`, defaulting to `'focus'`.
A reference sweep (imask `lazy`, jQuery-inputmask `showMaskOnFocus`,
react-input-mask `alwaysShowMask`, ngx-mask `showMaskTyped`, PrimeReact) found
no library that shows the full skeleton on a blurred, empty field by default:
the ones that reveal it do so on focus. So the resting field stays clean, and
focusing reveals `(___) ___-____` with the caret on the first slot; blurring an
unfinished field drops the placeholder tail. `'always'` (skeleton even when
blurred) and `'never'` (progressive) are the opt-outs.
- provider: `effectiveLazy` derived from `showMask` + focus; `revealOnFocus`
(wires the already-exported `firstEditableCaret`) and `settleOnBlur`; `isEmpty`
now reads the unmasked value so a visible skeleton doesn't read as filled.
- `'focus'` composes with `Field.floatingLabel`: focus floats the label and
reveals the skeleton without the two competing for the empty control.
- demo: `showMask` chips replace the `lazy` switch; README documents the
decision + the reference sweep.
Verified all three modes in the browser; provider tests 31 pass (adds a
focus/blur reveal test); check clean for mask-field.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>