Address Eidos audit findings

active-uix
dev 5 months ago
parent f9591c5216
commit 306c371aef

@ -101,6 +101,33 @@ Actualizacion Eidos recipes 2026-05-16:
- Revisar tokens declarados pero no usados para separar: reserva doctrinal
intencional vs residuo de CSS legacy.
Actualizacion Eidos auditoria Kimi 2026-05-16:
- Hallazgos corregidos:
- `tabs-indicator.svelte` pasa de `onMount` a `$effect` con cleanup ligado
al `ref`. Nota: `onMount` no es incompatible con Svelte 5, pero `$effect`
encaja mejor aqui porque el observer depende del elemento enlazado.
- Icono `folder-root.svelte` exportado como `FolderRoot` y catalogado como
`folder-root` / `FolderRoot`; se elimina el falso `FolderProvider`.
- Comentario de `toggle.svelte` corregido: Eidos expone `<Toggle>`, no
`<Toggle.Provider>`.
- Helpers repetidos de `eidos/lib` centralizados en `utils.ts`:
`clonePlain`, `isPlainRecord`, `hasOwn`, `toKebab`.
- `isPlainRecord` queda unificado como objeto literal o null-prototype; no
acepta arrays ni instancias de clase.
- Hallazgos evaluados pero no aplicados:
- `dialog.svelte` y `drawer.svelte` no necesitan `data-size` en el root:
sus props visuales viven en `Content`; `variant`/`direction` son headless
y ya los proyecta Soma/Morfo.
- `icon.svelte` mantiene `class` como prop DOM idiomatica; cambiar a
`className` seria menos natural en Svelte y no resuelve un bug real.
- `StyleBlockManager`, builder de recipes y action separada para Tabs
quedan como refactors posibles, no P0/P1.
- Validacion:
- `npm run check` -> 0 errores, 0 warnings.
- `npx vitest run src/uix/eidos` -> 4 archivos, 56 tests OK.
- `npm run test` -> 233 archivos, 2270 tests OK.
Actualizacion 2026-05-16:
- Barrido de wrappers publicos Soma frente a providers cerrado:

