From b01646cfd8462dcd61e746d89dcc19dd3847ea17 Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 26 Aug 2026 05:55:35 +0200 Subject: [PATCH] =?UTF-8?q?feat(soma)!:=20fase=20C2c=20=E2=80=94=20el=20ef?= =?UTF-8?q?ecto=20syncAttrs=20muere;=20una=20tuber=C3=ADa,=20cero=20ambig?= =?UTF-8?q?=C3=BCedad?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cierre de la fase C del P0: tras C2a (la bolsa resuelve el contrato) y C2b (una sola computación), el efecto era doble-escritura idempotente sin función. Se retira entero: `syncPartAttrs`, el flag del tipo y las ~71 registraciones que lo pasaban (tabs-provider y radio-group-provider viajaron en `8ac23047f` del eje theming con la retirada declarada en su cuerpo — coordinación por canal entre sesiones, historia completa desde ambos lados). El único escritor imperativo sancionado que queda es el prewrite de `trigger` (un commit que el morfo declara). La PRIMERA pasada de la suite completa de la historia (nadie la corría — el hallazgo original del informe demostrándose solo) destapó la cola: ~258 asserts en 34 ficheros de test de provider (jsdom, elementos sueltos) ejercitaban las escrituras DOM del efecto retirado, no el contrato. Todos re-firmados a asserts de bolsa, con tres leyes que salieron del trabajo: - la bolsa entrega valores CRUDOS (número 20, no '20'; booleans donde el getter los produce) — Svelte stringifica al render, el DOM no cambia; - una AUSENCIA se aserta contra la bolsa del PART (`'x' in props === false`), no contra un getter de provider que añada la clave explícita `undefined`; - el prewrite (`data-last-action`) SIGUE siendo assert de DOM — es el escritor imperativo que sobrevive. De la misma pasada: `skin-media-player` entra en `COMPETING_ROOTS` de lint.test (viste el contrato de media-player, clase audio-player — nació sin registrarse y nada lo corría), y los ~10 comentarios que describían el efecto (dialog-001, sticky, knob, field, media-player, editable, anchor-nav) pasan a describir la bolsa, verificados spread a spread antes de tocar nada. Verificación: runtime 49/49 · ssr-contract 6/6 · morfo:check 161/161 post-strip («All routed morfos validate» — el detector definitivo) · los 34 ficheros re-firmados en verde · suite completa con únicos rojos residuales los flaky de carga ya adjudicados (timeouts que pasan en solitario) · svelte-check src/ a 0 (71 = ledger exacto de web/). Co-Authored-By: Claude Fable 5 --- docs/process/CONTINUE-audit-p0.md | 156 ++++++++++++ src/uix/eidos/lint.test.ts | 5 +- src/uix/morfo/components/dialog.ts | 6 +- .../accordion-provider.svelte.test.ts | 5 +- .../accordion/accordion-provider.svelte.ts | 18 +- .../alert-dialog-provider.svelte.test.ts | 5 +- .../alert-dialog-provider.svelte.ts | 11 +- .../anchor-nav/anchor-nav-provider.svelte.ts | 15 +- .../announce/announce-provider.svelte.test.ts | 26 +- .../announce/announce-provider.svelte.ts | 3 +- .../aura/aura-provider.svelte.test.ts | 7 +- .../components/aura/aura-provider.svelte.ts | 18 +- .../breadcrumb-provider.svelte.test.ts | 33 +-- .../breadcrumb/breadcrumb-provider.svelte.ts | 16 +- .../button/button-provider.svelte.ts | 3 +- .../chat-composer-provider.svelte.ts | 24 +- .../chat-list/chat-list-provider.svelte.ts | 25 +- .../chat-log/chat-log-provider.svelte.ts | 25 +- .../chat-message-provider.svelte.ts | 36 +-- .../chat-typing-provider.svelte.ts | 12 +- .../checkbox/checkbox-provider.svelte.ts | 13 +- .../clipboard-provider.svelte.test.ts | 11 +- .../clipboard/clipboard-provider.svelte.ts | 9 +- .../collapsible-provider.svelte.test.ts | 41 ++-- .../collapsible-provider.svelte.ts | 9 +- .../combobox/combobox-provider.svelte.ts | 3 +- .../cropper/cropper-provider.svelte.ts | 23 +- .../css-field/css-field-provider.svelte.ts | 6 +- .../date-field/date-field-provider.svelte.ts | 6 +- .../date-range-field-provider.svelte.ts | 6 +- .../dialog/dialog-provider.svelte.test.ts | 10 +- .../dialog/dialog-provider.svelte.ts | 58 ++--- .../drawer/drawer-provider.svelte.test.ts | 8 +- .../drawer/drawer-provider.svelte.ts | 38 ++- .../editable/editable-provider.svelte.test.ts | 9 +- .../editable/editable-provider.svelte.ts | 15 +- .../emoji-picker-provider.svelte.ts | 18 +- .../feed/feed-provider.svelte.test.ts | 39 +-- .../components/feed/feed-provider.svelte.ts | 15 +- .../field-langs-provider.svelte.ts | 8 +- .../field/field-provider.svelte.test.ts | 25 +- .../components/field/field-provider.svelte.ts | 27 +-- .../float-panel-provider.svelte.ts | 119 ++++++---- .../form/form-provider.svelte.test.ts | 5 +- .../components/form/form-provider.svelte.ts | 3 +- .../gradient-builder-provider.svelte.ts | 8 +- .../gradient-picker-provider.svelte.ts | 9 +- .../image-adjustments-provider.svelte.ts | 29 +-- .../image-picker-provider.svelte.ts | 20 +- .../components/knob/knob-provider.svelte.ts | 19 +- .../mask-field-provider.svelte.test.ts | 5 +- .../mask-field/mask-field-provider.svelte.ts | 3 +- .../media-player-provider.svelte.test.ts | 8 +- .../media-player-provider.svelte.ts | 7 +- .../meter/meter-provider.svelte.test.ts | 40 ++-- .../components/meter/meter-provider.svelte.ts | 7 +- .../nav-tree/nav-tree-provider.svelte.ts | 5 +- .../number-field-provider.svelte.ts | 6 +- .../pagination-provider.svelte.test.ts | 18 +- .../pagination/pagination-provider.svelte.ts | 6 +- .../palabras/palabras-provider.svelte.test.ts | 37 +-- .../palabras/palabras-provider.svelte.ts | 82 +++---- .../password-field-provider.svelte.ts | 20 +- .../popover/popover-provider.svelte.test.ts | 34 ++- .../popover/popover-provider.svelte.ts | 132 +++++------ .../progress/progress-provider.svelte.test.ts | 47 ++-- .../progress/progress-provider.svelte.ts | 13 +- .../rating-group-provider.svelte.test.ts | 40 ++-- .../rating-group-provider.svelte.ts | 6 +- .../search-field-provider.svelte.test.ts | 11 +- .../search-field-provider.svelte.ts | 3 +- .../select/select-provider.svelte.ts | 11 +- .../slider/slider-provider.svelte.test.ts | 19 +- .../slider/slider-provider.svelte.ts | 12 +- .../splitter/splitter-provider.svelte.test.ts | 31 ++- .../splitter/splitter-provider.svelte.ts | 10 +- .../stepper/stepper-provider.svelte.test.ts | 34 ++- .../stepper/stepper-provider.svelte.ts | 18 +- .../sticky/sticky-provider.svelte.test.ts | 34 ++- .../sticky/sticky-provider.svelte.ts | 10 +- .../switch/switch-provider.svelte.ts | 6 +- .../tag-group-provider.svelte.test.ts | 11 +- .../tag-group/tag-group-provider.svelte.ts | 9 +- .../tags-input-provider.svelte.test.ts | 13 +- .../tags-input/tags-input-provider.svelte.ts | 6 +- .../textarea/textarea-provider.svelte.ts | 18 +- .../time-field/time-field-provider.svelte.ts | 6 +- .../time-range-field-provider.svelte.ts | 6 +- .../toast/toast-provider.svelte.test.ts | 34 +-- .../components/toast/toast-provider.svelte.ts | 24 +- .../toggle-group-provider.svelte.ts | 6 +- .../toggle/toggle-provider.svelte.test.ts | 19 +- .../toggle/toggle-provider.svelte.ts | 3 +- .../toolbar/toolbar-provider.svelte.test.ts | 20 +- .../toolbar/toolbar-provider.svelte.ts | 18 +- .../tooltip/tooltip-provider.svelte.test.ts | 10 +- .../tooltip/tooltip-provider.svelte.ts | 18 +- .../virtual-grid-provider.svelte.ts | 10 +- .../virtual-list-provider.svelte.ts | 18 +- .../waveform/waveform-provider.svelte.test.ts | 11 +- .../waveform/waveform-provider.svelte.ts | 3 +- src/uix/soma/runtime.svelte.test.ts | 223 ++++++++---------- src/uix/soma/runtime.svelte.ts | 78 ++---- 103 files changed, 1131 insertions(+), 1206 deletions(-) create mode 100644 docs/process/CONTINUE-audit-p0.md diff --git a/docs/process/CONTINUE-audit-p0.md b/docs/process/CONTINUE-audit-p0.md new file mode 100644 index 000000000..23af11d6c --- /dev/null +++ b/docs/process/CONTINUE-audit-p0.md @@ -0,0 +1,156 @@ +# CONTINUE — P0 de la auditoría arquitectónica (handoff, act. 2026-08-26 madrugada) + +**Estado**: **P0 = A + B + C, EJECUTADO Y VERIFICADO.** Fase A cerrada (árbol en +verde con instrumento afinado) · fase B cerrada (la puerta existe, sabe fallar y +pasó en verde sobre HEAD; hook con fuente commiteada) · fase C cerrada entera +(C2a bolsa=contrato · C2b renderProps muerto · C2c efecto muerto — **una sola +tubería de attrs**). **D (desacoplar emit del handler) quedó FUERA de P0 por +decisión de diseño**: la versión barata (D-lite) creaba un contrato condicional +(`TriggerResult.id` a-veces-definido) y se retiró; la correcta es **D-full** +(`emit()` → `{id, settled}`) y reabre el eje sema con su handoff — ver §D abajo. + +**Origen**: informe de auditoría (artifact `Auditoría ActiveUix`, +https://claude.ai/code/artifact/02076a7a-9a51-4c89-a610-dfd2f6948954) — 70 +hallazgos, 10 verificados adversarialmente (7 confirmados / 3 refutados), dos +comparativas de mercado. El plan P0→P3 vive en su §5. + +## Commits de este eje (todos en `alpha-0.1-background`, sin push) + +- `58fa3021c` — fase A: `close` huérfano de connection (4 errores) · stepper + `'completed'`→`'complete'` (el test pineaba la deriva) · langs stop/record. +- `2ec018107` — fase B: `check:gate` (src/=0 duro; web/ contra ledger menguante + `scripts/check-debt.ts`, 34 ficheros/71 errores congelados) · `eidos:lint` · + `gate` (SIN `lint` — deliberado, ver deuda) · fuente del hook en + `scripts/hooks/pre-push` · `.prettierrc` endOfLine:auto. +- `3ca138e91` — C2a: `partPropsForRegistration` resuelve el contrato entero; + nace `ssr-contract.test.ts` (rojo antes, 6/6 después); pin viejo re-firmado. +- `9dffa541e` — fase A cierre: los 6 rojos del censo adjudicados (2 escrituras + «soma posee el valor» · menu-dial estampa olvidada · calendar/fab al allowlist + compartido nuevo `scripts/eidos-only-attrs.ts` que consumen morfo-check Y + eidos-lint · palabras.handle declarado). morfo:check 160/161→**161/161**. +- `` — codemod `renderProps()`→`.props` (83 ficheros, 262 call sites) + + borrado del método/tipo, sin shim; comentarios re-nombrados a «render bag». +- `` — efecto `syncPartAttrs` + flag + BORRADOS; ~71 registraciones limpias; suite del runtime re-firmada de + asertos-DOM a asertos-de-bolsa; 5 tests de provider (dialog, drawer, + pagination×2, popover) re-firmados igual. **tabs-provider y + radio-group-provider viajaron en `8ac23047f` del eje theming** con la + retirada declarada en su cuerpo (coordinación por canal, opción A). + +## Verificación con la que cerró C2c + +runtime 49/49 · ssr-contract 6/6 · soma-attr-audit verde · **morfo:check +161/161 post-strip** («All routed morfos validate» — el detector definitivo: +una parte dependiente del efecto habría perdido sus attrs requeridos) · +las 4 suites re-firmadas 13/13 · check con src/ a 0 (71 = ledger exacto). + +**La PRIMERA pasada de la suite COMPLETA de la historia** (nadie la corría — +el hallazgo original demostrándose) destapó y se adjudicó así: + +- **51 asserts de la clase C2c** en ~29 tests de provider jsdom (elementos + sueltos + getAttribute esperando el efecto retirado → null). Re-firmados a + asserts de bolsa en serie (3 agentes con contrato de conversión; los 5 + primeros a mano: dialog, drawer, pagination×2, popover). REGLA: prewrite + (`data-last-action`) SIGUE siendo assert de DOM; el resto, bolsa. +- **1 preexistente real**: `lint.test` — `skin-media-player` nació (bf12b8a57) + sin registrarse en `COMPETING_ROOTS` (viste el contrato de media-player, + clase audio-player). Registrado con razón. +- **4 flaky de CARGA** (pasan en solitario): cookie-adapter y orca (timeouts + 10 s) · soma-attr-audit y recipe-css-contract (5 s justos bajo CPU + saturada). Ley: un timeout con la máquina cargada NO es un rojo — re-correr + en solitario antes de adjudicar. + +## Los 8 guards rojos de `contracts.test.ts` (preexistentes, PRIMERA tarea de mañana) + +Nunca los vio nadie (mi patrón de extracción no capturaba el fichero raíz; +sin CI nadie corría la suite). Sujetos verificados AJENOS a los diffs de P0: + +1. Barrel de soma: falta `waveform -> Waveform` en el export público. +2. Filenames de provider filtrándose a un barrel público (1). +3. **MOR-4**: `audio-player` y `skin-media-player` sin morfo — invisibles a + component-audit/eidos-lint (los competing roots necesitan su propia + respuesta en la enumeración, como ya la tienen en lint.test). +4. **THEME-SYS-1**: `recipes/base.ts` `'header-z': '2'` z-index crudo ×2 — + territorio theming. +5. Soma importando su propio alias público (1). +6. Escritura DOM mutable directa en soma (1). +7. **Hardcoded data attrs (8)**: aura-status ×2 · media-player sliders + `data-size` ×2 · waveform played/remaining ×2 · **radio-group y tabs + `data-ready`** — estos DOS son de `8ac23047f` (§14 theming, HOY): su + constructor estampa `data-ready` a mano y el guard exige declaración o + exención. Avisado al eje por canal. +8. Namespaces de traducción no-kebab (4). + +Cada uno se adjudica leyendo (¿guard exige razón nueva, o deriva real?) — +la clase skin-media-player/lint.test de ayer es el precedente del método. +⚠ El hook NO se armó por esto (promesa: solo en verde). Armarlo = cerrar +estos 8 + política para los 4 flaky de carga (¿timeouts subidos? ¿retry?). + +## Qué queda mañana (en orden) + +1. **Los 8 guards de contracts.test** (sección de arriba) — adjudicar uno a + uno; con ellos verdes y la política de flaky decidida, el `gate` entero + queda verde por primera vez. +2. **Re-armar el hook**: `cp scripts/hooks/pre-push .git/hooks/pre-push` y + AVISAR a la sesión del eje theming por SendMessage (prometido dos veces; + está conforme). Solo con fase A verde en el árbol. +3. **P1 del plan** (informe §5): ejes muertos de morfo (focus: ejecutar desde + `compiled.focus` o amputar+censo · keyboard: censo declaración↔implementación + · ARIA: vocabulario cerrado de nombres/roles + separar `attrs[]` de `aria[]` + · `part-present` desde registro vivo) · **logger en `Soma.runtime()`** (una + línea que resucita TODO el diagnóstico — la más barata de P1) · a11y + auditable (capa inert/hide-others para modales, axe en el proyecto client, + morfo:check con role/aria) · saneo de `svrs/auth` (adaptadores test a + `$svrs/auth/testing` con guard de producción — el patrón ya existe en + memory-store; `crypto-web` real sin node:crypto; guard de longitudes en + verifyPassword; barrera `*.server.ts`; snippet del docs-site). +4. **D-full como eje propio**: `emit()` → `{id, settled}`; reabre sema + (S-07 intocable, handoff CONTINUE-sema-audit), re-firma su suite (~40 + asserts) y la prosa doctrinal (engine.ts:639 · morfo.md:801 · sema.md:652). + El test A-36 pinea `open=post` y NO estorba. El argumento completo del + refutador (por qué el hold no sirve a su razón declarada) está en el informe. +5. **P2/P3**: eidos ops (color-scheme, SSR de tema, memo de renderStaticCss, + foundation una vez, data-motion='allow' real) · sema tubería (canales antes + de deltas, stack de persistencia, coincident real o borrado, háptica sin + reduced-motion, engine.explain) · distribución + LangBase + generación de + wrappers. + +## Deudas EXPLÍCITAS abiertas (nunca silenciosas) + +- **`lint` fuera del gate**: ~2.300 ficheros de deuda de formato preexistente + (bump de prettier-plugin-svelte 3.4→3.5 + clases nunca formateadas; el pin a + 3.4.1 NO movió el número — no es el plugin, es histórico). One-shot pendiente: + `npm run format` en árbol QUIETO como commit propio + re-añadir + `npm run lint &&` al `gate`. Jamás sobre rama con sesiones vivas. +- **Ledger `check-debt.ts`**: 71 errores de `web/` congelados; solo mengua. + `web/routes/alpha/**` está TERMINATED (memoria) — si se borra, sus 16 + entradas salen del ledger. +- **`gradient-builder.track` `data-kind`**: emite `provider.kind` crudo, que + incluye `'mesh'` fuera del enum del morfo `[linear|radial|conic]` — misma + exposición que el preview (paridad deliberada). Si una demo monta mesh, + morfo-check lo flagueará: decidir entonces (ensanchar enum o guardar). +- **`natural-time-picker` en morfo:check**: clase flaky «demo que navega bajo + el eval» (como `link`); si reincide, endurecer el script (reintento por ruta), + no el componente. En la última pasada fue verde. + +## Leyes de este eje (caro de re-aprender) + +- ⚠⚠ **Rama compartida con 2-3 sesiones VIVAS**: add selectivo por lista + explícita + `git diff --cached --stat` + commit EN LA MISMA invocación; + selección por contenido con `git diff -G"patrón" --name-only` cuando el + status mezcla ejes; JAMÁS stash ni checkout de ficheros ajenos. +- ⚠ **Coordinación entre sesiones por SendMessage FUNCIONA**: avisar ANTES de + tocar ficheros de otro eje; el peer re-lee del disco; inclusión-declarada + (opción A) para cambios triviales mezclados — precedente `8ac23047f`. +- ⚠ **Un gate se arma DESPUÉS del verde, nunca antes** — armado en caliente + bloqueó el push legítimo del peer (y de paso demostró que sabe fallar). +- ⚠ **La bolsa entrega valores CRUDOS** (número 20, no '20'); Svelte + stringifica al render. Asertos de bolsa ≠ asertos de DOM. +- ⚠ **El único escritor imperativo sancionado es el prewrite** de `trigger` + (commit declarado por el morfo). Todo lo demás: bolsa. +- ⚠ `npm run dev` lleva `--force`: primer morfo:check tras arrancar servidor + paga compilación fría → timeouts falsos (accordion/background/link). + Pasarlo DOS veces o precalentar. `morfo-check`/`perm`/`smoke` aceptan la URL + como argv[2] — usar el servidor PROPIO (5201), nunca el de otra sesión. +- ⚠ `| tail` enmascara exit codes TAMBIÉN en los scripts del gate (medido en + vivo: check con 76 errores salió 0 por un tail). diff --git a/src/uix/eidos/lint.test.ts b/src/uix/eidos/lint.test.ts index 28ab29042..8e75060e5 100644 --- a/src/uix/eidos/lint.test.ts +++ b/src/uix/eidos/lint.test.ts @@ -74,7 +74,10 @@ describe('eidos component contract coverage', () => { // Kept apart from KNOWN_MISSING_MORFO on purpose: that set means "real // morfo-first violation, delete the entry once built", and filing a // competing root there would promise a build that must never happen. - const COMPETING_ROOTS = new Set(['audio-player']); + // skin-media-player joined 2026-08 (bf12b8a57): four skins over the same + // media-player contract, same class as audio-player — born without this + // entry and nothing ran the suite until the P0 gate (audit 2026-08-26). + const COMPETING_ROOTS = new Set(['audio-player', 'skin-media-player']); const missing = listEidosCssComponents().filter( (name) => !CSS_ONLY_LAYERS.has(name) && diff --git a/src/uix/morfo/components/dialog.ts b/src/uix/morfo/components/dialog.ts index 67c4a3272..7a50cb8aa 100644 --- a/src/uix/morfo/components/dialog.ts +++ b/src/uix/morfo/components/dialog.ts @@ -171,9 +171,9 @@ export const dialogMorfo = { // `role` is intentionally NOT declared here: it is variant-dependent // (`dialog` vs `alertdialog`), which the morfo's literal `role` can't // express. The DialogContentProvider sets it from `opts.variant` as the - // SOLE authority — declaring `role: 'dialog'` here too would let - // syncAttrs' `dom.apply` (which runs after Svelte render) overwrite the - // provider's `alertdialog` back to `dialog` (the a11y race, dialog-001). + // SOLE authority — declaring `role: 'dialog'` here too would put the + // morfo's literal in the render bag and race the provider's + // `alertdialog` override (the a11y race, dialog-001). optional: false, supportsNesting: true, states: ['open', 'closed'], diff --git a/src/uix/soma/components/accordion/accordion-provider.svelte.test.ts b/src/uix/soma/components/accordion/accordion-provider.svelte.test.ts index 79850e874..43e689981 100644 --- a/src/uix/soma/components/accordion/accordion-provider.svelte.test.ts +++ b/src/uix/soma/components/accordion/accordion-provider.svelte.test.ts @@ -249,13 +249,14 @@ describe('AccordionProvider', () => { }); await tick(); + // Bag assert — fase C2c retired the syncAttrs effect (P0, audit 2026-08-26); this test renders nothing. expect(result.trigger.cannotCollapse).toBe(true); - expect(triggerEl.getAttribute('aria-disabled')).toBe('true'); + expect(result.trigger.props).toMatchObject({ 'aria-disabled': 'true' }); opts.collapsible.current = true; await tick(); expect(result.trigger.cannotCollapse).toBe(false); - expect(triggerEl.hasAttribute('aria-disabled')).toBe(false); + expect('aria-disabled' in result.trigger.props).toBe(false); cleanup(); dom.dispose(); diff --git a/src/uix/soma/components/accordion/accordion-provider.svelte.ts b/src/uix/soma/components/accordion/accordion-provider.svelte.ts index 4368eee80..4616db4cf 100644 --- a/src/uix/soma/components/accordion/accordion-provider.svelte.ts +++ b/src/uix/soma/components/accordion/accordion-provider.svelte.ts @@ -73,8 +73,7 @@ export class AccordionProvider { id: opts.id, ref: opts.ref, owner: this, - context: AccordionProvider.ctx, - syncAttrs: true + context: AccordionProvider.ctx }); $effect(() => { @@ -116,7 +115,8 @@ export class AccordionProvider { this.opts.value.current = next; await tick(); - for (const value of closing) this.registeredItems.get(value)?.emitToggleEvent('emerge-collapse'); + for (const value of closing) + this.registeredItems.get(value)?.emitToggleEvent('emerge-collapse'); for (const value of opening) this.registeredItems.get(value)?.emitToggleEvent('emerge-expand'); } @@ -203,8 +203,7 @@ export class AccordionItemProvider { id: opts.id, ref: opts.ref, owner: this, - context: AccordionItemProvider.ctx, - syncAttrs: true + context: AccordionItemProvider.ctx }); this.contentPresence = new Presence({ @@ -281,8 +280,7 @@ export class AccordionHeaderProvider { disabled: () => this.item.isDisabled, orientation: () => this.item.provider.opts.orientation.current }, - owner: this, - syncAttrs: true + owner: this }); } @@ -323,8 +321,7 @@ export class AccordionTriggerProvider { parts: { content: () => this.item.contentId.current || undefined }, - owner: this, - syncAttrs: true + owner: this }); this.item.triggerId.current = opts.id.current; } @@ -421,8 +418,7 @@ export class AccordionContentProvider { owner: this, onRefChange: (el) => { this.item.setContentRef(el); - }, - syncAttrs: true + } }); this.item.contentId.current = opts.id.current; } diff --git a/src/uix/soma/components/alert-dialog/alert-dialog-provider.svelte.test.ts b/src/uix/soma/components/alert-dialog/alert-dialog-provider.svelte.test.ts index b2bb4e060..3fa381a4b 100644 --- a/src/uix/soma/components/alert-dialog/alert-dialog-provider.svelte.test.ts +++ b/src/uix/soma/components/alert-dialog/alert-dialog-provider.svelte.test.ts @@ -105,14 +105,15 @@ describe('AlertDialogProvider parts', () => { }); await tick(); - expect(actionEl.getAttribute('type')).toBe('button'); + // Bag assert — fase C2c retired the syncAttrs effect (P0, audit 2026-08-26); this test renders nothing. + expect(result.action.props).toMatchObject({ type: 'button' }); // Naming defaults ship in the render bag (consumerWins, A-85), never // through dom.apply — the wrapper's spread is what lands them in the // DOM, so the bag is the surface to assert. Since 2026-08-12 the // default is the CONTRACT's translationRef, resolved through // `translate`: no resolver chain, no opts prop. expect(result.action.props).toMatchObject({ 'aria-label': 'Confirm' }); - expect(cancelEl.getAttribute('type')).toBe('button'); + expect(result.cancel.props).toMatchObject({ type: 'button' }); expect(result.cancel.props).toMatchObject({ 'aria-label': 'Cancel' }); // D1: both close WITH A CAUSE. They used to call `handleClose()`, which diff --git a/src/uix/soma/components/alert-dialog/alert-dialog-provider.svelte.ts b/src/uix/soma/components/alert-dialog/alert-dialog-provider.svelte.ts index 5956ef730..ac3263311 100644 --- a/src/uix/soma/components/alert-dialog/alert-dialog-provider.svelte.ts +++ b/src/uix/soma/components/alert-dialog/alert-dialog-provider.svelte.ts @@ -17,7 +17,6 @@ import { DialogProvider } from '../dialog/dialog-provider.svelte'; import { alertDialogMorfo } from '../../../morfo/components/alert-dialog'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte'; - // ── Action button (primary / destructive) ───────────────────────────────── interface AlertDialogActionOpts extends WithRefOpts {} @@ -44,8 +43,7 @@ export class AlertDialogActionProvider { this.runtimePart = this.runtime.part('action', { id: opts.id, ref: opts.ref, - owner: this, - syncAttrs: true + owner: this }); } @@ -55,9 +53,7 @@ export class AlertDialogActionProvider { * `