diff --git a/continue.md b/continue.md
index c1b94bf3a..b45385391 100644
--- a/continue.md
+++ b/continue.md
@@ -49,6 +49,39 @@ Arranque rapido para mañana:
6. Mantener fuera de commits los logs sueltos (`.codex-vite-*`,
`debug.log`) salvo que el usuario pida conservarlos.
+Actualizacion Eidos 2026-05-16:
+
+- Primera pasada de componentes Option C ejecutada:
+ - Barrels multi-part normalizados a asignacion explicita per-property
+ (`const Drawer = DrawerRoot as DrawerNamespace; Drawer.Trigger = Trigger;`)
+ y sin `Object.assign`.
+ - Tipos root visuales renombrados a `AccordionProps`, `DrawerProps`,
+ `DialogProps`, `TabsProps`, etc. Eidos ya no define `XxxProviderProps`;
+ `ProviderProps` solo aparece como alias importado desde Soma cuando la
+ superficie headless lo llama asi.
+ - `src/uix/eidos/components/README.md` actualizado con la convencion real.
+ - `Toaster` deja de usar un alias interno `Provider` para no reintroducir el
+ modelo mental retirado.
+ - Aniadido `src/uix/eidos/lib/style.ts` con `composeInlineStyle(...)` y
+ aplicado a wrappers que mezclan estilo del consumidor con custom properties
+ de receta: overlay de Dialog/Drawer, Toast.Viewport, Checkbox.Group,
+ RadioGroup, Tabs.Indicator, Icon y primitives SVG.
+- Validacion de esta pasada:
+ - `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.
+ - SSR smoke de `web/routes/uix/components/{accordion,avatar,checkbox,
+ collapsible,dialog,drawer,popover,radio-group,switch,tabs,toast,toggle,
+ tooltip}` -> HTTP 200.
+- Siguiente foco recomendado en Eidos:
+ 1. Auditar recipes/CSS contra tokens generados por `ActiveEidos` y decidir
+ que CSS legacy queda como recipe por componente y que debe generarse.
+ 2. Revisar componentes visuales que anaden partes eidos-only (`Header`,
+ `Footer`, `Status`, `Main`, SVG primitives) para documentar si necesitan
+ morfo propio o si son recipe primitives privadas.
+ 3. Revisar tests de componentes Eidos: hoy hay cobertura del runtime Eidos,
+ pero no tests unitarios especificos para wrappers visuales.
+
Actualizacion 2026-05-16:
- Barrido de wrappers publicos Soma frente a providers cerrado:
diff --git a/src/uix/eidos/components/README.md b/src/uix/eidos/components/README.md
index d9616c9f0..3d90947cc 100644
--- a/src/uix/eidos/components/README.md
+++ b/src/uix/eidos/components/README.md
@@ -78,9 +78,9 @@ contexto headless, acepta los bindables del estado (`open`, `value`,
/**
* Eidos `` — root component. Wraps the Soma
* to set up the component context.
- */
+ */
import { Provider as SomaDrawerProvider } from '$soma/components/drawer';
- import type { DrawerProviderProps } from './types';
+ import type { DrawerProps } from './types';
let {
open = $bindable(false),
@@ -88,7 +88,7 @@ contexto headless, acepta los bindables del estado (`open`, `value`,
isDragging = $bindable(false),
children,
...rest
- }: DrawerProviderProps = $props();
+ }: DrawerProps = $props();
@@ -169,7 +169,7 @@ export { Drawer };
export default Drawer;
export type {
- DrawerProviderProps as DrawerProps,
+ DrawerProps,
DrawerTriggerProps as TriggerProps,
DrawerOverlayProps as OverlayProps,
DrawerContentProps as ContentProps
@@ -187,7 +187,7 @@ import type {
// …
} from '$soma/components/drawer';
-export type DrawerProviderProps = SomaDrawerProviderProps;
+export type DrawerProps = SomaDrawerProviderProps;
export type DrawerTriggerProps = SomaDrawerTriggerProps;
// Eidos añade props visuales que el recipe consume vía data-attrs
diff --git a/src/uix/eidos/components/accordion/accordion.svelte b/src/uix/eidos/components/accordion/accordion.svelte
index 06258fa2e..e9732e54d 100644
--- a/src/uix/eidos/components/accordion/accordion.svelte
+++ b/src/uix/eidos/components/accordion/accordion.svelte
@@ -18,7 +18,7 @@
*/
import * as SomaAccordion from '$soma/components/accordion';
import { ActiveEidos } from '$uix/eidos';
- import type { AccordionProviderProps } from './types';
+ import type { AccordionProps } from './types';
import { setAccordionEidosContext } from './accordion-context';
let {
@@ -27,7 +27,7 @@
variant = 'outline',
children,
...rest
- }: AccordionProviderProps = $props();
+ }: AccordionProps = $props();
const eidos = ActiveEidos.require();
const resolvedSize = $derived(typeof size === 'string' ? size : eidos.resolve(size, 'md'));
diff --git a/src/uix/eidos/components/accordion/index.ts b/src/uix/eidos/components/accordion/index.ts
index 461142386..eb5aef7b7 100644
--- a/src/uix/eidos/components/accordion/index.ts
+++ b/src/uix/eidos/components/accordion/index.ts
@@ -12,25 +12,33 @@
//
//
// `` IS the root (lives in accordion.svelte). Children
-// attached via Object.assign — bits-ui / shadcn-svelte pattern. No
-// Provider exported publicly; no flat-with-snippets invention.
+// are attached explicitly. No Provider exported publicly; no
+// flat-with-snippets invention.
import AccordionRoot from './accordion.svelte';
import Item from './accordion-item.svelte';
import Header from './accordion-header.svelte';
import Trigger from './accordion-trigger.svelte';
import Content from './accordion-content.svelte';
-export const Accordion = Object.assign(AccordionRoot, {
- Item,
- Header,
- Trigger,
- Content
-});
+type AccordionNamespace = typeof AccordionRoot & {
+ Item: typeof Item;
+ Header: typeof Header;
+ Trigger: typeof Trigger;
+ Content: typeof Content;
+};
+
+const Accordion = AccordionRoot as AccordionNamespace;
+Accordion.Item = Item;
+Accordion.Header = Header;
+Accordion.Trigger = Trigger;
+Accordion.Content = Content;
+
+export { Accordion };
export default Accordion;
export type {
- AccordionProviderProps as AccordionProps,
+ AccordionProps,
AccordionItemProps as ItemProps,
AccordionHeaderProps as HeaderProps,
AccordionTriggerProps as TriggerProps,
diff --git a/src/uix/eidos/components/accordion/types.ts b/src/uix/eidos/components/accordion/types.ts
index c2043b666..263064459 100644
--- a/src/uix/eidos/components/accordion/types.ts
+++ b/src/uix/eidos/components/accordion/types.ts
@@ -15,14 +15,14 @@ export type AccordionHeaderProps = SomaAccordionHeaderProps;
export type AccordionContentProps = SomaAccordionContentProps;
/**
- * Provider props in eidos accept `ResponsiveProp` for
+ * Root props in eidos accept `ResponsiveProp` for
* the `size` axis. The wrapper resolves it via `Eidos.resolve` and
* stamps `data-size`; headless never receives size/variant because those
* axes are visual-only. Variant stays single-valued — picking outline
* vs surface vs ghost on a per-breakpoint basis is not a use case worth
* exposing.
*/
-export type AccordionProviderProps = SomaAccordionProviderProps & {
+export type AccordionProps = SomaAccordionProviderProps & {
size?: ResponsiveProp;
variant?: AccordionVariant;
};
diff --git a/src/uix/eidos/components/avatar/avatar.svelte b/src/uix/eidos/components/avatar/avatar.svelte
index 6d138bb9d..2ce3cbe2a 100644
--- a/src/uix/eidos/components/avatar/avatar.svelte
+++ b/src/uix/eidos/components/avatar/avatar.svelte
@@ -9,7 +9,7 @@
* JD
*
*/
- import type { AvatarProviderProps } from './types';
+ import type { AvatarProps } from './types';
let {
size = 'md',
@@ -18,7 +18,7 @@
radius = 'full',
children,
...restProps
- }: AvatarProviderProps = $props();
+ }: AvatarProps = $props();
, 'children'> & {
+export type AvatarProps = Omit, 'children'> & {
/** Visual size token. @default 'md' */
size?: AvatarSize;
/** Visual variant. @default 'soft' */
diff --git a/src/uix/eidos/components/checkbox/checkbox-group.svelte b/src/uix/eidos/components/checkbox/checkbox-group.svelte
index 9e6f1fe5e..b9b76ad69 100644
--- a/src/uix/eidos/components/checkbox/checkbox-group.svelte
+++ b/src/uix/eidos/components/checkbox/checkbox-group.svelte
@@ -5,18 +5,24 @@
* - vertical + columns N → CSS grid with N columns (items wrap)
* - vertical (no columns) → flex column
* - horizontal → flex row (or grid with auto cols if columns set)
- */
+ */
import * as Checkbox from '$soma/components/checkbox';
+ import { composeInlineStyle } from '$uix/eidos/lib/style';
import type { CheckboxGroupProps } from './types';
- let { columns, value = $bindable([]), children, ...rest }: CheckboxGroupProps = $props();
+ let { columns, value = $bindable([]), style, children, ...rest }: CheckboxGroupProps = $props();
+
+ const columnsStyle = $derived(
+ columns ? `--_checkbox-group-columns: ${columns};` : undefined
+ );
+ const mergedStyle = $derived(composeInlineStyle(style, columnsStyle));
{@render children?.()}
diff --git a/src/uix/eidos/components/checkbox/checkbox.svelte b/src/uix/eidos/components/checkbox/checkbox.svelte
index 19f69d5aa..b1d7ae2ce 100644
--- a/src/uix/eidos/components/checkbox/checkbox.svelte
+++ b/src/uix/eidos/components/checkbox/checkbox.svelte
@@ -12,7 +12,7 @@
*/
import { ActiveEidos } from '$uix/eidos';
import * as SomaCheckbox from '$soma/components/checkbox';
- import type { CheckboxProviderProps } from './types';
+ import type { CheckboxProps } from './types';
let {
size = 'md',
@@ -22,7 +22,7 @@
indeterminate = $bindable(false),
children: bodyContent,
...rest
- }: CheckboxProviderProps = $props();
+ }: CheckboxProps = $props();
const eidos = ActiveEidos.require();
const resolvedSize = $derived(eidos.resolve(size, 'md'));
diff --git a/src/uix/eidos/components/checkbox/index.ts b/src/uix/eidos/components/checkbox/index.ts
index eeaeb3c0e..6a9d055e2 100644
--- a/src/uix/eidos/components/checkbox/index.ts
+++ b/src/uix/eidos/components/checkbox/index.ts
@@ -18,17 +18,25 @@ import HiddenInput from './checkbox-hidden-input.svelte';
import Group from './checkbox-group.svelte';
import GroupLabel from './checkbox-group-label.svelte';
-export const Checkbox = Object.assign(CheckboxRoot, {
- Indicator,
- HiddenInput,
- Group,
- GroupLabel
-});
+type CheckboxNamespace = typeof CheckboxRoot & {
+ Indicator: typeof Indicator;
+ HiddenInput: typeof HiddenInput;
+ Group: typeof Group;
+ GroupLabel: typeof GroupLabel;
+};
+
+const Checkbox = CheckboxRoot as CheckboxNamespace;
+Checkbox.Indicator = Indicator;
+Checkbox.HiddenInput = HiddenInput;
+Checkbox.Group = Group;
+Checkbox.GroupLabel = GroupLabel;
+
+export { Checkbox };
export default Checkbox;
export type {
- CheckboxProviderProps as CheckboxProps,
+ CheckboxProps,
CheckboxIndicatorProps as IndicatorProps,
CheckboxHiddenInputProps as HiddenInputProps,
CheckboxGroupProps as GroupProps,
diff --git a/src/uix/eidos/components/checkbox/types.ts b/src/uix/eidos/components/checkbox/types.ts
index ea37929ee..536789aa9 100644
--- a/src/uix/eidos/components/checkbox/types.ts
+++ b/src/uix/eidos/components/checkbox/types.ts
@@ -44,7 +44,7 @@ export type CheckboxIconSnippetArgs = {
indeterminate: boolean;
};
-export type CheckboxProviderProps = SomaCheckboxProviderProps & {
+export type CheckboxProps = SomaCheckboxProviderProps & {
/** Visual size preset. Resolved via `Eidos.resolve`. @default 'md' */
size?: ResponsiveProp;
/** Visual variant. @default 'solid' */
diff --git a/src/uix/eidos/components/collapsible/index.ts b/src/uix/eidos/components/collapsible/index.ts
index 34ec7ff51..6f9f5a547 100644
--- a/src/uix/eidos/components/collapsible/index.ts
+++ b/src/uix/eidos/components/collapsible/index.ts
@@ -10,10 +10,16 @@ import CollapsibleRoot from './collapsible.svelte';
import Trigger from './collapsible-trigger.svelte';
import Content from './collapsible-content.svelte';
-export const Collapsible = Object.assign(CollapsibleRoot, {
- Trigger,
- Content
-});
+type CollapsibleNamespace = typeof CollapsibleRoot & {
+ Trigger: typeof Trigger;
+ Content: typeof Content;
+};
+
+const Collapsible = CollapsibleRoot as CollapsibleNamespace;
+Collapsible.Trigger = Trigger;
+Collapsible.Content = Content;
+
+export { Collapsible };
export default Collapsible;
diff --git a/src/uix/eidos/components/dialog/dialog-overlay.svelte b/src/uix/eidos/components/dialog/dialog-overlay.svelte
index d6d20ed65..2deba8e7c 100644
--- a/src/uix/eidos/components/dialog/dialog-overlay.svelte
+++ b/src/uix/eidos/components/dialog/dialog-overlay.svelte
@@ -6,6 +6,7 @@
* inline `--dialog-overlay-opacity` custom property so the recipe's
* `color-mix` picks it up without a class swap.
*/
+ import { composeInlineStyle } from '$uix/eidos/lib/style';
import * as Dialog from '$soma/components/dialog';
import type { DialogOverlayProps } from './types';
@@ -23,12 +24,10 @@
const resolvedOpacity = $derived(serializeOpacity(overlayOpacity));
const composedStyle = $derived(
- [style, resolvedOpacity ? `--dialog-overlay-opacity:${resolvedOpacity}` : '']
- .filter(Boolean)
- .join(';')
+ composeInlineStyle(style, resolvedOpacity ? `--dialog-overlay-opacity: ${resolvedOpacity};` : undefined)
);
-
+
{@render children?.()}
diff --git a/src/uix/eidos/components/dialog/dialog.svelte b/src/uix/eidos/components/dialog/dialog.svelte
index 6bc8f04f9..688240471 100644
--- a/src/uix/eidos/components/dialog/dialog.svelte
+++ b/src/uix/eidos/components/dialog/dialog.svelte
@@ -20,9 +20,9 @@
*
*/
import * as SomaDialog from '$soma/components/dialog';
- import type { DialogProviderProps } from './types';
+ import type { DialogProps } from './types';
- let { open = $bindable(false), children, ...rest }: DialogProviderProps = $props();
+ let { open = $bindable(false), children, ...rest }: DialogProps = $props();
diff --git a/src/uix/eidos/components/dialog/index.ts b/src/uix/eidos/components/dialog/index.ts
index 0e5854e29..df9ac43ea 100644
--- a/src/uix/eidos/components/dialog/index.ts
+++ b/src/uix/eidos/components/dialog/index.ts
@@ -27,22 +27,35 @@ import Header from './dialog-header.svelte';
import Footer from './dialog-footer.svelte';
import { Portal } from '$soma/components/internal';
-export const Dialog = Object.assign(DialogRoot, {
- Trigger,
- Portal,
- Overlay,
- Content,
- Title,
- Description,
- Close,
- Header,
- Footer
-});
+type DialogNamespace = typeof DialogRoot & {
+ Trigger: typeof Trigger;
+ Portal: typeof Portal;
+ Overlay: typeof Overlay;
+ Content: typeof Content;
+ Title: typeof Title;
+ Description: typeof Description;
+ Close: typeof Close;
+ Header: typeof Header;
+ Footer: typeof Footer;
+};
+
+const Dialog = DialogRoot as DialogNamespace;
+Dialog.Trigger = Trigger;
+Dialog.Portal = Portal;
+Dialog.Overlay = Overlay;
+Dialog.Content = Content;
+Dialog.Title = Title;
+Dialog.Description = Description;
+Dialog.Close = Close;
+Dialog.Header = Header;
+Dialog.Footer = Footer;
+
+export { Dialog };
export default Dialog;
export type {
- DialogProviderProps as DialogProps,
+ DialogProps,
DialogTriggerProps as TriggerProps,
DialogOverlayProps as OverlayProps,
DialogContentProps as ContentProps,
diff --git a/src/uix/eidos/components/dialog/types.ts b/src/uix/eidos/components/dialog/types.ts
index 06c4d707d..c55772d60 100644
--- a/src/uix/eidos/components/dialog/types.ts
+++ b/src/uix/eidos/components/dialog/types.ts
@@ -27,7 +27,7 @@ export type DialogSize = Extract;
*/
export type DialogPosition = Position;
-export type DialogProviderProps = SomaDialogProviderProps;
+export type DialogProps = SomaDialogProviderProps;
export type DialogTriggerProps = SomaDialogTriggerProps;
export type DialogContentProps = SomaDialogContentProps & {
diff --git a/src/uix/eidos/components/drawer/drawer-overlay.svelte b/src/uix/eidos/components/drawer/drawer-overlay.svelte
index 7b99d4f67..a1facd6f1 100644
--- a/src/uix/eidos/components/drawer/drawer-overlay.svelte
+++ b/src/uix/eidos/components/drawer/drawer-overlay.svelte
@@ -11,6 +11,7 @@
* wrapper still mounts the headless part for consistency, but it has no
* visual effect.
*/
+ import { composeInlineStyle } from '$uix/eidos/lib/style';
import * as Drawer from '$soma/components/drawer';
import type { DrawerOverlayProps } from './types';
@@ -28,12 +29,10 @@
const resolvedOpacity = $derived(serializeOpacity(overlayOpacity));
const composedStyle = $derived(
- [style, resolvedOpacity ? `--drawer-overlay-opacity:${resolvedOpacity}` : '']
- .filter(Boolean)
- .join(';')
+ composeInlineStyle(style, resolvedOpacity ? `--drawer-overlay-opacity: ${resolvedOpacity};` : undefined)
);
-
+
{@render children?.()}
diff --git a/src/uix/eidos/components/drawer/drawer.svelte b/src/uix/eidos/components/drawer/drawer.svelte
index 2bd8a2531..2d5b22389 100644
--- a/src/uix/eidos/components/drawer/drawer.svelte
+++ b/src/uix/eidos/components/drawer/drawer.svelte
@@ -13,7 +13,7 @@
*
*/
import { Provider as SomaDrawerProvider } from '$soma/components/drawer';
- import type { DrawerProviderProps } from './types';
+ import type { DrawerProps } from './types';
let {
open = $bindable(false),
@@ -21,7 +21,7 @@
isDragging = $bindable(false),
children,
...rest
- }: DrawerProviderProps = $props();
+ }: DrawerProps = $props();
diff --git a/src/uix/eidos/components/drawer/index.ts b/src/uix/eidos/components/drawer/index.ts
index cbd3ceb24..537af94f4 100644
--- a/src/uix/eidos/components/drawer/index.ts
+++ b/src/uix/eidos/components/drawer/index.ts
@@ -18,9 +18,9 @@
//
//
// `` IS the root (lives in drawer.svelte, wraps Soma's
-// Drawer.Provider). All child parts are attached as named members via
-// Object.assign — bits-ui / shadcn-svelte pattern. No Provider exported
-// publicly; no flat-with-snippet-slots invention. Children naming
+// Drawer.Provider). All child parts are attached as named members
+// explicitly. No Provider exported publicly; no flat-with-snippet-slots
+// invention. Children naming
// follows air / headless: Trigger, Content, Overlay, Title, Description,
// Close, Portal (+ Header, Footer as eidos-only layout shells, and
// Handle inherited from Soma's drag affordance).
@@ -36,9 +36,9 @@ import Header from './drawer-header.svelte';
import Footer from './drawer-footer.svelte';
import { Portal } from '$soma/components/internal';
-// Explicit per-property assignment instead of `Object.assign(DrawerRoot, ...)`
+// Explicit per-property assignment instead of bulk assignment
// to avoid a Svelte 5 hydration quirk where mutating the component
-// constructor with bulk Object.assign caused `` children
+// constructor in bulk caused `` children
// to re-mount on hydration (button appeared, then disappeared).
type DrawerNamespace = typeof DrawerRoot & {
Trigger: typeof Trigger;
@@ -69,7 +69,7 @@ export { Drawer };
export default Drawer;
export type {
- DrawerProviderProps as DrawerProps,
+ DrawerProps,
DrawerTriggerProps as TriggerProps,
DrawerOverlayProps as OverlayProps,
DrawerContentProps as ContentProps,
diff --git a/src/uix/eidos/components/drawer/types.ts b/src/uix/eidos/components/drawer/types.ts
index d40d0dde0..647e4f0d9 100644
--- a/src/uix/eidos/components/drawer/types.ts
+++ b/src/uix/eidos/components/drawer/types.ts
@@ -24,7 +24,7 @@ export type DrawerSize = Extract;
export type { DrawerVariant, DrawerDirection, DrawerSide };
-export type DrawerProviderProps = SomaDrawerProviderProps;
+export type DrawerProps = SomaDrawerProviderProps;
export type DrawerTriggerProps = SomaDrawerTriggerProps;
export type DrawerHandleProps = SomaDrawerHandleProps;
export type DrawerTitleProps = SomaDrawerTitleProps;
diff --git a/src/uix/eidos/components/icon/icon.svelte b/src/uix/eidos/components/icon/icon.svelte
index 91df8e16b..f024b06a3 100644
--- a/src/uix/eidos/components/icon/icon.svelte
+++ b/src/uix/eidos/components/icon/icon.svelte
@@ -1,4 +1,5 @@