@ -950,7 +950,7 @@ export const iconCategories: IconCategory[] = [
{ name: 'folder-dot', pascal: 'FolderDot' },
{ name: 'folder-kanban', pascal: 'FolderKanban' },
{ name: 'folder-open-dot', pascal: 'FolderOpenDot' },
{ name: 'folder-provider', pascal: 'FolderProvider' },
{ name: 'folder-root', pascal: 'FolderRoot' },
{ name: 'form', pascal: 'Form' },
{ name: 'gallery-horizontal', pascal: 'GalleryHorizontal' },
{ name: 'gallery-horizontal-end', pascal: 'GalleryHorizontalEnd' },
@ -1425,7 +1425,7 @@ export const iconCategories: IconCategory[] = [
{ name: 'folder-output', pascal: 'FolderOutput' },
{ name: 'folder-pen', pascal: 'FolderPen' },
{ name: 'folder-plus', pascal: 'FolderPlus' },
{ name: 'folder-provider', pascal: 'FolderProvider' },
{ name: 'folder-root', pascal: 'FolderRoot' },
{ name: 'folder-search', pascal: 'FolderSearch' },
{ name: 'folder-search-2', pascal: 'FolderSearch2' },
{ name: 'folder-symlink', pascal: 'FolderSymlink' },

@ -706,7 +706,7 @@ export { default as FolderOpenDot } from './lib/folder-open-dot.svelte';
export { default as FolderOutput } from './lib/folder-output.svelte';
export { default as FolderPen } from './lib/folder-pen.svelte';
export { default as FolderPlus } from './lib/folder-plus.svelte';
export { default as FolderProvider } from './lib/folder-root.svelte';
export { default as FolderRoot } from './lib/folder-root.svelte';
export { default as FolderSearch } from './lib/folder-search.svelte';
export { default as FolderSearch2 } from './lib/folder-search-2.svelte';
export { default as FolderSymlink } from './lib/folder-symlink.svelte';

@ -15,7 +15,6 @@
* `prefers-reduced-motion: reduce` by skipping the transition (still
* snaps to the active trigger).
*/
import { onMount } from 'svelte';
import { composeInlineStyle } from '$uix/eidos/lib/style';
import * as Tabs from '$soma/components/tabs';
import type { TabsIndicatorProps } from './types';
@ -54,7 +53,7 @@
);
const mergedStyle = $derived(composeInlineStyle(style, geometryStyle));
onMount(() => {
$effect(() => {
const el = ref;
if (!el) return;
const list = el.parentElement;
@ -78,7 +77,10 @@
return () => {
mo.disconnect();
ro.disconnect();
if (rafId !== null) cancelAnimationFrame(rafId);
if (rafId !== null) {
cancelAnimationFrame(rafId);
rafId = null;
}
};
});
</script>

@ -1,6 +1,6 @@
<script lang="ts">
/**
* Eidos `<Toggle.Provider>` — visual wrapper over the Soma.
* Eidos `<Toggle>` — visual wrapper over the Soma.
*
* The Soma owns `pressed` state, `data-state`,
* `aria-pressed`, and the `commit-toggle` perceptual event. Eidos adds

@ -35,6 +35,7 @@ import {
serializeEidosConfigDocument as serializeEidosConfigDocumentEnvelope,
type EidosConfigDocument
} from './persistence'
import { clonePlain, hasOwn, isPlainRecord } from './utils'
import { EidosConfigValidationError } from '../errors'
const COLOR_ROLE_SET = new Set<string>(COLOR_ROLES)
@ -674,10 +675,6 @@ function isTextMetric(value: unknown): value is TextMetric {
)
}
function isPlainRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
function resolveRoleScaleName(
roleDef: string | ColorRoleDefinition | undefined
): string | undefined {
@ -685,15 +682,6 @@ function resolveRoleScaleName(
return roleDef?.scale
}
function hasOwn<T extends object>(value: T, key: PropertyKey): boolean {
return Object.prototype.hasOwnProperty.call(value, key)
}
function isCssTokenSegment(value: string): boolean {
return /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(value)
}
function clonePlain<T>(value: T): T {
if (typeof structuredClone === 'function') return structuredClone(value)
return JSON.parse(JSON.stringify(value)) as T
}

@ -14,6 +14,7 @@ import {
type SizePrimitiveSet,
type TypographyPrimitiveSet
} from './config-types'
import { toKebab } from './utils'
export function createEidosCssContract(options: EidosConfig): EidosCssContract {
const staticTokens: EidosCssContractToken[] = []
@ -581,7 +582,3 @@ function getKnownColorScaleNames(options: EidosConfig): readonly string[] {
return [...names]
}
function toKebab(value: string): string {
return value.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)
}

@ -1,4 +1,5 @@
import type { EidosConfig } from './config-types'
import { clonePlain, isPlainRecord } from './utils'
export type EidosConfigPatch = DeepPartial<EidosConfig>
@ -38,16 +39,3 @@ function mergePlain(base: unknown, patch: unknown): unknown {
return output
}
function isPlainRecord(value: unknown): value is Record<string, unknown> {
return (
typeof value === 'object' &&
value !== null &&
!Array.isArray(value) &&
Object.getPrototypeOf(value) === Object.prototype
)
}
function clonePlain<T>(value: T): T {
if (typeof structuredClone === 'function') return structuredClone(value)
return JSON.parse(JSON.stringify(value)) as T
}

@ -1,4 +1,5 @@
import type { EidosConfig } from './config-types'
import { clonePlain, isPlainRecord } from './utils'
import { EidosConfigDocumentError } from '../errors'
export const EIDOS_CONFIG_DOCUMENT_KIND = 'uix.eidos-config' as const
@ -74,16 +75,3 @@ export function isEidosConfigDocumentLike(input: unknown): input is Record<strin
return isPlainRecord(input) && input.kind === EIDOS_CONFIG_DOCUMENT_KIND
}
function isPlainRecord(value: unknown): value is Record<string, unknown> {
return (
typeof value === 'object' &&
value !== null &&
!Array.isArray(value) &&
Object.getPrototypeOf(value) === Object.prototype
)
}
function clonePlain<T>(value: T): T {
if (typeof structuredClone === 'function') return structuredClone(value)
return JSON.parse(JSON.stringify(value)) as T
}

@ -30,6 +30,7 @@ import {
type TypographyPrimitiveSet
} from './config-types'
import { createEidosCssContract } from './contract'
import { toKebab } from './utils'
import { EidosCssVariableError, EidosThemeNotFoundError } from '../errors'
export interface RenderThemeCssOptions {
@ -639,10 +640,6 @@ function formatFontName(name: string): string {
return `'${name.replace(/'/g, "\\'")}'`
}
function toKebab(value: string): string {
return value.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)
}
function getContractVariableNames(
contract: EidosCssContract | readonly EidosCssContractToken[]
): Set<string> {

@ -0,0 +1,18 @@
export function isPlainRecord(value: unknown): value is Record<string, unknown> {
if (typeof value !== 'object' || value === null || Array.isArray(value)) return false
const proto = Object.getPrototypeOf(value)
return proto === Object.prototype || proto === null
}
export function hasOwn<T extends object>(value: T, key: PropertyKey): boolean {
return Object.prototype.hasOwnProperty.call(value, key)
}
export function toKebab(value: string): string {
return value.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)
}
export function clonePlain<T>(value: T): T {
if (typeof structuredClone === 'function') return structuredClone(value)
return JSON.parse(JSON.stringify(value)) as T
}
Loading…
Cancel
Save

Powered by TurnKey Linux.