From 729f3c0b4568b55db5b9715237e6864d5c8ed6b5 Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 27 Jun 2026 01:35:47 +0200 Subject: [PATCH] =?UTF-8?q?fix(uix):=20SYS-5=20=E2=80=94=20drop=20double-w?= =?UTF-8?q?ritten=20attrs=20owned=20by=20syncAttrs=20(drawer,=20popover)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both parts register with `syncAttrs: true`, so the runtime writes their morfo-declared attrs to the DOM after render — overwriting any provider re-set (the dialog-001 mechanism). The providers re-declared the same attrs anyway: - drawer Content [drawer-007]: dropped manual `role: 'dialog'` + `aria-modal`. The morfo Content declares `role: 'dialog'` and `aria-modal` (condition: the registered `modal` source = `variant === 'overlay'` = isOverlay) — identical values. Browser-verified: after the change the Content still has role="dialog" + aria-modal="true" on an overlay drawer, now from the morfo. - popover Close [popover-001]: dropped manual `type: 'button'` + `aria-label`. The morfo Close declares `type` and `aria-label` via `commonRef('buttons.close')`, which syncAttrs already wrote (the provider's POPOVER_LANGS.CLOSE re-set was dead). Removed the now-orphan import. Single authority per attribute (rule 7.7). check: 0 new errors; drawer + popover provider tests 7/7. Co-Authored-By: Claude Opus 4.8 --- src/uix/soma/components/drawer/drawer-provider.svelte.ts | 6 ++++-- src/uix/soma/components/popover/popover-provider.svelte.ts | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/uix/soma/components/drawer/drawer-provider.svelte.ts b/src/uix/soma/components/drawer/drawer-provider.svelte.ts index 9ad500999..a4fb3333b 100644 --- a/src/uix/soma/components/drawer/drawer-provider.svelte.ts +++ b/src/uix/soma/components/drawer/drawer-provider.svelte.ts @@ -1041,8 +1041,10 @@ export class DrawerContentProvider { return { ...this.runtimePart.props, - role: 'dialog' as const, - 'aria-modal': this.provider.isOverlay ? true : undefined, + // role ('dialog') + aria-modal (condition: `modal` source = isOverlay) are + // declared by the morfo Content part and written by syncAttrs, which runs + // after render and would overwrite a provider re-set anyway (SYS-5 / the + // dialog-001 mechanism). The morfo is the single authority — not re-set here. 'aria-describedby': this.provider.descriptionId.current || undefined, 'aria-labelledby': this.provider.titleId.current || undefined, 'data-state': getDataOpenClosed(this.provider.opts.open.current), diff --git a/src/uix/soma/components/popover/popover-provider.svelte.ts b/src/uix/soma/components/popover/popover-provider.svelte.ts index 989f33b97..53895c26f 100644 --- a/src/uix/soma/components/popover/popover-provider.svelte.ts +++ b/src/uix/soma/components/popover/popover-provider.svelte.ts @@ -17,7 +17,6 @@ import type { } from '../../types'; import { KEYS } from '../../keyboard'; import { Soma } from '../../core/soma.svelte'; -import { POPOVER_LANGS } from './langs'; import { contains } from '$adom'; import type { TimerHandle } from '$timer'; @@ -752,8 +751,9 @@ export class PopoverCloseProvider { readonly props = $derived.by(() => ({ ...this.runtimePart.props, - type: 'button' as const, - 'aria-label': this.provider.soma.langs.ts(POPOVER_LANGS.CLOSE), + // type ('button') + aria-label (commonRef 'buttons.close') are declared by + // the morfo Close part + written by syncAttrs (which overwrites a provider + // re-set after render, SYS-5). The morfo is the single authority. onclick: this.onclick })); }