diff --git a/src/uix/soma/components/collapsible/collapsible-provider.svelte.ts b/src/uix/soma/components/collapsible/collapsible-provider.svelte.ts index b900978d7..62c684431 100644 --- a/src/uix/soma/components/collapsible/collapsible-provider.svelte.ts +++ b/src/uix/soma/components/collapsible/collapsible-provider.svelte.ts @@ -1,5 +1,5 @@ -import { context } from '../../provider'; -import { state, type Active, type ActiveProps, type State, type StateProps } from '$libs/reactive'; +import { context, type WithRefOpts } from '../../provider'; +import { state, type ActiveProps, type StateProps } from '$libs/reactive'; import type { SomaMouseEvent } from '../../types'; import { Soma } from '../../core/soma.svelte'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte'; @@ -8,10 +8,7 @@ import { collapsibleMorfo } from '../../../morfo/components/collapsible'; // ── Provider ───────────────────────────────────────────────────────────────── interface CollapsibleOpts - extends StateProps<{ open: boolean }>, ActiveProps<{ disabled: boolean }> { - id: Active; - ref: State; -} + extends WithRefOpts, StateProps<{ open: boolean }>, ActiveProps<{ disabled: boolean }> {} /** * Collapsible root — runtime-direct (no `Provider` base class). @@ -95,10 +92,7 @@ export class CollapsibleProvider { // ── Trigger ────────────────────────────────────────────────────────────────── -interface CollapsibleTriggerOpts { - id: Active; - ref: State; -} +interface CollapsibleTriggerOpts extends WithRefOpts {} export class CollapsibleTriggerProvider { static create(opts: CollapsibleTriggerOpts) { @@ -138,10 +132,7 @@ export class CollapsibleTriggerProvider { // ── Content ────────────────────────────────────────────────────────────────── -interface CollapsibleContentOpts { - id: Active; - ref: State; -} +interface CollapsibleContentOpts extends WithRefOpts {} export class CollapsibleContentProvider { static create(opts: CollapsibleContentOpts) { diff --git a/src/uix/soma/components/dialog/dialog-provider.svelte.ts b/src/uix/soma/components/dialog/dialog-provider.svelte.ts index faa82bcf2..94b1d3ede 100644 --- a/src/uix/soma/components/dialog/dialog-provider.svelte.ts +++ b/src/uix/soma/components/dialog/dialog-provider.svelte.ts @@ -1,12 +1,5 @@ -import { context } from '../../provider'; -import { - readableActive, - state, - type Active, - type ActiveProps, - type State, - type StateProps -} from '$libs/reactive'; +import { context, type ProviderOpts, type WithRefOpts } from '../../provider'; +import { readableActive, state, type ActiveProps, type StateProps } from '$libs/reactive'; import type { OnChangeFn, SomaMouseEvent } from '../../types'; import { Soma } from '../../core/soma.svelte'; import { watch } from '$libs/reactive'; @@ -66,6 +59,7 @@ const DISMISS_CAUSES: Record< interface DialogOpts extends + ProviderOpts, StateProps<{ open: boolean }>, ActiveProps<{ disabled: boolean; @@ -73,10 +67,7 @@ interface DialogOpts variant: DialogVariant; intent: 'neutral' | 'risk' | 'threat'; onOpenChangeComplete: OnChangeFn; - }> { - id: Active; - ref?: State; -} + }> {} /** * Dialog root — runtime-direct (no `Provider` base inheritance). @@ -294,10 +285,7 @@ export class DialogProvider { // ── Trigger ──────────────────────────────────────────────────────────────── -interface DialogTriggerOpts { - id: Active; - ref: State; -} +interface DialogTriggerOpts extends WithRefOpts {} export class DialogTriggerProvider { static create(opts: DialogTriggerOpts) { @@ -343,7 +331,7 @@ export class DialogTriggerProvider { // ── Content ───────────────────────────────────────────────────────────────── -interface DialogContentOpts extends ActiveProps<{ +interface DialogContentOpts extends WithRefOpts, ActiveProps<{ trapFocus: boolean | undefined; loop: boolean; preventScroll: boolean | undefined; @@ -355,10 +343,7 @@ interface DialogContentOpts extends ActiveProps<{ onInteractOutside: (e: PointerEvent) => void; interactOutsideBehavior: DismissalBehavior | undefined; onFocusOutside: (e: FocusEvent) => void; -}> { - id: Active; - ref: State; -} +}> {} export class DialogContentProvider { static create(opts: DialogContentOpts) { @@ -477,10 +462,7 @@ export class DialogContentProvider { // ── Overlay ──────────────────────────────────────────────────────────────── -interface DialogOverlayOpts { - id: Active; - ref: State; -} +interface DialogOverlayOpts extends WithRefOpts {} export class DialogOverlayProvider { static create(opts: DialogOverlayOpts) { @@ -520,10 +502,7 @@ export class DialogOverlayProvider { // ── Title ────────────────────────────────────────────────────────────────── -interface DialogTitleOpts extends ActiveProps<{ level: number }> { - id: Active; - ref: State; -} +interface DialogTitleOpts extends WithRefOpts, ActiveProps<{ level: number }> {} export class DialogTitleProvider { static create(opts: DialogTitleOpts) { @@ -556,10 +535,7 @@ export class DialogTitleProvider { // ── Description ──────────────────────────────────────────────────────────── -interface DialogDescriptionOpts { - id: Active; - ref: State; -} +interface DialogDescriptionOpts extends WithRefOpts {} export class DialogDescriptionProvider { static create(opts: DialogDescriptionOpts) { @@ -589,10 +565,7 @@ export class DialogDescriptionProvider { // ── Close ────────────────────────────────────────────────────────────────── -interface DialogCloseOpts { - id: Active; - ref: State; -} +interface DialogCloseOpts extends WithRefOpts {} export class DialogCloseProvider { static create(opts: DialogCloseOpts) { diff --git a/src/uix/soma/components/drawer/drawer-provider.svelte.ts b/src/uix/soma/components/drawer/drawer-provider.svelte.ts index 99a30df70..b215c715e 100644 --- a/src/uix/soma/components/drawer/drawer-provider.svelte.ts +++ b/src/uix/soma/components/drawer/drawer-provider.svelte.ts @@ -1,14 +1,7 @@ -import { context } from '../../provider'; +import { context, type ProviderOpts, type WithRefOpts } from '../../provider'; import { resolveDir } from '../../direction'; import { boolToEmptyStrOrUndef, getDataOpenClosed } from '$adom'; -import { - readableActive, - state, - type Active, - type ActiveProps, - type State, - type StateProps -} from '$libs/reactive'; +import { readableActive, state, type ActiveProps, type StateProps } from '$libs/reactive'; import type { SomaMouseEvent, SomaKeyboardEvent, Direction } from '../../types'; import { KEYS } from '../../keyboard'; import { Soma } from '../../core/soma.svelte'; @@ -125,10 +118,8 @@ function defaultMinSize(refSize: number): number { // ── Provider (root) ───────────────────────────────────────────────────────── -interface DrawerOpts extends StateProps, ActiveProps { - id: Active; - ref?: State; -} +interface DrawerOpts + extends ProviderOpts, StateProps, ActiveProps {} /** * Drawer root — runtime-direct (no `Provider` base inheritance). @@ -397,10 +388,7 @@ export class DrawerProvider { // ── Trigger ───────────────────────────────────────────────────────────────── -interface DrawerTriggerOpts { - id: Active; - ref: State; -} +interface DrawerTriggerOpts extends WithRefOpts {} export class DrawerTriggerProvider { static create(opts: DrawerTriggerOpts) { @@ -460,10 +448,7 @@ export class DrawerTriggerProvider { // ── Content ───────────────────────────────────────────────────────────────── -interface DrawerContentOpts extends ActiveProps { - id: Active; - ref: State; -} +interface DrawerContentOpts extends WithRefOpts, ActiveProps {} export class DrawerContentProvider { static create(opts: DrawerContentOpts) { @@ -1094,10 +1079,7 @@ export class DrawerContentProvider { // ── Overlay ───────────────────────────────────────────────────────────────── -interface DrawerOverlayOpts { - id: Active; - ref: State; -} +interface DrawerOverlayOpts extends WithRefOpts {} export class DrawerOverlayProvider { static create(opts: DrawerOverlayOpts) { @@ -1146,10 +1128,7 @@ export class DrawerOverlayProvider { // ── Handle ────────────────────────────────────────────────────────────────── -interface DrawerHandleOpts { - id: Active; - ref: State; -} +interface DrawerHandleOpts extends WithRefOpts {} export class DrawerHandleProvider { static create(opts: DrawerHandleOpts) { @@ -1187,10 +1166,7 @@ export class DrawerHandleProvider { // ── Title ─────────────────────────────────────────────────────────────────── -interface DrawerTitleOpts extends ActiveProps<{ level: number }> { - id: Active; - ref: State; -} +interface DrawerTitleOpts extends WithRefOpts, ActiveProps<{ level: number }> {} export class DrawerTitleProvider { static create(opts: DrawerTitleOpts) { @@ -1223,10 +1199,7 @@ export class DrawerTitleProvider { // ── Description ───────────────────────────────────────────────────────────── -interface DrawerDescriptionOpts { - id: Active; - ref: State; -} +interface DrawerDescriptionOpts extends WithRefOpts {} export class DrawerDescriptionProvider { static create(opts: DrawerDescriptionOpts) { @@ -1256,10 +1229,7 @@ export class DrawerDescriptionProvider { // ── Close ─────────────────────────────────────────────────────────────────── -interface DrawerCloseOpts { - id: Active; - ref: State; -} +interface DrawerCloseOpts extends WithRefOpts {} export class DrawerCloseProvider { static create(opts: DrawerCloseOpts) { diff --git a/src/uix/soma/components/float-panel/float-panel-provider.svelte.ts b/src/uix/soma/components/float-panel/float-panel-provider.svelte.ts index 01aca6305..58952a97b 100644 --- a/src/uix/soma/components/float-panel/float-panel-provider.svelte.ts +++ b/src/uix/soma/components/float-panel/float-panel-provider.svelte.ts @@ -1,12 +1,11 @@ import { untrack } from 'svelte'; import { resolveDir } from '../../direction'; -import { context } from '../../provider'; +import { context, type ProviderOpts, type WithRefOpts } from '../../provider'; import { state, readableActive, type Active, type ActiveProps, - type State, type StateProps } from '$libs/reactive'; import type { OnChangeFn, SomaMouseEvent, SomaKeyboardEvent, Direction } from '../../types'; @@ -86,6 +85,7 @@ const KB_ANNOUNCE_DEBOUNCE_MS = 300; interface FloatPanelOpts extends + ProviderOpts, StateProps<{ open: boolean; position: FloatPanelPosition | undefined; @@ -115,10 +115,7 @@ interface FloatPanelOpts zIndex: number | undefined; onOpenChangeComplete: OnChangeFn | undefined; dir: Direction | undefined; - }> { - id: Active; - ref?: State; -} + }> {} export class FloatPanelProvider { /** @@ -960,10 +957,7 @@ export class FloatPanelProvider { // ── Trigger ───────────────────────────────────────────────────────────────── -interface FloatPanelTriggerOpts { - id: Active; - ref: State; -} +interface FloatPanelTriggerOpts extends WithRefOpts {} export class FloatPanelTriggerProvider { static create(opts: FloatPanelTriggerOpts) { @@ -1007,10 +1001,7 @@ export class FloatPanelTriggerProvider { // ── Content ─────────────────────────────────────────────────────────────────── -interface FloatPanelContentOpts { - id: Active; - ref: State; -} +interface FloatPanelContentOpts extends WithRefOpts {} export class FloatPanelContentProvider { static create(opts: FloatPanelContentOpts) { @@ -1187,10 +1178,7 @@ export class FloatPanelContentProvider { // ── Header (drag handle) ────────────────────────────────────────────────────── -interface FloatPanelHeaderOpts { - id: Active; - ref: State; -} +interface FloatPanelHeaderOpts extends WithRefOpts {} export class FloatPanelHeaderProvider { static create(opts: FloatPanelHeaderOpts) { @@ -1238,10 +1226,7 @@ export class FloatPanelHeaderProvider { // ── Title ────────────────────────────────────────────────────────────────── -interface FloatPanelTitleOpts extends ActiveProps<{ level: number }> { - id: Active; - ref: State; -} +interface FloatPanelTitleOpts extends WithRefOpts, ActiveProps<{ level: number }> {} export class FloatPanelTitleProvider { static create(opts: FloatPanelTitleOpts) { @@ -1270,9 +1255,7 @@ export class FloatPanelTitleProvider { // ── Close ──────────────────────────────────────────────────────────────────── -interface FloatPanelCloseOpts { - id: Active; - ref: State; +interface FloatPanelCloseOpts extends WithRefOpts { action: Active<'save' | 'cancel' | 'dismiss'>; } @@ -1310,10 +1293,7 @@ export class FloatPanelCloseProvider { // ── Body ─────────────────────────────────────────────────────────────────── -interface FloatPanelBodyOpts { - id: Active; - ref: State; -} +interface FloatPanelBodyOpts extends WithRefOpts {} export class FloatPanelBodyProvider { static create(opts: FloatPanelBodyOpts) { @@ -1340,10 +1320,7 @@ export class FloatPanelBodyProvider { // ── Controls (header button group) ────────────────────────────────────────── -interface FloatPanelControlsOpts { - id: Active; - ref: State; -} +interface FloatPanelControlsOpts extends WithRefOpts {} export class FloatPanelControlsProvider { static create(opts: FloatPanelControlsOpts) { @@ -1370,10 +1347,7 @@ export class FloatPanelControlsProvider { // ── Action (generic header button) ────────────────────────────────────────── -interface FloatPanelActionOpts { - id: Active; - ref: State; -} +interface FloatPanelActionOpts extends WithRefOpts {} export class FloatPanelActionProvider { static create(opts: FloatPanelActionOpts) { @@ -1405,10 +1379,8 @@ export class FloatPanelActionProvider { // ── StageTrigger (minimize / maximize / restore) ───────────────────────────── -interface FloatPanelStageTriggerOpts extends ActiveProps<{ stage: FloatPanelStage }> { - id: Active; - ref: State; -} +interface FloatPanelStageTriggerOpts + extends WithRefOpts, ActiveProps<{ stage: FloatPanelStage }> {} export class FloatPanelStageTriggerProvider { static create(opts: FloatPanelStageTriggerOpts) { @@ -1443,10 +1415,8 @@ export class FloatPanelStageTriggerProvider { // ── ResizeHandle ────────────────────────────────────────────────────────────── -interface FloatPanelResizeHandleOpts extends ActiveProps<{ edge: FloatPanelResizeEdge }> { - id: Active; - ref: State; -} +interface FloatPanelResizeHandleOpts + extends WithRefOpts, ActiveProps<{ edge: FloatPanelResizeEdge }> {} export class FloatPanelResizeHandleProvider { static create(opts: FloatPanelResizeHandleOpts) { @@ -1481,10 +1451,7 @@ export class FloatPanelResizeHandleProvider { // ── ResizeGrip (keyboard resize affordance) ───────────────────────────────── -interface FloatPanelResizeGripOpts { - id: Active; - ref: State; -} +interface FloatPanelResizeGripOpts extends WithRefOpts {} export class FloatPanelResizeGripProvider { static create(opts: FloatPanelResizeGripOpts) { diff --git a/src/uix/soma/components/popover/popover-provider.svelte.ts b/src/uix/soma/components/popover/popover-provider.svelte.ts index 046dd211e..8e7ec9c0d 100644 --- a/src/uix/soma/components/popover/popover-provider.svelte.ts +++ b/src/uix/soma/components/popover/popover-provider.svelte.ts @@ -1,10 +1,9 @@ -import { context } from '../../provider'; +import { context, type ProviderOpts, type WithRefOpts } from '../../provider'; import { readableActive, state, type Active, type ActiveProps, - type State, type StateProps } from '$libs/reactive'; import type { @@ -76,13 +75,12 @@ const DISMISS_CAUSES: Record< interface PopoverOpts extends + ProviderOpts, StateProps<{ open: boolean }>, ActiveProps<{ modal: boolean; onOpenChangeComplete: OnChangeFn; }> { - id: Active; - ref?: State; /** * The direction this popover asserts, raw (`activeDir` result). The root * renders no element, so nothing is stamped in place — the value exists to @@ -335,7 +333,7 @@ export class PopoverProvider { // ── Trigger ───────────────────────────────────────────────────────────────── -interface PopoverTriggerOpts extends ActiveProps<{ +interface PopoverTriggerOpts extends WithRefOpts, ActiveProps<{ openOnHover: boolean; openDelay: number; closeDelay: number; @@ -347,10 +345,7 @@ interface PopoverTriggerOpts extends ActiveProps<{ * to honour `disabled` / `readonly` without losing focusability. */ disabled?: boolean; -}> { - id: Active; - ref: State; -} +}> {} export class PopoverTriggerProvider { static create(opts: PopoverTriggerOpts) { @@ -456,7 +451,7 @@ export class PopoverTriggerProvider { // ── Content ───────────────────────────────────────────────────────────────── -interface PopoverContentOpts extends ActiveProps<{ +interface PopoverContentOpts extends WithRefOpts, ActiveProps<{ side: Side; sideOffset: number; align: Align; @@ -489,10 +484,7 @@ interface PopoverContentOpts extends ActiveProps<{ * from the portal host instead of forcing `ltr`. */ dir: Direction | undefined; style: StyleProperties | null | undefined | string; -}> { - id: Active; - ref: State; -} +}> {} export class PopoverContentProvider { static create(opts: PopoverContentOpts) { @@ -640,10 +632,7 @@ export class PopoverContentProvider { // ── Title ────────────────────────────────────────────────────────────────── -interface PopoverTitleOpts extends ActiveProps<{ level: number }> { - id: Active; - ref: State; -} +interface PopoverTitleOpts extends WithRefOpts, ActiveProps<{ level: number }> {} export class PopoverTitleProvider { static create(opts: PopoverTitleOpts) { @@ -674,10 +663,7 @@ export class PopoverTitleProvider { // ── Description ──────────────────────────────────────────────────────────── -interface PopoverDescriptionOpts { - id: Active; - ref: State; -} +interface PopoverDescriptionOpts extends WithRefOpts {} export class PopoverDescriptionProvider { static create(opts: PopoverDescriptionOpts) { @@ -707,10 +693,7 @@ export class PopoverDescriptionProvider { // ── Arrow ──────────────────────────────────────────────────────────────────── -interface PopoverArrowOpts { - id: Active; - ref: State; -} +interface PopoverArrowOpts extends WithRefOpts {} export class PopoverArrowProvider { static create(opts: PopoverArrowOpts) { @@ -742,9 +725,7 @@ export class PopoverArrowProvider { // ── Close ──────────────────────────────────────────────────────────────────── -interface PopoverCloseOpts { - id: Active; - ref: State; +interface PopoverCloseOpts extends WithRefOpts { action: Active<'save' | 'cancel' | 'dismiss'>; } @@ -784,10 +765,7 @@ export class PopoverCloseProvider { // ── Anchor ─────────────────────────────────────────────────────────────────── -interface PopoverAnchorOpts { - id: Active; - ref: State; -} +interface PopoverAnchorOpts extends WithRefOpts {} export class PopoverAnchorProvider { static create(opts: PopoverAnchorOpts) { @@ -822,10 +800,7 @@ export class PopoverAnchorProvider { // ── Overlay ────────────────────────────────────────────────────────────────── -interface PopoverOverlayOpts { - id: Active; - ref: State; -} +interface PopoverOverlayOpts extends WithRefOpts {} export class PopoverOverlayProvider { static create(opts: PopoverOverlayOpts) { diff --git a/src/uix/soma/components/toast/toast-provider.svelte.ts b/src/uix/soma/components/toast/toast-provider.svelte.ts index 4148ed589..1ee753b23 100644 --- a/src/uix/soma/components/toast/toast-provider.svelte.ts +++ b/src/uix/soma/components/toast/toast-provider.svelte.ts @@ -1,8 +1,8 @@ import { untrack } from 'svelte'; import type { Direction } from '../../types'; -import { context } from '../../provider'; -import { readableActive, state, type Active, type ActiveProps, type State } from '$libs/reactive'; +import { context, type ProviderOpts, type WithRefOpts } from '../../provider'; +import { readableActive, state, type ActiveProps } from '$libs/reactive'; import type { SomaMouseEvent } from '../../types'; import { Soma } from '../../core/soma.svelte'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte'; @@ -15,13 +15,13 @@ import { toastMorfo } from '../../../morfo/components/toast'; // ── Provider (root, virtual) ──────────────────────────────────────────────── -interface ToastProviderOpts extends ActiveProps<{ - toaster: Toaster; - label: string | undefined; -}> { - id: Active; - ref?: State; -} +interface ToastProviderOpts + extends + ProviderOpts, + ActiveProps<{ + toaster: Toaster; + label: string | undefined; + }> {} /** * Toast root — runtime-direct (no `Provider` base inheritance). @@ -71,10 +71,7 @@ export class ToastProvider { // ── Viewport ──────────────────────────────────────────────────────────────── -interface ToastViewportOpts { - id: Active; - ref: State; -} +interface ToastViewportOpts extends WithRefOpts {} export class ToastViewportProvider { static create(opts: ToastViewportOpts) { @@ -172,12 +169,12 @@ export class ToastViewportProvider { // ── Item (individual toast) ───────────────────────────────────────────────── -interface ToastItemOpts extends ActiveProps<{ - toast: ToastData; -}> { - id: Active; - ref: State; -} +interface ToastItemOpts + extends + WithRefOpts, + ActiveProps<{ + toast: ToastData; + }> {} export class ToastItemProvider { static readonly ctx = context('ToastItem'); @@ -518,10 +515,7 @@ export class ToastItemProvider { // ── Title ─────────────────────────────────────────────────────────────────── -interface ToastTitleOpts { - id: Active; - ref: State; -} +interface ToastTitleOpts extends WithRefOpts {} export class ToastTitleProvider { static create(opts: ToastTitleOpts) { @@ -551,10 +545,7 @@ export class ToastTitleProvider { // ── Description ───────────────────────────────────────────────────────────── -interface ToastDescriptionOpts { - id: Active; - ref: State; -} +interface ToastDescriptionOpts extends WithRefOpts {} export class ToastDescriptionProvider { static create(opts: ToastDescriptionOpts) { @@ -584,12 +575,12 @@ export class ToastDescriptionProvider { // ── Action ────────────────────────────────────────────────────────────────── -interface ToastActionOpts extends ActiveProps<{ - altText: string; -}> { - id: Active; - ref: State; -} +interface ToastActionOpts + extends + WithRefOpts, + ActiveProps<{ + altText: string; + }> {} export class ToastActionProvider { static create(opts: ToastActionOpts) { @@ -624,10 +615,7 @@ export class ToastActionProvider { // ── Close ─────────────────────────────────────────────────────────────────── -interface ToastCloseOpts { - id: Active; - ref: State; -} +interface ToastCloseOpts extends WithRefOpts {} export class ToastCloseProvider { static create(opts: ToastCloseOpts) { @@ -667,10 +655,7 @@ export class ToastCloseProvider { // ── Status ────────────────────────────────────────────────────────────────── -interface ToastStatusOpts { - id: Active; - ref: State; -} +interface ToastStatusOpts extends WithRefOpts {} /** * Decorative status indicator (the leading-edge icon disc). Pure @@ -706,10 +691,7 @@ export class ToastStatusProvider { // ── Main ──────────────────────────────────────────────────────────────────── -interface ToastMainOpts { - id: Active; - ref: State; -} +interface ToastMainOpts extends WithRefOpts {} /** * Layout-only stack container for Title + Description + Action. Declared diff --git a/src/uix/soma/provider/opts-census.test.ts b/src/uix/soma/provider/opts-census.test.ts new file mode 100644 index 000000000..70bb7b688 --- /dev/null +++ b/src/uix/soma/provider/opts-census.test.ts @@ -0,0 +1,75 @@ +import { readdirSync, readFileSync } from 'node:fs'; +import { join, relative } from 'node:path'; +import { describe, expect, it } from 'vitest'; + +/** + * The census guard of the opts axis: no opts interface inside + * `soma/components` re-declares `id` / `ref` in its own body — those two + * keys come from `WithRefOpts` / `ProviderOpts` (`provider.svelte.ts`), + * the single place that says what they are. History: 49 interfaces across + * 7 files had drifted into verbatim copies before the 2026-08-05 merge. + * + * The scan is a raw-line, CASE-SENSITIVE match — deliberately not a + * parser. The brace-matching parser that produced the first census count + * missed 16 interfaces whose `extends` clause carries inline literal + * generics (`StateProps<{ open: boolean }>`); a line match has no such + * blind spot, and it also covers `type XOpts = { … }` literal bodies. + * Case matters: `menubar-provider.svelte.ts` composes an internal menu + * with `ref: state(null)` — the FUNCTION `state` at a + * call-site — which a case-insensitive scan would flag as a false positive. + */ + +const COMPONENTS_DIR = join(__dirname, '..', 'components'); + +/** + * Signed exceptions — relative path → reason. Growing this list is a + * decision, not a convenience. + */ +const EXCEPTIONS: Record = {}; + +// Prefix matches on purpose: a widened `id: Active` or +// a narrowed `ref: State` is also a deviation from the base. +const ID_DECL = /^\s*id\s*:\s*Active/; +const REF_DECL = /^\s*ref\s*\??\s*:\s*State { + const files = scanTsFiles(COMPONENTS_DIR); + + it('finds the catalogue (sanity: the census is not scanning an empty tree)', () => { + expect(files.length).toBeGreaterThan(200); + }); + + it('no file in components/ re-declares the canonical id/ref lines', () => { + const offenders: string[] = []; + for (const file of files) { + const rel = relative(COMPONENTS_DIR, file).replace(/\\/g, '/'); + if (rel in EXCEPTIONS) continue; + const lines = readFileSync(file, 'utf-8').split('\n'); + for (let i = 0; i < lines.length; i++) { + if (ID_DECL.test(lines[i]) || REF_DECL.test(lines[i])) { + offenders.push(`${rel}:${i + 1}`); + } + } + } + expect( + offenders, + 'id/ref re-declared verbatim — extend WithRefOpts / ProviderOpts instead' + ).toEqual([]); + }); + + it('the exceptions are real files that still exist', () => { + const relSet = new Set(files.map((f) => relative(COMPONENTS_DIR, f).replace(/\\/g, '/'))); + const stale = Object.keys(EXCEPTIONS).filter((rel) => !relSet.has(rel)); + expect(stale, 'these exceptions point at files that no longer exist — retire them').toEqual([]); + }); +});