|
|
|
|
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
|
|
|
import { join } from 'node:path'
|
|
|
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
|
|
|
|
|
|
const COMPONENTS_DIR = 'src/uix/eidos/components'
|
|
|
|
|
const ROOT_FILE_EXCEPTIONS = new Set(['svg'])
|
test(eidos): triage the 15 long-red suite failures — 289/289 green, check 61→59
Accumulated test debt (the eidos suite had been red across several feature
landings — THEME-SYS-1 named z-index, palette-first static donor library,
spin-field extraction, icon stroke-map refinement — none of them regressions;
baselined against HEAD). Each triaged stale-vs-bug by confirming current
behavior in code before touching a line.
Stale tests (code correct/improved, assertion not updated):
- z-index '76' -> 'var(--z-index-overlay-tooltip)' (the code is guarded by
THEME-SYS-1 in contracts.test.ts — the raw integer is forbidden).
- frost '80%' -> 'var(--depth-overlay-translucency, 80%)' (parameterized).
- C7 size-bundle coordinate ('--control-height-md' -> '--size-md-control-height').
- icon visual-stroke ratios 1.5->1.125, 1->0.923 (stroke map md 2->1.5, xxl
icon 48->52px; the arithmetic follows).
- delegate/CSS-only purple-9: the slim foundation legitimately emits the
role-referenced donor scales (purple=primary) as overridable :root defaults;
the real delegation guarantee is 'no baked [data-theme] block', which the
assertion now checks.
Test over-flagging (fixed the test, not the code):
- CSS-only shared layer: spin-field (no wrapper/index) excluded from the
wrapper/barrel/namespace contracts (CSS_ONLY_LAYERS).
- soma-runtime vars: 'var(--x, fallback)' is runtime-optional (the STUMBLES #7
soma->eidos surface) — only bare 'var(--x)' requires a recipe declaration.
- orphan check made corpus-wide: composition means a sibling consumes a
component's tokens (date-picker/month-grid consume calendar-control-*).
- natural-time-picker fixed-tone sky colors documented-exempt (Words-rail
doctrine).
- dialog drift-detector converted to a synthetic fixture (dialog.css no longer
ships the dead rule it self-tested against).
Real code fixes:
- declare '--pin-input-cell-readonly-bg' (I consumed it in C2b without
declaring it).
- remove 5 truly-dead tokens (drawer content-bg + 4 color-field
format-select/segment — 0 consumers anywhere in eidos/soma/web).
- clamp time-picker's 'size' to PopoverSize before forwarding (xs->sm):
TimePickerSize has xs, PopoverSize doesn't — a latent type bug my recipe
change surfaced via a complexity-budget flip. The fix dropped check 61->59,
also clearing navigation-menu-content of the same class.
card-group flagged as a REAL gap (KNOWN_MISSING_MORFO): it ships parts + chrome
but no morfo (morfo-first violation) — tracked, not silenced.
Verified: vitest src/uix/eidos 23 files / 289 tests green; npm run check 59
(better than the 61 baseline); component:audit 0 BROKEN.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
// CSS-only shared visual layers: no wrapper, no index.ts — a `{name}.css`
|
|
|
|
|
// consumed by sibling recipes via structural identity (`data-{name}*`). The
|
|
|
|
|
// wrapper/barrel/namespace contracts do not apply. `spin-field` is the shared
|
|
|
|
|
// spin-field visual behind css-field + number-field.
|
|
|
|
|
const CSS_ONLY_LAYERS = new Set(['spin-field'])
|
|
|
|
|
const COMPONENT_SOURCE_FILE = /\.(css|svelte|ts)$/
|
|
|
|
|
|
|
|
|
|
interface NamespaceContract {
|
|
|
|
|
component: string
|
|
|
|
|
properties: readonly string[]
|
|
|
|
|
assignments: readonly string[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function stripComments(source: string): string {
|
|
|
|
|
return source.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function readComponentDirs(): readonly string[] {
|
|
|
|
|
return readdirSync(COMPONENTS_DIR, { withFileTypes: true })
|
|
|
|
|
.filter((entry) => entry.isDirectory())
|
|
|
|
|
.map((entry) => entry.name)
|
|
|
|
|
.sort()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function readComponentSourceFiles(dir = COMPONENTS_DIR): readonly { path: string; source: string }[] {
|
|
|
|
|
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
|
|
|
const path = join(dir, entry.name)
|
|
|
|
|
|
|
|
|
|
if (entry.isDirectory()) return readComponentSourceFiles(path)
|
|
|
|
|
if (!entry.isFile() || !COMPONENT_SOURCE_FILE.test(entry.name)) return []
|
|
|
|
|
|
|
|
|
|
return [{ path, source: stripComments(readFileSync(path, 'utf8')) }]
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function readIndexSource(component: string): string {
|
|
|
|
|
return stripComments(readFileSync(join(COMPONENTS_DIR, component, 'index.ts'), 'utf8'))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function readNamespaceContract(source: string): NamespaceContract | undefined {
|
|
|
|
|
const match = /type\s+([A-Z][A-Za-z0-9]+)Namespace\s*=\s*typeof\s+\w+\s*&\s*\{([\s\S]*?)\n\}/.exec(
|
|
|
|
|
source
|
|
|
|
|
)
|
|
|
|
|
if (!match) return undefined
|
|
|
|
|
|
|
|
|
|
const component = match[1]
|
|
|
|
|
const body = match[2]
|
|
|
|
|
const properties = [...body.matchAll(/^\s*([A-Z][A-Za-z0-9]+)\s*:/gm)].map((m) => m[1])
|
|
|
|
|
const assignments = [
|
|
|
|
|
...source.matchAll(new RegExp(`\\b${component}\\.([A-Z][A-Za-z0-9]+)\\s*=`, 'g'))
|
|
|
|
|
].map((m) => m[1])
|
|
|
|
|
|
|
|
|
|
return { component, properties, assignments }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('Eidos component API contract', () => {
|
|
|
|
|
it('keeps component barrels on the disciplined option C surface', () => {
|
|
|
|
|
const violations: string[] = []
|
|
|
|
|
|
|
|
|
|
for (const component of readComponentDirs()) {
|
test(eidos): triage the 15 long-red suite failures — 289/289 green, check 61→59
Accumulated test debt (the eidos suite had been red across several feature
landings — THEME-SYS-1 named z-index, palette-first static donor library,
spin-field extraction, icon stroke-map refinement — none of them regressions;
baselined against HEAD). Each triaged stale-vs-bug by confirming current
behavior in code before touching a line.
Stale tests (code correct/improved, assertion not updated):
- z-index '76' -> 'var(--z-index-overlay-tooltip)' (the code is guarded by
THEME-SYS-1 in contracts.test.ts — the raw integer is forbidden).
- frost '80%' -> 'var(--depth-overlay-translucency, 80%)' (parameterized).
- C7 size-bundle coordinate ('--control-height-md' -> '--size-md-control-height').
- icon visual-stroke ratios 1.5->1.125, 1->0.923 (stroke map md 2->1.5, xxl
icon 48->52px; the arithmetic follows).
- delegate/CSS-only purple-9: the slim foundation legitimately emits the
role-referenced donor scales (purple=primary) as overridable :root defaults;
the real delegation guarantee is 'no baked [data-theme] block', which the
assertion now checks.
Test over-flagging (fixed the test, not the code):
- CSS-only shared layer: spin-field (no wrapper/index) excluded from the
wrapper/barrel/namespace contracts (CSS_ONLY_LAYERS).
- soma-runtime vars: 'var(--x, fallback)' is runtime-optional (the STUMBLES #7
soma->eidos surface) — only bare 'var(--x)' requires a recipe declaration.
- orphan check made corpus-wide: composition means a sibling consumes a
component's tokens (date-picker/month-grid consume calendar-control-*).
- natural-time-picker fixed-tone sky colors documented-exempt (Words-rail
doctrine).
- dialog drift-detector converted to a synthetic fixture (dialog.css no longer
ships the dead rule it self-tested against).
Real code fixes:
- declare '--pin-input-cell-readonly-bg' (I consumed it in C2b without
declaring it).
- remove 5 truly-dead tokens (drawer content-bg + 4 color-field
format-select/segment — 0 consumers anywhere in eidos/soma/web).
- clamp time-picker's 'size' to PopoverSize before forwarding (xs->sm):
TimePickerSize has xs, PopoverSize doesn't — a latent type bug my recipe
change surfaced via a complexity-budget flip. The fix dropped check 61->59,
also clearing navigation-menu-content of the same class.
card-group flagged as a REAL gap (KNOWN_MISSING_MORFO): it ships parts + chrome
but no morfo (morfo-first violation) — tracked, not silenced.
Verified: vitest src/uix/eidos 23 files / 289 tests green; npm run check 59
(better than the 61 baseline); component:audit 0 BROKEN.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
if (CSS_ONLY_LAYERS.has(component)) continue
|
|
|
|
|
const source = readIndexSource(component)
|
|
|
|
|
|
|
|
|
|
if (/\bObject\.assign\s*\(/.test(source)) {
|
|
|
|
|
violations.push(`${component}: Object.assign`)
|
|
|
|
|
}
|
|
|
|
|
if (/export\s+(?:type\s+)?\{[^}]*\bProvider\b[^}]*\}/s.test(source)) {
|
|
|
|
|
violations.push(`${component}: public Provider export`)
|
|
|
|
|
}
|
|
|
|
|
if (/from\s+['"]\.\/.*provider\.svelte['"]/.test(source)) {
|
|
|
|
|
violations.push(`${component}: provider component import`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expect(violations).toEqual([])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('keeps removed flat API markers out of component source', () => {
|
|
|
|
|
const violations = readComponentSourceFiles()
|
|
|
|
|
.filter(({ source }) => /\bdata-[a-z0-9-]+-flat\b/.test(source))
|
|
|
|
|
.map(({ path }) => path)
|
|
|
|
|
|
|
|
|
|
expect(violations).toEqual([])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('keeps component source free of Soma-prefixed runtime contracts', () => {
|
|
|
|
|
const violations = readComponentSourceFiles()
|
|
|
|
|
.filter(({ source }) => /\bdata-soma-|--soma-/.test(source))
|
|
|
|
|
.map(({ path }) => path)
|
|
|
|
|
|
|
|
|
|
expect(violations).toEqual([])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('keeps every public Eidos component rooted at {component}.svelte', () => {
|
|
|
|
|
const missingRoots = readComponentDirs()
|
|
|
|
|
.filter((component) => !ROOT_FILE_EXCEPTIONS.has(component))
|
test(eidos): triage the 15 long-red suite failures — 289/289 green, check 61→59
Accumulated test debt (the eidos suite had been red across several feature
landings — THEME-SYS-1 named z-index, palette-first static donor library,
spin-field extraction, icon stroke-map refinement — none of them regressions;
baselined against HEAD). Each triaged stale-vs-bug by confirming current
behavior in code before touching a line.
Stale tests (code correct/improved, assertion not updated):
- z-index '76' -> 'var(--z-index-overlay-tooltip)' (the code is guarded by
THEME-SYS-1 in contracts.test.ts — the raw integer is forbidden).
- frost '80%' -> 'var(--depth-overlay-translucency, 80%)' (parameterized).
- C7 size-bundle coordinate ('--control-height-md' -> '--size-md-control-height').
- icon visual-stroke ratios 1.5->1.125, 1->0.923 (stroke map md 2->1.5, xxl
icon 48->52px; the arithmetic follows).
- delegate/CSS-only purple-9: the slim foundation legitimately emits the
role-referenced donor scales (purple=primary) as overridable :root defaults;
the real delegation guarantee is 'no baked [data-theme] block', which the
assertion now checks.
Test over-flagging (fixed the test, not the code):
- CSS-only shared layer: spin-field (no wrapper/index) excluded from the
wrapper/barrel/namespace contracts (CSS_ONLY_LAYERS).
- soma-runtime vars: 'var(--x, fallback)' is runtime-optional (the STUMBLES #7
soma->eidos surface) — only bare 'var(--x)' requires a recipe declaration.
- orphan check made corpus-wide: composition means a sibling consumes a
component's tokens (date-picker/month-grid consume calendar-control-*).
- natural-time-picker fixed-tone sky colors documented-exempt (Words-rail
doctrine).
- dialog drift-detector converted to a synthetic fixture (dialog.css no longer
ships the dead rule it self-tested against).
Real code fixes:
- declare '--pin-input-cell-readonly-bg' (I consumed it in C2b without
declaring it).
- remove 5 truly-dead tokens (drawer content-bg + 4 color-field
format-select/segment — 0 consumers anywhere in eidos/soma/web).
- clamp time-picker's 'size' to PopoverSize before forwarding (xs->sm):
TimePickerSize has xs, PopoverSize doesn't — a latent type bug my recipe
change surfaced via a complexity-budget flip. The fix dropped check 61->59,
also clearing navigation-menu-content of the same class.
card-group flagged as a REAL gap (KNOWN_MISSING_MORFO): it ships parts + chrome
but no morfo (morfo-first violation) — tracked, not silenced.
Verified: vitest src/uix/eidos 23 files / 289 tests green; npm run check 59
(better than the 61 baseline); component:audit 0 BROKEN.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
.filter((component) => !CSS_ONLY_LAYERS.has(component))
|
|
|
|
|
.filter((component) => !existsSync(join(COMPONENTS_DIR, component, `${component}.svelte`)))
|
|
|
|
|
|
|
|
|
|
expect(missingRoots).toEqual([])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('keeps namespace type members and explicit assignments in sync', () => {
|
|
|
|
|
const violations: string[] = []
|
|
|
|
|
|
|
|
|
|
for (const component of readComponentDirs()) {
|
test(eidos): triage the 15 long-red suite failures — 289/289 green, check 61→59
Accumulated test debt (the eidos suite had been red across several feature
landings — THEME-SYS-1 named z-index, palette-first static donor library,
spin-field extraction, icon stroke-map refinement — none of them regressions;
baselined against HEAD). Each triaged stale-vs-bug by confirming current
behavior in code before touching a line.
Stale tests (code correct/improved, assertion not updated):
- z-index '76' -> 'var(--z-index-overlay-tooltip)' (the code is guarded by
THEME-SYS-1 in contracts.test.ts — the raw integer is forbidden).
- frost '80%' -> 'var(--depth-overlay-translucency, 80%)' (parameterized).
- C7 size-bundle coordinate ('--control-height-md' -> '--size-md-control-height').
- icon visual-stroke ratios 1.5->1.125, 1->0.923 (stroke map md 2->1.5, xxl
icon 48->52px; the arithmetic follows).
- delegate/CSS-only purple-9: the slim foundation legitimately emits the
role-referenced donor scales (purple=primary) as overridable :root defaults;
the real delegation guarantee is 'no baked [data-theme] block', which the
assertion now checks.
Test over-flagging (fixed the test, not the code):
- CSS-only shared layer: spin-field (no wrapper/index) excluded from the
wrapper/barrel/namespace contracts (CSS_ONLY_LAYERS).
- soma-runtime vars: 'var(--x, fallback)' is runtime-optional (the STUMBLES #7
soma->eidos surface) — only bare 'var(--x)' requires a recipe declaration.
- orphan check made corpus-wide: composition means a sibling consumes a
component's tokens (date-picker/month-grid consume calendar-control-*).
- natural-time-picker fixed-tone sky colors documented-exempt (Words-rail
doctrine).
- dialog drift-detector converted to a synthetic fixture (dialog.css no longer
ships the dead rule it self-tested against).
Real code fixes:
- declare '--pin-input-cell-readonly-bg' (I consumed it in C2b without
declaring it).
- remove 5 truly-dead tokens (drawer content-bg + 4 color-field
format-select/segment — 0 consumers anywhere in eidos/soma/web).
- clamp time-picker's 'size' to PopoverSize before forwarding (xs->sm):
TimePickerSize has xs, PopoverSize doesn't — a latent type bug my recipe
change surfaced via a complexity-budget flip. The fix dropped check 61->59,
also clearing navigation-menu-content of the same class.
card-group flagged as a REAL gap (KNOWN_MISSING_MORFO): it ships parts + chrome
but no morfo (morfo-first violation) — tracked, not silenced.
Verified: vitest src/uix/eidos 23 files / 289 tests green; npm run check 59
(better than the 61 baseline); component:audit 0 BROKEN.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
if (CSS_ONLY_LAYERS.has(component)) continue
|
|
|
|
|
const contract = readNamespaceContract(readIndexSource(component))
|
|
|
|
|
if (!contract) continue
|
|
|
|
|
|
|
|
|
|
const declared = new Set(contract.properties)
|
|
|
|
|
const assigned = new Set(contract.assignments)
|
|
|
|
|
for (const property of declared) {
|
|
|
|
|
if (!assigned.has(property)) {
|
|
|
|
|
violations.push(`${component}: ${contract.component}.${property} declared but not assigned`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (const property of assigned) {
|
|
|
|
|
if (!declared.has(property)) {
|
|
|
|
|
violations.push(`${component}: ${contract.component}.${property} assigned but not declared`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expect(violations).toEqual([])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('keeps component wrappers free of legacy lifecycle and scoped styles', () => {
|
|
|
|
|
const violations: string[] = []
|
|
|
|
|
|
|
|
|
|
for (const component of readComponentDirs()) {
|
|
|
|
|
const dir = join(COMPONENTS_DIR, component)
|
|
|
|
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
|
|
|
if (!entry.isFile() || !entry.name.endsWith('.svelte')) continue
|
|
|
|
|
const path = join(dir, entry.name)
|
|
|
|
|
const source = readFileSync(path, 'utf8')
|
|
|
|
|
|
|
|
|
|
if (/\bonMount\b/.test(source)) violations.push(`${component}/${entry.name}: onMount`)
|
|
|
|
|
if (/<style(?:\s|>)/.test(source)) violations.push(`${component}/${entry.name}: <style>`)
|
|
|
|
|
if (/\bdata-soma-/.test(source)) {
|
|
|
|
|
violations.push(`${component}/${entry.name}: data-soma-*`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expect(violations).toEqual([])
|
|
|
|
|
})
|
|
|
|
|
})
|