diff --git a/audit/SUMMARY.md b/audit/SUMMARY.md index c0f64f61a..b54138b08 100644 --- a/audit/SUMMARY.md +++ b/audit/SUMMARY.md @@ -255,12 +255,7 @@ omits `archetype:'item'`), VirtualList (SvelteMap), accordion + tabs (fully clea O(N) per mutation + fragile (breaks silently if refactored to `.set()` direct) — A33 says rewrite to SvelteMap. virtual-list is the positive model (4× SvelteMap). No plain `$state(new Map/Set)` anywhere in B5 (the dangerous form). - one fix: migrate the four clone-reassign sites to `SvelteMap`/`SvelteSet` (O(1) `.set`, no clone). MEDIUM. -- REFUTED on re-verification (2026-06-27): NONE of the four providers (nor the shared soma layers they consume) - contain a `this.x = new Map(this.x)` clone-and-reassign or any `$state(new Map/Set)` registry. They track items - via `getItems()` DOM query + the `value: string[]` opt + the lifted `selectedSet` `$derived(new Set(...))` (Phase 1). - No reactive-collection anti-pattern exists — the finding does not hold. (virtual-list correctly uses SvelteMap where - it genuinely needs a reactive registry.) -- fix-status: refuted (false positive) +- fix-status: open ## Batch-2 systemic confirmations + corrections diff --git a/audit/components/drawer.md b/audit/components/drawer.md index ed7180020..565553a3d 100644 --- a/audit/components/drawer.md +++ b/audit/components/drawer.md @@ -52,7 +52,7 @@ systemic hits: SYS-1: scope-drift (morfo scope missing 'eidos' despite recipe di - repro: Read src/uix/soma/components/drawer/drawer-provider.svelte.ts lines 1044-1045 and compare with morfo aria declarations at lines 184, 214-217 - proposed-fix: Remove explicit role and aria-modal from provider props — let the runtime apply them from morfo. If dynamic aria-modal is needed, ensure logic matches the morfo's prop-truthy condition exactly. - verify: [downgraded] Real double-write confirmed but severity overstated. Content part is `syncAttrs: true` (provider line 517), so the runtime's syncPartAttrs effect (runtime.svelte.ts:456-465) writes morfo's static `role: 'dialog'` (morfo:184) + dynamic `aria-modal` gated by `prop-truthy modal` (morfo:214-217). Provider props ALSO set them (provider:1044 `role: 'dialog' as const`, 1045 `'aria-modal': this.provider.isOverlay ? true : undefined`). So both attrs are written by two authorities — a true SYS-5 'una sola autoridad' violation. BUT the rubric reserves HIGH for SYS-5 'when the two values can diverge'. They cannot: `role` is the same literal both sides, and `aria-modal` = `isOverlay` (provider:190 `variant==='overlay'`) is identical to morfo's `modal` source (provider:254 `() => opts.variant.current === 'overlay'`). Non-divergent double-write → MEDIUM, not HIGH. Same pattern repeats for Title (provider:1208 `role:'heading'` + 1209 `aria-level` vs morfo:279/282) and Trigger (provider:443-446 type/aria-haspopup/aria-expanded vs morfo:154/162-164) — systemic across this provider. -- fix-status: fixed (729f3c0b) +- fix-status: open ### MEDIUM: SYS-3 — drawer-008 - dimension: F diff --git a/audit/components/popover.md b/audit/components/popover.md index 662865d0a..9a335bc2b 100644 --- a/audit/components/popover.md +++ b/audit/components/popover.md @@ -48,7 +48,7 @@ Counts: CRITICAL 0 · HIGH 0 · MEDIUM 3 · LOW 0. component's own discipline. (Contrast Dialog where the analogous double-write IS a live bug — dialog-001.) - proposed-fix: drop `type`/`aria-label` from the Close props; let `syncAttrs` own them (the morfo already declares both). Keep only `onclick`. -- fix-status: fixed (729f3c0b) +- fix-status: open ### MEDIUM: `content-z: '75'` / `overlay-z: '60'` magic z-index literals - dimension: E-bis diff --git a/src/uix/eidos/generated/base.css b/src/uix/eidos/generated/base.css index 8e96d51fc..35572b3bb 100644 --- a/src/uix/eidos/generated/base.css +++ b/src/uix/eidos/generated/base.css @@ -3422,7 +3422,7 @@ --dropdown-menu-item-radius: var(--radius-sm); --dropdown-menu-item-bg-hover: var(--color-primary-element); --dropdown-menu-item-fg-hover: var(--color-content-primary); - --dropdown-menu-item-disabled-opacity: var(--opacity-disabled); + --dropdown-menu-item-disabled-opacity: 0.55; --dropdown-menu-heading-px: var(--space-2); --dropdown-menu-heading-py: var(--space-1); --dropdown-menu-heading-font-size: var(--font-size-xs); @@ -3440,7 +3440,7 @@ --context-menu-item-radius: var(--radius-sm); --context-menu-item-bg-hover: var(--color-primary-element); --context-menu-item-fg-hover: var(--color-content-primary); - --context-menu-item-disabled-opacity: var(--opacity-disabled); + --context-menu-item-disabled-opacity: 0.55; --context-menu-heading-px: var(--space-2); --context-menu-heading-py: var(--space-1); --context-menu-heading-font-size: var(--font-size-xs); diff --git a/src/uix/eidos/lib/recipes/base.ts b/src/uix/eidos/lib/recipes/base.ts index 4674d44da..cdcd480ac 100644 --- a/src/uix/eidos/lib/recipes/base.ts +++ b/src/uix/eidos/lib/recipes/base.ts @@ -4291,7 +4291,7 @@ export const THEME_BASE_RECIPE_TOKENS = { 'item-radius': 'var(--radius-sm)', 'item-bg-hover': 'var(--color-primary-element)', 'item-fg-hover': 'var(--color-content-primary)', - 'item-disabled-opacity': 'var(--opacity-disabled)', + 'item-disabled-opacity': '0.55', 'heading-px': 'var(--space-2)', 'heading-py': 'var(--space-1)', 'heading-font-size': 'var(--font-size-xs)', @@ -4315,7 +4315,7 @@ export const THEME_BASE_RECIPE_TOKENS = { 'item-radius': 'var(--radius-sm)', 'item-bg-hover': 'var(--color-primary-element)', 'item-fg-hover': 'var(--color-content-primary)', - 'item-disabled-opacity': 'var(--opacity-disabled)', + 'item-disabled-opacity': '0.55', 'heading-px': 'var(--space-2)', 'heading-py': 'var(--space-1)', 'heading-font-size': 'var(--font-size-xs)', diff --git a/src/uix/soma/components/drawer/drawer-provider.svelte.ts b/src/uix/soma/components/drawer/drawer-provider.svelte.ts index a4fb3333b..9ad500999 100644 --- a/src/uix/soma/components/drawer/drawer-provider.svelte.ts +++ b/src/uix/soma/components/drawer/drawer-provider.svelte.ts @@ -1041,10 +1041,8 @@ export class DrawerContentProvider { return { ...this.runtimePart.props, - // 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. + role: 'dialog' as const, + 'aria-modal': this.provider.isOverlay ? true : undefined, '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 53895c26f..989f33b97 100644 --- a/src/uix/soma/components/popover/popover-provider.svelte.ts +++ b/src/uix/soma/components/popover/popover-provider.svelte.ts @@ -17,6 +17,7 @@ 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'; @@ -751,9 +752,8 @@ export class PopoverCloseProvider { readonly props = $derived.by(() => ({ ...this.runtimePart.props, - // 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. + type: 'button' as const, + 'aria-label': this.provider.soma.langs.ts(POPOVER_LANGS.CLOSE), onclick: this.onclick })); }