From 1dd9d62eae7bf8ec99e41c464c9d0b6567e69db9 Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 16 May 2026 04:05:47 +0200 Subject: [PATCH] Refactor Sium domain schema helpers --- continue.md | 82 ++++++++++++++++++++++++++++++++- src/arts/sium/types/_helpers.ts | 70 ++++++++++++++++++++++++++++ src/arts/sium/types/color.ts | 23 ++------- src/arts/sium/types/date.ts | 45 ++++-------------- src/arts/sium/types/time.ts | 47 ++++--------------- 5 files changed, 176 insertions(+), 91 deletions(-) diff --git a/continue.md b/continue.md index 38c31d36a..32b1e6119 100644 --- a/continue.md +++ b/continue.md @@ -1,6 +1,86 @@ # Continue -Fecha de corte: 2026-05-15. Rama: `active-uix`. +Fecha de corte: 2026-05-16. Rama: `active-uix`. + +Actualizacion 2026-05-16: + +- Soma queda en estado funcionalmente cubierto a nivel provider/runtime: + `npm run check` -> 0 errores / 0 warnings, `npm run test` -> 233 archivos, + 2242 tests OK, y la guardia de `*-provider.svelte.ts` sin test directo + devuelve `NO_MISSING_PROVIDER_TESTS`. +- Lo que falta para cerrar Soma de verdad no es mas cobertura de providers, sino + las capas exteriores: + 1. Auditar wrappers Svelte publicos (`*.svelte`) frente a sus providers: + props bindables, snippets, forwarding de attrs, eventos y defaults. + 2. Auditar barrels publicos (`index.ts`, `exports.ts`) para que no expongan + clases runtime internas, reexports muertos ni fachadas sobre `$libs`. + 3. Validar rutas `/uix/components/*` con navegador: sin `pageerror`, sin + errores de consola, sin warnings de traduccion inesperados y con flujos + basicos interactivos. + 4. Sincronizar inventario/documentacion Soma con el estado real: componente + activo, visual-native, wrapper publico, provider runtime y tests. + 5. Mantener la doctrina DOM actual: escrituras gestionadas, listeners + globales, focus/scroll imperativos y portal/query global pasan por + `ActiveDom`; lecturas locales (`getBoundingClientRect`, `contains`, + `closest`, `scrollTop`, etc.) siguen siendo responsabilidad del componente. + 6. Dejar optimizaciones de rendimiento para despues de cerrar API publica y + wrappers: virtualizacion compartida, datetime compartido y micro-churn del + runtime solo si aparecen datos reales o tests que lo justifiquen. +- Auditoria `src/arts/sium/kimi-audit.md` leida y evaluada. Mi veredicto: + buena auditoria, bastante alineada con el rol real de Sium como codec + + validador + introspeccion para Active, pero con dos matices: + - La prioridad critica correcta es integridad de datos, no ampliar Sium hasta + parecerse a Zod. + - Algunos fixes descritos como "1 linea" no lo son si queremos mantener + contratos `decodeSync`, `~sium` y `Form.AutoFields` coherentes. +- Sium P0 recomendado, sin cambios breaking: + 1. Corregir `isPlainObjectRecord(...)` para aceptar solo objetos planos. No + usar `value.constructor === Object` como unica regla: preferir + `Object.getPrototypeOf(value) === Object.prototype || null` para no romper + objetos null-prototype y reducir falsos positivos. + 2. Mejorar `email()` con una regex conservadora de producto, no RFC completa: + dominio sin puntos dobles y TLD practico de 2+ caracteres. + 3. Cachear `regex(pattern)` sin mutar `lastIndex`. Si el pattern trae `g` o + `y`, clonar sin flags mutables o resetear antes de probar. + 4. Alinear README con `ctx.rootValue`: hoy solo es seguro en refines de objeto + ya decodificado. Las validaciones cross-field deben vivir en el object + refine, no en field refines. + 5. Revisar `optional/nullable/defaulted` con schema interno async. El problema + es real, pero la solucion no es mover un `if`: `createSchema.decodeSync` + lanza por `spec.async === true` antes de entrar en `decodeImpl`. Hace falta + una decision de runtime (`syncShortCircuit`, decodeSync custom por wrapper, + o semantica documentada de "schema async siempre async"). +- Sium P1 coordinado con consumidores: + 1. No cambiar `lazy()` a `kind: 'lazy'` hasta que + `src/uix/soma/components/form/components/form-auto-fields.svelte` sepa + renderizar/posponer ese kind. Hoy `SchemaKind` no incluye `lazy`. + 2. Anadir refinements pequenos y utiles: `finite`, `safe`, `positive`, + `nonempty`, `uuid` solo si hay uso inmediato en Active/Form. + 3. Anadir utilidades de objeto (`partial`, `pick`, `omit`, `extend`) y + estructuras (`tuple`, `record`) cuando AutoFields y `~sium.shape` tengan + contrato de introspeccion claro. + 4. Considerar `preprocess` y `coerce` como features de formularios, no como + ampliacion generalista. +- Sium P2 interno: + 1. Cerrado: `types/_helpers.ts` incorpora `createDomainValueSchema(...)` y + `createDomainRangeSchema(...)`. `colorValue`, `dateValue`, `timeValue`, + `dateRange` y `timeRange` comparten ahora el patron identity codec + + guard + widget + range order sin mover esa logica a `core`. + 2. Unificar paths sync/async de combinadores solo con tests amplios; es zona + sensible por `SiumAsyncSchemaError`. + 3. Documentar limites de `lazy()` con inputs ciclicos o anadir proteccion + `WeakSet` si empieza a recibir datos no confiables. +- Validacion Sium domain factory: + - `npx vitest run src/arts/sium/test/color.test.ts src/arts/sium/test/date.test.ts src/arts/sium/test/time.test.ts src/arts/sium/test/standard-schema.test.ts` -> 4 archivos, 122 tests OK. + - `npm run check` -> 0 errores, 0 warnings. +- Criterio de cierre para Sium antes de tocar consumidores UI: + - Tests focales de `src/arts/sium` verdes. + - `npm run check` sin warnings. + - README y tipos `SchemaKind`/`SiumShape` sincronizados. + - `Form.AutoFields` actualizado antes de cualquier kind nuevo visible en + `~sium`. + - Ninguna feature nueva entra si solo duplica Zod/Valibot sin consumidor + propio de Active. Actualizacion 2026-05-15: diff --git a/src/arts/sium/types/_helpers.ts b/src/arts/sium/types/_helpers.ts index ced9201fc..7d24bf1f6 100644 --- a/src/arts/sium/types/_helpers.ts +++ b/src/arts/sium/types/_helpers.ts @@ -1,4 +1,64 @@ +import { SiumValidationError } from '../errors'; +import { createSchema } from '../core/schema'; import { issue } from '../core/issue'; +import type { MetaAnnotations, Schema, SchemaKind } from '../core/types'; + +type DomainSchemaOptions = { + kind: SchemaKind; + expected: string; + guard: (value: unknown) => value is T; + widget: string; + meta?: MetaAnnotations; +}; + +type DomainRangeSchemaOptions = DomainSchemaOptions & { + isOrdered: (value: T) => boolean; +}; + +/** + * Creates an identity codec for domain values validated by a type guard. + * + * Kept under `types/` because this is not a generic sium primitive: it is a + * small factory for Active domain values such as color, date, and time. + */ +export function createDomainValueSchema(opts: DomainSchemaOptions): Schema { + return createSchema({ + kind: opts.kind, + decodeImpl: (input) => { + if (!opts.guard(input)) { + throw createDomainTypeError(opts.expected, input); + } + return input; + }, + encode: (value) => value, + meta: { ...opts.meta, widget: opts.widget } + }); +} + +/** + * Creates an identity codec for ordered domain ranges. + * + * The range guard owns the structural shape (`start`/`end` optionality and + * item type). This helper only centralizes the shared type + order errors. + */ +export function createDomainRangeSchema(opts: DomainRangeSchemaOptions): Schema { + return createSchema({ + kind: opts.kind, + decodeImpl: (input) => { + if (!opts.guard(input)) { + throw createDomainTypeError(opts.expected, input); + } + + if (!opts.isOrdered(input)) { + throw new SiumValidationError([createRangeOrderIssue()]); + } + + return input; + }, + encode: (value) => value, + meta: { ...opts.meta, widget: opts.widget } + }); +} /** * Range issue raised when `start` is strictly after `end`. Shared by date and @@ -11,3 +71,13 @@ export function createRangeOrderIssue() { params: {} }); } + +function createDomainTypeError(expected: string, input: unknown): SiumValidationError { + return new SiumValidationError([ + issue({ + code: 'type', + message: '#?sium.errors.type|Expected {{expected}} but received {{actual}}', + params: { expected, actual: String(input) } + }) + ]); +} diff --git a/src/arts/sium/types/color.ts b/src/arts/sium/types/color.ts index 0e25be54e..4c19e07bf 100644 --- a/src/arts/sium/types/color.ts +++ b/src/arts/sium/types/color.ts @@ -3,30 +3,17 @@ import { isColorValue } from '$libs/color'; import { number } from '../core'; import { meta, pipe } from '../core'; import { integer, max, min } from '../core'; -import { createSchema } from '../core/schema'; -import { SiumValidationError } from '../core'; -import { issue } from '../core/issue'; +import { createDomainValueSchema } from './_helpers'; /** * Validates a composite color value with hex, rgb, hsl, and hsv representations. */ export function colorValue() { - return createSchema({ + return createDomainValueSchema({ kind: 'color', - decodeImpl: (input) => { - if (!isColorValue(input)) { - throw new SiumValidationError([ - issue({ - code: 'type', - message: '#?sium.errors.type|Expected {{expected}} but received {{actual}}', - params: { expected: 'ColorValue', actual: String(input) } - }) - ]); - } - return input; - }, - encode: (value) => value, - meta: { widget: 'color-picker' } + expected: 'ColorValue', + guard: isColorValue, + widget: 'color-picker' }); } diff --git a/src/arts/sium/types/date.ts b/src/arts/sium/types/date.ts index 9d291cf5e..6e1cc5d7b 100644 --- a/src/arts/sium/types/date.ts +++ b/src/arts/sium/types/date.ts @@ -6,8 +6,7 @@ import { integer, max, min } from '../core'; import { createSchema } from '../core/schema'; import { SiumValidationError } from '../core'; import { issue } from '../core/issue'; -import { createRangeOrderIssue } from './_helpers'; - +import { createDomainRangeSchema, createDomainValueSchema } from './_helpers'; function isDateValue(value: unknown): value is DateValue { if (value == null || typeof value !== 'object') return false; @@ -40,22 +39,11 @@ export function dateValue(opts?: { calendar?: 'gregorian' | 'buddhist' | string; granularity?: 'day' | 'hour' | 'minute' | 'second'; }) { - return createSchema({ + return createDomainValueSchema({ kind: 'date', - decodeImpl: (input) => { - if (!isDateValue(input)) { - throw new SiumValidationError([ - issue({ - code: 'type', - message: '#?sium.errors.type|Expected {{expected}} but received {{actual}}', - params: { expected: 'DateValue', actual: String(input) } - }) - ]); - } - return input; - }, - encode: (value) => value, - meta: { widget: 'date-field' } + expected: 'DateValue', + guard: isDateValue, + widget: 'date-field' }); } @@ -102,25 +90,12 @@ export function coerceDate() { * Validates a date range object with optional start and end DateValues. */ export function dateRange() { - return createSchema({ + return createDomainRangeSchema({ kind: 'date-range', - decodeImpl: (input) => { - if (!isDateRange(input)) { - throw new SiumValidationError([ - issue({ - code: 'type', - message: '#?sium.errors.type|Expected {{expected}} but received {{actual}}', - params: { expected: 'DateRange', actual: String(input) } - }) - ]); - } - if (!isOrderedDateRange(input)) { - throw new SiumValidationError([createRangeOrderIssue()]); - } - return input; - }, - encode: (value) => value, - meta: { widget: 'date-range-field' } + expected: 'DateRange', + guard: isDateRange, + isOrdered: isOrderedDateRange, + widget: 'date-range-field' }); } diff --git a/src/arts/sium/types/time.ts b/src/arts/sium/types/time.ts index 9c795aa2c..8dfddf63d 100644 --- a/src/arts/sium/types/time.ts +++ b/src/arts/sium/types/time.ts @@ -2,10 +2,7 @@ import type { TimeValue, TimeRange } from '$libs/days'; import { enumOf, number } from '../core/primitives'; import { meta, pipe } from '../core/pipe'; import { integer, max, min } from '../core/refines'; -import { createSchema } from '../core/schema'; -import { SiumValidationError } from '../errors'; -import { issue } from '../core/issue'; -import { createRangeOrderIssue } from './_helpers'; +import { createDomainRangeSchema, createDomainValueSchema } from './_helpers'; function isTimeValue(value: unknown): value is TimeValue { if (value == null || typeof value !== 'object') return false; @@ -43,22 +40,11 @@ export function timeValue(opts?: { granularity?: 'hour' | 'minute' | 'second'; hourCycle?: 12 | 24; }) { - return createSchema({ + return createDomainValueSchema({ kind: 'time', - decodeImpl: (input) => { - if (!isTimeValue(input)) { - throw new SiumValidationError([ - issue({ - code: 'type', - message: '#?sium.errors.type|Expected {{expected}} but received {{actual}}', - params: { expected: 'TimeValue', actual: String(input) } - }) - ]); - } - return input; - }, - encode: (value) => value, - meta: { widget: 'time-field' } + expected: 'TimeValue', + guard: isTimeValue, + widget: 'time-field' }); } @@ -66,25 +52,12 @@ export function timeValue(opts?: { * Validates a time range object with optional start and end TimeValues. */ export function timeRange() { - return createSchema({ + return createDomainRangeSchema({ kind: 'time-range', - decodeImpl: (input) => { - if (!isTimeRange(input)) { - throw new SiumValidationError([ - issue({ - code: 'type', - message: '#?sium.errors.type|Expected {{expected}} but received {{actual}}', - params: { expected: 'TimeRange', actual: String(input) } - }) - ]); - } - if (!isOrderedTimeRange(input)) { - throw new SiumValidationError([createRangeOrderIssue()]); - } - return input; - }, - encode: (value) => value, - meta: { widget: 'time-range-field' } + expected: 'TimeRange', + guard: isTimeRange, + isOrdered: isOrderedTimeRange, + widget: 'time-range-field' }); }