From 75fa94005b82a1b7f9d76417d7be913bd4fb4bec Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 12 Jul 2026 03:38:15 +0200 Subject: [PATCH] =?UTF-8?q?refactor(stepper):=20THM-4=20=E2=80=94=20consol?= =?UTF-8?q?idate=20step=20state=20on=20data-state,=20drop=20boolean=20alia?= =?UTF-8?q?ses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every step part (item/trigger/indicator/separator) declared BOTH `data-state` (enum) AND `data-current`/`data-complete`/`data-incomplete` (booleans) — the same state twice, used inconsistently (enum on trigger/indicator, boolean on separator). Consolidate on the canonical `data-state`: - morfo: prune the 12 boolean declarations. - provider: drop the current/complete/incomplete sources (item/indicator/separator) + the trigger's explicit boolean writes + the now-unused isComplete/isIncomplete getters (isCurrent stays — it drives aria-current/tabindex). - separator.css: [data-complete] -> [data-state='complete']. - README + provider test updated to the single form. Verified with the real component (Next advances → complete/current/ incomplete via data-state, boolean attrs gone, complete separator still paints). stepper unused 30->18; provider test + contracts green. Co-Authored-By: Claude Opus 4.8 --- .../continue-cleanroom-fixes-2026-07.md | 28 +++++++++++++------ src/uix/eidos/components/stepper/stepper.css | 2 +- src/uix/morfo/components/stepper.ts | 20 +++---------- src/uix/soma/components/stepper/README.md | 10 ------- .../stepper/stepper-provider.svelte.test.ts | 3 -- .../stepper/stepper-provider.svelte.ts | 20 ++----------- 6 files changed, 27 insertions(+), 56 deletions(-) diff --git a/docs/process/continue-cleanroom-fixes-2026-07.md b/docs/process/continue-cleanroom-fixes-2026-07.md index 09ca1ae0b..1d1a626a7 100644 --- a/docs/process/continue-cleanroom-fixes-2026-07.md +++ b/docs/process/continue-cleanroom-fixes-2026-07.md @@ -394,18 +394,28 @@ incomplete) **Y** los booleanos `data-current`/`data-complete`/`data-incomplete` (`propRef`, severity optional). El propio CSS las usa **inconsistentemente**: enum en trigger/indicator, booleano `data-complete` en el separator. Así que NO es deuda-sin-consumidor (la forma booleana SÍ se consume en el separator) — -es **redundancia**: cada parte declara ambas formas y usa una. Consolidar en -`data-state` (canónico) + migrar el separator a `data-state='complete'` + podar -los booleanos limpiaría la inconsistencia, PERO es **cambio de API pública** -(un consumidor podría estilizar `[data-complete]`) → **decisión de diseño**, no -poda unilateral. +es **redundancia**: cada parte declara ambas formas y usa una. + +**CONSOLIDADO ✅ (decisión de usuario 2026-07-12)** en `data-state` (canónico), +API pública unificada. 5 sitios: morfo (podados 12 declaraciones booleanas de +item/trigger/indicator/separator) · provider (podados los sources +current/complete/incomplete de item/indicator/separator + los writes explícitos +del trigger + los getters `isComplete`/`isIncomplete` — `isCurrent` se queda, +lo usan aria-current/tabindex) · provider-test (assertions booleanas fuera) · +README (10 filas booleanas fuera) · separator.css (`[data-complete]` → +`[data-state='complete']`). **Verificado real** (demo, botón Next): estados por +`data-state` (complete/current/incomplete), booleanos ausentes, separator +completo pinta `--_stepper-palette-solid` vía `data-state='complete']` +(behavior-preserving). Gates: provider-test + contracts verdes · baseline 9 · +**stepper unused 30→18** (total catálogo 1152→1140). ### THM-4 — lectura emergente (2 lotes) -media-player (0 deuda) + stepper (0 deuda-real, 1 redundancia de API): los -hotspots son **mayormente legítimos** (composites grandes + superficie -behavioral); la deuda-sin-consumidor genuina es **rara**. Lo accionable son -**decisiones de diseño** (dual-API de stepper), no podas mecánicas. Esto +media-player (0 deuda) + stepper (0 deuda-real, 1 redundancia de API — +**consolidada**): los hotspots son **mayormente legítimos** (composites grandes ++ superficie behavioral); la deuda-sin-consumidor genuina es **rara**. Lo +accionable son **decisiones de diseño** (como el dual-API de stepper que el +usuario decidió consolidar), no podas mecánicas. Esto CONFIRMA la predicción de la doctrina ("la mayoría son legítimos") y sugiere que **la tasa de deuda NO justifica una regla de máquina** — THM-4 es sobre todo adjudicación documentada. Restan color-picker/avatar/trp por adjudicar diff --git a/src/uix/eidos/components/stepper/stepper.css b/src/uix/eidos/components/stepper/stepper.css index fbae8767a..889e52228 100644 --- a/src/uix/eidos/components/stepper/stepper.css +++ b/src/uix/eidos/components/stepper/stepper.css @@ -146,7 +146,7 @@ min-block-size: var(--stepper-separator-min-size); } -[data-stepper-separator][data-complete] { +[data-stepper-separator][data-state='complete'] { background: var(--_stepper-palette-solid); } diff --git a/src/uix/morfo/components/stepper.ts b/src/uix/morfo/components/stepper.ts index 33bb9d44c..feaeaae44 100644 --- a/src/uix/morfo/components/stepper.ts +++ b/src/uix/morfo/components/stepper.ts @@ -86,10 +86,7 @@ export const stepperMorfo = { attr: 'data-orientation', values: ['horizontal', 'vertical'], value: v.propRef('orientation') - }, - { attr: 'data-current', value: v.propRef('current'), severity: 'optional' }, - { attr: 'data-complete', value: v.propRef('complete'), severity: 'optional' }, - { attr: 'data-incomplete', value: v.propRef('incomplete'), severity: 'optional' } + } ], aria: [] }, @@ -103,10 +100,7 @@ export const stepperMorfo = { optional: true, states: ['complete', 'current', 'incomplete'], data: [ - { attr: 'data-state', values: ['complete', 'current', 'incomplete'] }, - { attr: 'data-current', severity: 'optional' }, - { attr: 'data-complete', severity: 'optional' }, - { attr: 'data-incomplete', severity: 'optional' } + { attr: 'data-state', values: ['complete', 'current', 'incomplete'] } ], aria: [ { attr: 'type', value: v.literal('button') }, @@ -151,10 +145,7 @@ export const stepperMorfo = { attr: 'data-state', values: ['complete', 'current', 'incomplete'], value: v.propRef('state') - }, - { attr: 'data-current', value: v.propRef('current'), severity: 'optional' }, - { attr: 'data-complete', value: v.propRef('complete'), severity: 'optional' }, - { attr: 'data-incomplete', value: v.propRef('incomplete'), severity: 'optional' } + } ], aria: [{ attr: 'aria-hidden', value: v.literal('true') }] }, @@ -177,10 +168,7 @@ export const stepperMorfo = { attr: 'data-orientation', values: ['horizontal', 'vertical'], value: v.propRef('orientation') - }, - { attr: 'data-current', value: v.propRef('current'), severity: 'optional' }, - { attr: 'data-complete', value: v.propRef('complete'), severity: 'optional' }, - { attr: 'data-incomplete', value: v.propRef('incomplete'), severity: 'optional' } + } ], aria: [] }, diff --git a/src/uix/soma/components/stepper/README.md b/src/uix/soma/components/stepper/README.md index f973e79ce..3fa6ee088 100644 --- a/src/uix/soma/components/stepper/README.md +++ b/src/uix/soma/components/stepper/README.md @@ -72,24 +72,14 @@ A multi-step wizard with step indicators, navigation controls, and content panel | List | `data-orientation` | `horizontal` \| `vertical` | | Item | `data-stepper-item` | Always present | | Item | `data-state` | `complete` \| `current` \| `incomplete` | -| Item | `data-current` | Present when this is the current step | -| Item | `data-complete` | Present when this step is completed | -| Item | `data-incomplete` | Present when this step is not yet reached | | Item | `data-orientation` | `horizontal` \| `vertical` | | Trigger | `data-stepper-trigger` | Always present | | Trigger | `data-state` | `complete` \| `current` \| `incomplete` | -| Trigger | `data-current` | Present when current | -| Trigger | `data-complete` | Present when completed | -| Trigger | `data-incomplete` | Present when incomplete | | Trigger | `data-orientation` | `horizontal` \| `vertical` | | Indicator | `data-stepper-indicator` | Always present | | Indicator | `data-state` | `complete` \| `current` \| `incomplete` | -| Indicator | `data-current` | Present when current | -| Indicator | `data-complete` | Present when completed | -| Indicator | `data-incomplete` | Present when incomplete | | Separator | `data-stepper-separator` | Always present | | Separator | `data-state` | `complete` \| `current` \| `incomplete` | -| Separator | `data-complete` | Present when the step before it is complete | | Separator | `data-orientation` | `horizontal` \| `vertical` | | Content | `data-stepper-content` | Always present | | Content | `data-state` | `complete` \| `current` \| `incomplete` | diff --git a/src/uix/soma/components/stepper/stepper-provider.svelte.test.ts b/src/uix/soma/components/stepper/stepper-provider.svelte.test.ts index 77449bf47..dd100b5d7 100644 --- a/src/uix/soma/components/stepper/stepper-provider.svelte.test.ts +++ b/src/uix/soma/components/stepper/stepper-provider.svelte.test.ts @@ -151,11 +151,8 @@ describe('StepperProvider', () => { expect(root.getAttribute('data-orientation')).toBe('horizontal'); expect(root.getAttribute('data-linear')).toBe(''); expect(firstEl.getAttribute('data-state')).toBe('complete'); - expect(firstEl.getAttribute('data-complete')).toBe(''); expect(currentEl.getAttribute('data-state')).toBe('current'); - expect(currentEl.getAttribute('data-current')).toBe(''); expect(lastEl.getAttribute('data-state')).toBe('incomplete'); - expect(lastEl.getAttribute('data-incomplete')).toBe(''); expect(indicatorEl.getAttribute('aria-hidden')).toBe('true'); expect(indicatorEl.getAttribute('data-state')).toBe('current'); expect(separatorEl.getAttribute('role')).toBe('separator'); diff --git a/src/uix/soma/components/stepper/stepper-provider.svelte.ts b/src/uix/soma/components/stepper/stepper-provider.svelte.ts index 425263759..f1989ac8b 100644 --- a/src/uix/soma/components/stepper/stepper-provider.svelte.ts +++ b/src/uix/soma/components/stepper/stepper-provider.svelte.ts @@ -214,10 +214,7 @@ export class StepperItemProvider { context: StepperItemProvider.ctx, props: { state: () => this.stepState, - orientation: () => this.provider.opts.orientation.current, - current: () => this.isCurrent, - complete: () => this.isComplete, - incomplete: () => this.isIncomplete + orientation: () => this.provider.opts.orientation.current }, syncAttrs: true }); @@ -232,8 +229,6 @@ export class StepperItemProvider { readonly stepState = $derived.by(() => this.provider.getStepState(this.opts.index.current)); readonly isCurrent = $derived.by(() => this.stepState === 'current'); - readonly isComplete = $derived.by(() => this.stepState === 'complete'); - readonly isIncomplete = $derived.by(() => this.stepState === 'incomplete'); readonly props = $derived.by(() => this.runtimePart.assert({ @@ -343,9 +338,6 @@ export class StepperTriggerProvider { // Soma-owned data-* — the morfo declares these without a value source // (documentation only) or not at all (data-orientation): 'data-state': this.item.stepState, - 'data-current': boolToEmptyStrOrUndef(this.item.isCurrent), - 'data-complete': boolToEmptyStrOrUndef(this.item.isComplete), - 'data-incomplete': boolToEmptyStrOrUndef(this.item.isIncomplete), 'data-disabled': boolToEmptyStrOrUndef(this.isDisabled), 'data-orientation': this.provider.opts.orientation.current, onclick: this.onclick, @@ -375,10 +367,7 @@ export class StepperIndicatorProvider { ref: opts.ref, owner: this, props: { - state: () => this.item.stepState, - current: () => this.item.isCurrent, - complete: () => this.item.isComplete, - incomplete: () => this.item.isIncomplete + state: () => this.item.stepState }, syncAttrs: true }); @@ -418,10 +407,7 @@ export class StepperSeparatorProvider { owner: this, props: { state: () => this.item.stepState, - orientation: () => this.provider.opts.orientation.current, - current: () => this.item.isCurrent, - complete: () => this.item.isComplete, - incomplete: () => this.item.isIncomplete + orientation: () => this.provider.opts.orientation.current }, syncAttrs: true });