astra
alpha-0.1-background
alpha-0.1-dir-prefs
alpha-0.1-sec-dom
menubar-v4-safe
active-uix
morfo-runtime
morfo-driven-soma
semantuix
glm-5
main
sium-v1.0
${ noResults }
254 Commits (201a4efb26153e90c8307cda850e9eb98f773bcd)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
0a014ed9e5 |
Remove UIX audit docs
|
5 months ago |
|
|
26166b9e3c |
Document UIX and arts ownership
|
5 months ago |
|
|
b121554918 |
Fix UIX docs theme and popover dismiss
|
5 months ago |
|
|
3dc775f440 |
Update UIX reauditoria notes
|
5 months ago |
|
|
223cdf9e92 |
Align sema docs with channel ownership
|
5 months ago |
|
|
694eb5c567 |
Clarify prefs projection contract
|
5 months ago |
|
|
e117141c8f |
Use plural naming in langs docs
|
5 months ago |
|
|
77c37cd940 |
Clarify prefs ownership docs
|
5 months ago |
|
|
f76b30499a |
Align Soma docs with runtime contracts
|
5 months ago |
|
|
87ab4f0496 |
Limit Eidos token barrel to active recipes
|
5 months ago |
|
|
6e8ced2e4c |
Archive Eidos base theme CSS
|
5 months ago |
|
|
e21bf6c8b8 |
Stop importing Eidos contracts from tokens
|
5 months ago |
|
|
80328a4ad3 |
Stop importing archived Eidos contracts
|
5 months ago |
|
|
8d6d4c5700 |
Generate Eidos base CSS artifact
|
5 months ago |
|
|
26b9b83eb5 |
Move prefs projection out of ActiveUix
|
5 months ago |
|
|
df1562f852 |
Document Eidos static CSS boundary
|
5 months ago |
|
|
31399d80b2 |
Add Eidos density scalars
|
5 months ago |
|
|
d5ab8e9d99 |
Add Eidos layout primitives
|
5 months ago |
|
|
ae19318090 |
Document frontend as legacy opt-in service
|
5 months ago |
|
|
6d25364b21 |
Clarify Morfo value attrs runtime contract
|
5 months ago |
|
|
f331c988c1 |
Route UIX managed DOM writes through ActiveDom
|
5 months ago |
|
|
b90d0a24a9 |
Reduce UIX runtime allocation churn
|
5 months ago |
|
|
6476efffe7 |
Purge visual slices from Sema
|
5 months ago |
|
|
4cc5f26a5a |
Defer sound channel priming
|
5 months ago |
|
|
6dfb07ee44 |
Clarify UIX theme contracts
|
5 months ago |
|
|
c12b1c48c8 |
Refactor active uix architecture
|
5 months ago |
|
|
e0eb5f718e |
Advance eidos theming and DOM ownership
|
5 months ago |
|
|
cb5097b3c3 |
Add active eidos runtime and morfo lang registry
|
5 months ago |
|
|
7214ae7940 |
uix: drawer/popover/toast/accordion eidos wrappers + sema packs + /uix docs scaffold
- eidos: migrate drawer / popover / toast / accordion to wrapper subdirectory pattern (provider + parts + tokens + recipe per component); avatar wrapper added; toggle/switch/collapsible recipe polish. - sema: per-component packs (accordion, drawer, popover, toast); engine / resolver / event / sema-map updates; visual channel hold refinements. - morfo: drawer event surface (variant, dragToDismiss, isDragging, minSize/ maxSize, onDragStart); popover/toast/accordion event refinements; schema + types updates. - soma: drawer provider rewrite (variant, dragToDismiss, isDragging binding, drag callbacks); toast provider/toaster restructure with toast-main + toast-status parts; popover content/arrow polish; gesture/floating layer updates; manipulation layer scaffold; avatar removed (eidos owns it now). - web/routes: legacy demos updated for new APIs; new /uix docs scaffold (web/routes/uix/) with Radix-style shell, theme/lang/format/semantic prefs, and standardized component template (Drawer page first — 6 tabs, live controls, drag-hint arrows, scroll-safe body wrapper). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
e6862265e2 |
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color
sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
29ce812f94 |
collapsible: flat <Collapsible> ergonomic default + compound parts
Per the doctrine update in GUIA §13: eidos for multi-part components
exposes BOTH a flat default that auto-composes Provider/Trigger/Content
AND the compound parts for advanced cases. Soma + morfo remain the
universal composable foundation underneath.
Why: with the previous "compound only" rule for multi-part, eidos
collapsible was a pure pass-through that added nothing operational —
the consumer wrote the same import + same shape as soma. The flat
default makes eidos earn its layer by collapsing the 90% case to a
single call:
// 90% case — flat ergonomic
<Collapsible bind:open>
{#snippet trigger()}Show details{/snippet}
<p>Body content.</p>
</Collapsible>
// Advanced — compound (parts in different subtrees, multi-trigger,
// conditional content)
<header><Collapsible.Provider bind:open>
<Collapsible.Trigger>Show</Collapsible.Trigger>
</Collapsible.Provider></header>
<aside>
<Collapsible.Content>...</Collapsible.Content>
</aside>
The architectural virtue: if the flat doesn't fit, a developer drops
to soma + morfo and composes freely. No vendor lock-in in the visual
layer; eidos is the design system convention, soma is the universal
primitive.
Files:
- src/uix/eidos/components/collapsible/collapsible.svelte (NEW): flat
default that auto-composes Provider + Trigger + Content via a
`trigger` snippet for the toggle label and `children` for the body.
- src/uix/eidos/components/collapsible/types.ts: adds
`CollapsibleFlatProps = SomaCollapsibleProps & { trigger?: Snippet }`.
- src/uix/eidos/components/collapsible/index.ts: exports `default`
(flat) plus named `Provider`/`Trigger`/`Content` (compound).
- src/uix/eidos/components/collapsible/{trigger,content,provider}.svelte:
fixed children destructuring so snippets propagate cleanly to soma
(was previously spreading $props() blindly which can lose snippet
bindings in Svelte 5).
- src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md: NEW §13 "Doctrina del API:
soma compound, eidos opinionado" formalises the two-shape rule and
the architectural virtue (soma+morfo as universal foundation).
Renumbered §13/§14 → §14/§15.
- web/routes/collapsible/+page.svelte: live preview now uses the flat
form; eidos snippet shows the flat shape; soma snippet keeps the
compound shape; Notes tab updated.
- src/uix/eidos/components/collapsible/README.md: rewrote API section
with both shapes + the "if neither fits, use soma+morfo" escape.
Tests 218/218 green. npm run check baseline 39.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
a9ad71118b |
fix: dark mode + collapsible demo layout shift
Two real bugs reported on the collapsible demo:
1. **Dark mode broken across all migrated components.**
- The docs site layout sets `document.documentElement.dataset.theme =
'light'|'dark'` — those are the selector names the docs CSS uses.
- The eidos themes (themes/base/{light,dark}.css) only respond to
`[data-theme='base-light'|'base-dark']`. Different namespace.
- Result: docs grays swap in dark mode but eidos doctrinal palette
(primary/secondary/neutral/affirm/fulfill/risk/threat/loss) stays
in light values, so component text renders dark on dark.
- Fix: each eidos theme now accepts BOTH selectors. `[data-theme=
'base-light'], [data-theme='light'], :root { ... }` and equivalent
for dark. Existing consumers that use `base-*` keep working; the
short `light/dark` aliases handle the docs site convention.
2. **Collapsible trigger jumps from center to left when expanded.**
- The preview-stage uses `display: flex; justify-content: center;
align-items: center;` — natural for buttons/switches that don't
grow.
- `Collapsible.Provider` is `display: block` so it shrink-fits its
content as a flex item. Closed = trigger-width (centered visually);
open = trigger + content width (longer, so the visual center
anchors trigger at the left edge).
- Fix: collapsible demo overrides the preview-stage style with
`justify-content: flex-start; align-items: flex-start` and wraps
the provider in a `max-inline-size: 32rem` panel. Disclosure
ergonomics need anchor-left + grow-down, not center.
- Also swapped the body's `--gray-50` (docs site token) for
`--color-surface-raised` + `--color-content-primary` (eidos
doctrinal tokens) so the panel inherits the dark-mode swap.
npm run check holds at the 39-error baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
d10281bc94 |
collapsible: migrate to eidos wrapper pattern (third pilot)
Third component migrated to the eidos wrapper subdirectory pattern,
following the new "check air baseline first" discipline.
Air baseline recovered from commit 0a391408^:
- air/components/collapsible: 3 wrappers (Provider/Trigger/Content)
with `class` passthrough, `bind:open`, `onOpenChange`, manual
`air.interaction.play('expansion', enter|exit)` perceptual emit.
- No size, intent, color, or chevron slot.
Migration decisions (presented BEFORE coding per scope-approval rule):
- Two doctrinal events (`expand` + `collapse`, both emerge family,
sequence='pre', no intent — emerge is transitional). Replaces air's
manual `air.interaction.play` with `runtime.trigger('expand'|
'collapse')` which atravieses visual + sound + vibra channels.
- No intent/color/size — air didn't have them and disclosure is
non-evaluative per guide §3.1.
- Multi-part API: Provider + Trigger + Content (no flat default per
doctrine §10).
Files:
- src/uix/morfo/components/collapsible.ts: scope expanded to ['soma',
'sema'], events array added with two emerge events.
- src/uix/soma/components/collapsible/collapsible-provider.svelte.ts:
runtime.events handlers `expand`/`collapse` mutate state; toggle()
fires runtime.trigger(next ? 'expand' : 'collapse').
- src/uix/eidos/components/collapsible/{collapsible.css,
collapsible-provider.svelte, collapsible-trigger.svelte,
collapsible-content.svelte, types.ts, index.ts, README.md}.
- src/uix/eidos/components/collapsible.css (legacy flat) deleted.
- src/uix/eidos/index.css: import path updated to subdirectory.
- web/routes/collapsible/+page.svelte: full interactive testbed with
tabs (Live, Soma, Morfo, Sema, Eidos, Notes) mirroring switch's
structure. Sema tab shows per-channel signature cards for both
expand and collapse events.
Disciplina formalizada en CLAUDE.md hand-off + memory entry: "Check
air baseline first" before any future eidos migration. Switch shipped
without this check and lost ResponsiveProp<Size> (regression). Don't
repeat with collapsible/dialog/drawer/popover/toast/avatar.
npm run check holds at the 39-error baseline. Tests 218/218 green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
3d0b234ec8 |
soma/morfo/active-uix: relocate runtime + rename SomaRuntime + sema opcional
Architectural correction. Morfo was importing Svelte runes, $adom, and
$uix/sema — all violations of "morfo is pure declarative DNA". The
runtime that interprets a CompiledMorfo lives where it executes: soma.
Move:
- src/uix/morfo/runtime.svelte.ts → src/uix/soma/runtime.svelte.ts
- src/uix/morfo/runtime.svelte.test.ts → src/uix/soma/runtime.svelte.test.ts
Rename (consistent with the move; "soma's runtime", per the doctrine
"morfo declares, soma executes"):
- class MorfoRuntime → SomaRuntime
- createMorfoRuntime() → createSomaRuntime()
- type MorfoRuntimeSources → SomaRuntimeSources
- error prefixes [morfo-runtime] → [soma-runtime]
Sema is now genuinely ornamental:
- ActiveUix.semantic returns EngineSemantic | undefined (no longer
throws when no engine is configured). Components in SSR / headless
tests / audio-disabled environments stay functional.
- SomaRuntime.trigger() skips the emit step AND the target-resolution
check when no engine is present — only requires a DOM target when
something will actually be emitted.
- Soma.semantic getter returns | undefined too (was non-optional).
Consumers updated:
- src/uix/active-uix/active-uix.svelte.ts — imports createSomaRuntime
from $soma; semantic getter returns | undefined
- src/uix/active-uix/types.ts — interface SomaRuntime, semantic | undefined
- 8 providers (toggle, switch, collapsible, dialog, drawer, popover,
toast, avatar) — type SomaRuntime, import path $soma
- src/uix/soma/index.ts — re-exports createSomaRuntime + types
- src/uix/morfo/index.ts — drops the runtime re-exports (morfo is pure)
- src/uix/sema/signal.ts — JSDoc reference updated
- src/uix/soma/core/soma.svelte.ts — semantic getter | undefined
Test added: SomaRuntime.trigger() succeeds without a semantic engine
(skips emit + target check, runs handler directly).
Docs aligned to the new shape:
- active_architecture.md — runtime locations, sema as ornamental
- soma/SOMA_ARCHITECTURE.md, soma/README.md — name + import paths
- morfo/README.md — file listing now flags morfo as PURE TS, points
at soma/runtime.svelte.ts for the interpreter
- sema/README.md — SomaRuntime references
- README.md — root architecture diagram
- CLAUDE.md — hand-off note
Tests 218/218 green. npm run check holds at the 39-error baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
4d26761263 |
docs + switch: align active docs to canon, migrate switch to eidos wrapper
Phase 4 (docs alignment with the canonical guide):
- active_architecture.md — Dialog example refactored to the new event
shape (target inside semantic, explicit verb + sequence). Verbs
table grouped by family with the post-canon vocabulary (select/
toggle moved to commit, edit removed from handle, new verbs added).
Section 10 (Estado actual) updated to 2026-05-08 with current
progress: doctrina cerrada, eidos en producción, SoundChannel
eager-init, switch migrated, etc. "Para profundizar" §14 points at
the canonical guide.
- morfo/README.md — Step 5.5 added covering the doctrinal event
shape with all fields (semantic.family, .verb, .target, .sequence,
.intent) and the two naming shapes ({verb}-{variant} and
{family}-{verb}).
- sema/README.md — verbs section restructured to Record-by-family
matching the runtime; documents the verbs that moved between
families per the canon. Naming shapes section updated with both
forms and validateEventName signature.
- CLAUDE.md — "Session hand-off" rewritten with the post-canon
state. Pointer at GUIA_IMPLEMENTACION_SEMAUIX.md as the canonical
source. Pending list refreshed: switch is the immediate next
component, persistence/a11ySemantic/polymorphism deferred until
real consumers appear.
Switch migration to the eidos wrapper pattern:
- morfo/components/switch.ts — adds events: [{ commit-toggle }] with
the doctrinal shape (verb=toggle, sequence=post, intent fromProp
binding to the 4 supported intents). New data-* (data-color with
the 6-token Toggle/Switch subset, data-readonly, data-invalid,
data-required) and corresponding aria-*. scope expanded to
['soma', 'sema'].
- soma/components/switch/types.ts — adds intent, color, readonly,
invalid, aria-labelledby props with full JSDoc. Subset constrained
per guide §3.1 (Toggle/Switch row).
- soma/components/switch/switch-provider.svelte.ts — runtime-direct,
Field integration via FieldProvider.get(), isDisabled/isReadonly/
isRequired/isInvalid OR-merge derivations, intent ↔ color
resolution (intent wins when evaluative), runtime.trigger(
'commit-toggle') in onclick.
- soma/components/switch/components/switch.svelte — wires the new
props through bindProps.
- eidos/components/switch/{switch.css, switch.svelte, types.ts,
index.ts, README.md} — full subdirectory pattern, mirroring
toggle. Recipe drops legacy success/warning/danger/info selectors;
declares the 6 doctrinal data-color values directly. Wrapper
auto-renders Switch.Thumb inside the track and exposes an optional
`thumb` snippet for in-thumb glyphs.
- eidos/components/switch.css (legacy flat) deleted; eidos/index.css
updated to import the subdirectory recipe.
- web/routes/switch/+page.svelte — interactive testbed with all soma
props as live controls + tabs (Live, Soma, Morfo, Sema, Eidos,
Notes). Mirrors toggle's structure.
npm run check holds at the 39-error baseline. Tests 217/217 green.
Pending:
- Migrate collapsible / dialog / drawer / popover / toast / avatar
to the eidos wrapper pattern.
- Persistence + holds-by-intent — defer until first signal.warn /
signal.alert consumer.
- a11ySemantic + polymorphic events — defer until concrete need.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
d4dfdaffa9 |
sema/morfo/eidos: align with GUIA_IMPLEMENTACION_SEMAUIX (canonical guide)
The new doctrinal guide at src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md
supersedes src/docs/sema-implementation-guide.md and locks the
canonical vocabulary, event shape and color tokens. This commit aligns
the codebase to that canon in three concrete moves.
Phase 1 — sema/verbs.ts vocabulary:
- Restructured from flat array to family-keyed Record per the guide.
- Moved select / toggle / acknowledge → commit (these fix state, not
contact). Removed edit from handle (it's shift.enter-mode now).
- Added new verbs per the canon: contact (tap, focus, trigger,
release), commit (complete, restore, expire, set, remove, reorder),
signal (remind), handle (rotate, scroll, reorder), shift (route,
step, return, context), sustain (start, loading, waiting, syncing,
processing, streaming, pending, retrying, end).
- validateEventName now recognises both naming shapes:
{verb}-{variant} (head=verb) and {family}-{verb} (head=family).
- New helper familyForVerb. Tests rewritten — 16/16 green.
Phase 2 — morfo event shape:
- target moved from event-level into event.semantic. Adds optional
semantic.verb (advisory, validated against SEMA_VERBS[family]) and
optional semantic.sequence ('pre' | 'coincident' | 'post').
- compile.ts and schema.ts updated. The 22 events across toggle,
toast, popover, drawer, dialog refactored to the new shape with
explicit verb + sequence.
- Polymorphic capability (allowedFamilies + defaultSemantic) and
a11ySemantic deferred — no current consumer needs them; will land
when the first concrete usage appears.
Phase 5 — color tokens (doctrinal 8):
- themes/base/light.css and dark.css rewritten to expose primary,
secondary, neutral, affirm, fulfill, risk, threat, loss. Renames
success→fulfill, warning→risk, danger→threat (same hex). New
primitives for secondary (slate-blue), affirm (teal-mint, low
activation), loss (deep violet-grave, posterior). info palette
deleted entirely — per guide, "info no es intent, es signal.announce
+ neutral".
- _static.css legacy compatibility aliases (--color-bg, --color-success,
--color-error, --color-accent, ...) deleted. Clean cut, no
transition. Consumers reference doctrinal palettes directly.
- 29 consumers (token files + recipes) migrated:
- All --*-color-info-* declarations dropped.
- Component palettes extended with secondary / affirm / loss.
- switch + checkbox recipes updated to use [data-color='affirm'|
'risk'|'threat'|'secondary'] (was 'success'|'warning'|'danger'|
'info').
- toast tokens + recipe renamed to affirm/fulfill/risk/threat/loss
(was success/error/warning/info).
- meter, progress, mark, badge, button tokens renamed.
- Contracts (contracts/contract.css + contracts/components/*)
updated.
- focus-ring-color-error in themes now points to --color-risk-border
(validation errors are signal.warn + risk per doctrina, not threat).
Phase 4 (partial):
- src/docs/sema-implementation-guide.md deleted (superseded by the
canonical guide).
Tests: 217/217 green across uix. npm run check holds at the 39-error
baseline (all pre-existing in unrelated test files; none introduced).
Pending in follow-up commits:
- Update active_architecture.md, sema/README.md, morfo/README.md,
CLAUDE.md to reflect the canon.
- Switch component migration to the eidos wrapper pattern.
- Persistence field (untilAction / untilFix / stateBound) and
holds-by-intent — defer until the first signal.warn / signal.alert
consumer appears.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
2d35f4b3e9 |
docs: prune obsolete audits + studies, refresh remaining references
Deleted 60 obsolete documents that no longer reflect the active-uix
state:
- 47 per-component audit logs (*-audit.md, audit_N.md, AUDIT.md)
- 5 global soma audits (AUDIT_1, codex_audit, soma-audit-04-20/21,
audit-prompt template)
- 5 closed studies (soma/study, morfo/study, morfo/DESIGN, morfo/
PROVIDER_STUDY_2026-04-23, uix/CONTINUITY_2026-04-24)
- 2 superseded refactor proposals (refactor_claude, refactor_code)
- 1 stale bug log (form/BUG-onchange-onblur-hang — repro path
src/routes/test/sium/kitchen-sink no longer exists)
Updated the remaining operational references to drop dead links and
reflect the post-cleanup architecture:
- src/uix/README.md — rewritten links section, dropped pointers to
terra/air/CONTINUITY/refactor docs, updated adom/dom paths
- src/uix/active_architecture.md — header now dated 2026-05-08, lists
the major shifts since 2026-04-26 (provider inheritance dropped,
eidos in production, doctrina parte IV, sound eager-init); pendings
section reflects the eidos migration roadmap; "see also" purged of
deleted files
- src/uix/morfo/README.md — file tree updated (removed DESIGN/study,
added compile/runtime/PERMUTATION_RUNNER); see-also pruned
- src/uix/sema/README.md — SoundChannel section now describes the
eager-init pattern (constructor-time, not lazy); pointer to
sema-implementation-guide added
- src/uix/soma/README.md — drops terra/air mentions, declares the
actual dependency direction (eidos -> soma, not the reverse)
- src/uix/soma/SOMA_ARCHITECTURE.md — §15 rewritten as "Estado actual
y deuda histórica" reflecting Provider-inheritance-drop; demo path
updated to web/routes/
- src/uix/soma/COMPONENT_GUIDE.md — A23 dates section now points to
$libs/days (no external/dates façade); demo paths swapped from
/test/soma/* to web/routes/
- src/uix/morfo/PERMUTATION_RUNNER.md — paths swapped to web/routes/
with a relocation note
- CLAUDE.md — Refactor Documents section replaced with Reference
Documents pointing to per-layer READMEs
npm run check holds at baseline (39 pre-existing errors, none new).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
f5a2a7fb49 |
eidos: pilot wrapper pattern + doctrinal API conventions
Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper +
types + index + README) replacing the flat CSS-only form. Pattern is
documented in eidos/components/README.md and the toggle README.
Shared types live in eidos/lib/types.ts. First export is `Size` (8 values
xxs..xxl + full); components narrow with `Extract<Size, ...>` per the
per-component-subset doctrine. No `Eidos` prefix on types — module path
already conveys the layer.
API doctrine:
- soma stays compound (Toggle.Provider) for symmetry with multi-part
- eidos exports both default + Provider so single-part components
accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>`
(compound-style consumers)
SoundChannel eager-init fixes the autoplay race: AudioContext is created
+ resumed synchronously on the first user gesture (capture-phase
listener registered in the constructor), avoiding the previous race
where the first emit() scheduled the resume in a microtask outside the
gesture window.
Demo page (web/routes/toggle/+page.svelte) restructured so the live
preview renders ALWAYS above the tablist — Sema-tab Play buttons can
fire on the real toggle. Motion preview amplifies scale ×8 visually
only; doctrinal values stay in the <dl>.
Conventions 7-13 added to src/docs/sema-implementation-guide.md
covering: directory structure, wrapper composition, no Eidos prefix,
soma compound vs eidos flat, iconOnly sr-only body, sound eager-init,
docs-preview amplification.
CLAUDE.md gets a session hand-off block listing where things stand and
next concrete steps (migrate switch/collapsible/dialog/drawer/popover/
toast/avatar; wire topbar sound mute to masterGain; rename theme
tokens to drop the success/warning/danger fallback aliases).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
1b9740f50d |
soma/{dialog,drawer,popover,toast}: Phase A — runtime-direct migration
Closes Phase A of the soma cleanup. Every soma component that was
already runtime-aware (Toggle, Switch, Collapsible, Avatar from prior
commits + the four in this commit) now lives without the `Provider<T>`
inheritance.
Pattern applied to each (8 classes total across the four components):
- Drop `extends Provider<...>`. Each class is plain.
- Drop module-level `createAttrs(morfo)` and `registerContract(morfo)`
side-effect calls.
- Drop `super(opts, ...)`. Constructors inline:
root: this.opts = opts; XxxProvider.ctx.set(this); …
child: this.opts = opts; this.provider = XxxProvider.require(); …
- Drop `assertProps()` wrapping. Eidos `lint.ts` covers contract
validation at design time; morfo type system covers enum mismatches
at compile time.
- `props` derived: `{ ...runtime.partProps(part), …overrides }`.
- For sub-parts that own a per-instance prop (Title.level,
Toast.Action.altText), pass the source through `registerPart(...,
{ props: { x: () => opts.x.current } })` so resolution stays local
to that part's effect.
- For ref-tracking callbacks (Dialog/Drawer/Popover content/overlay,
Drawer trigger/handle), use `registerPart(..., { onRefChange })`
instead of the legacy `super(opts, spec, undefined, onRefChange)`.
Component-specific notes:
- Dialog: 7 classes; root tracks parent + nesting via context lookup
BEFORE `ctx.set(this)`. `dismissWith(action)` API kept identical.
- Drawer: 8 classes; gesture/snap/handle plumbing untouched. The
drawer morfo doesn't declare `value:` sources for `data-state`,
`data-side`, `data-dragging`, etc., so those stay as inline
overrides in each part's `props` (component-specific computed
state, not morfo contract). To be lifted into the morfo as a
follow-up if it pays off in eidos selectors.
- Popover: 8 classes; floating layer (`FloatingProvider`/Anchor/
Content/Arrow) wires through unchanged.
- Toast: 7 classes; multi-runtime structure preserved — root
runtime carries Viewport, each Item creates its own runtime so
`partRef('item')` resolves to that toast's element. Title /
Description / Action / Close inside an Item register on the
Item's runtime, not the root's.
- alert-dialog: small drive-by — `this.dialog.soma?.langs.ts(…)`
became `Soma.get()?.langs.ts(…)` because `DialogProvider.soma` is
no longer an instance field.
Verified: 209/209 UIX tests green; TS clean for all migrated files
(remaining repo-wide TS errors pre-existing, unrelated).
After this commit:
- 8 soma components are runtime-direct (toggle, switch, collapsible,
avatar, dialog, drawer, popover, toast).
- ~52 components still use `Provider<T>` via the legacy `baseProps +
resolveMorfoProps` path. Those will migrate as Fase B —
opportunistically (each time one is touched for another reason)
rather than in a sweep, per the user's standing preference for
small, surgical commits.
- The `Provider<T>` base class stays as transitional infrastructure
until Phase B drains. Final deletion lands in Phase C.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
74c8b2e189 |
soma/{switch,avatar}: pilots 3+4 — runtime-direct, drop Provider inheritance
Same migration shape as toggle / collapsible. After 4 pilots the
pattern is consistent enough to extract a tiny base class — that's a
follow-up commit.
Switch (root + Thumb) — exercises `runtime.keydown` for action
dispatch (Enter / Space → 'toggle' action that calls
`preventDefault()` before flipping state).
Avatar (root + Image + Fallback) — exercises a multi-part component
where one child (Image) drives a watch.pre side effect to preload the
image and update parent state, and another (Fallback) reads the
parent's `status` reactively.
Per-class diff in both files:
- drop `extends Provider<...>`
- drop module-level `createAttrs(morfo)` and `registerContract(morfo)`
- drop `super(opts, ...)` calls; root inlines `ctx.set(this)`,
children inline `this.opts = opts; this.provider = Parent.require()`
- drop `this.assertProps(...)` wrapping in `props` derived
- drop `WithRefOpts` extension; inline `id: Active<string>; ref:
State<HTMLElement | null>` per child opts shape (more opt-in)
Avatar's `AvatarFallbackOpts` keeps `ref: State<HTMLElement | null>`
required because the fallback always renders DOM. `AvatarImageOpts` and
the root `AvatarOpts` derive from props via `OptsFromProps`.
Verified: 209/209 UIX tests green, TS clean for both files.
After this commit the migration sample is:
- toggle (single part, no ctx)
- switch (single part + ctx + actions/keyboard)
- collapsible (3 parts + ctx + cross-part id sources via partRef)
- avatar (3 parts + ctx + side-effect watch.pre + multi-state)
Common shape across all four:
- `readonly opts: T;` declaration + `this.opts = opts` assignment
- root: `XxxProvider.ctx.set(this)` (when ctx present)
- root: `Soma.require()` + `soma.uix.runtime(morfo, sources)` +
`runtime.registerPart('provider', { id, ref })`
- children: `this.provider = XxxProvider.require()` +
`parent.runtime.registerPart(part, { id, ref })`
- `props` derived → `{ ...runtime.partProps(part), ...customProps }`
The 60+ unmigrated components still use the legacy `Provider<T>` base
(baseProps + resolveMorfoProps path). That class stays put until those
components are also runtime-aware.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
7d125262d8 |
soma/collapsible: pilot — drop Provider inheritance (multi-part + context)
Second pilot. Collapsible exercises the multi-part case Toggle didn't:
3 providers (root + Trigger + Content), one shared morfo runtime
created by the root, and Svelte context wiring so children can find
the parent. The migration shape mirrors Toggle but factors in the
extra plumbing.
What changed:
- All three classes (`CollapsibleProvider`, `CollapsibleTriggerProvider`,
`CollapsibleContentProvider`) drop `extends Provider<...>`.
- Root: `CollapsibleProvider.ctx.set(this)` is called manually in the
constructor (replaces the implicit `super(opts, spec, ctx)` setup).
The `context()` helper from `\$soma/provider/context` is independent
of the `Provider` base class and remains unchanged.
- Children: just store `this.opts = opts` and look up the parent via
`CollapsibleProvider.require()`. No `super(opts, spec)` call.
- Drop module-level `createAttrs(collapsibleMorfo)` and
`registerContract(collapsibleMorfo)` — same dead-side-effect pattern
Toggle had.
- Drop `assertProps` in every `props` derived. Eidos linter + morfo
types cover what the runtime contract validation did.
What stayed identical:
- Public surface — `CollapsibleProvider.create(opts)`,
`CollapsibleProvider.require()`, `state.props`, `state.toggle()`.
- The shared-runtime pattern: root creates `uix.runtime(morfo, sources)`
once; children call `parent.runtime.registerPart('trigger' | 'content',
{ id, ref })`. This is what the morfo runtime was designed for.
- The `triggerId` / `contentId` cross-part state sources for
`partRef` resolution. Children publish their id via direct assign
(per the "id registration: direct assign" rule, not \$effect).
- All 209 UIX tests green.
Note on `WithRefOpts`: the `Provider` base used `extends WithRefOpts,
StateProps<...>, ActiveProps<...>` to compose the opts type. Without
the base, the migrated classes inline `id: Active<string>; ref:
State<HTMLElement | null>` directly. Tradeoff: a couple of extra lines
of opts shape per class, but the type composition is now opt-in
(children that don't need a ref can omit it) instead of mandated by
inheritance.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
bf49f22b64 |
soma/toggle: pilot — drop Provider inheritance, use MorfoRuntime directly
First soma component to live without the `Provider` base class. Toggle
already used `uix.runtime(toggleMorfo, sources)` for the morfo-derived
attrs since an earlier refactor; this commit removes the residual
inheritance + dev-time validation scaffolding that was no longer
pulling its weight.
What changed:
- `ToggleProvider` no longer extends `Provider<ToggleOpts>`. It is a
plain class that owns `opts`, the field hookup, and the runtime.
- Drop the module-level `createAttrs(toggleMorfo)` (dead — return
value was unused) and `registerContract(toggleMorfo)` (the contract
registry is only read by `assertContract`, which we no longer call
for this component).
- Drop `super(opts, { morfo: toggleMorfo, part: 'provider' })`. The
runtime's `registerPart('provider', { id, ref })` already attaches
the ref via `attachRef` internally.
- Drop `this.assertProps(...)` wrapping in the `props` derived. The
eidos linter (`scripts/eidos-lint.ts`) already validates emitted
selectors against the morfo's `cssSelectors` contract at design
time, and the morfo type system catches enum mismatches at
compile time. Runtime contract validation was belt-and-suspenders.
What stayed identical:
- Public surface (`ToggleProvider.create(opts)`, `state.props`,
`state.snippetProps`).
- `OptsFromProps`/`bindProps` from `\$soma/provider/opts` — those
helpers are independent of the `Provider` class and remain useful.
- The wrapper `toggle.svelte` is untouched; it still consumes
`state.props` and `state.snippetProps` as before.
- All 209 UIX tests green.
The `Provider` base class is intentionally not deprecated. The other
60+ soma components still depend on it. Toggle proves the pattern
works for runtime-only components; future migrations follow per
concrete need (a 2nd consumer of the new pattern justifies extracting
shared bits, per the "no premature abstraction" rule).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
52532f5542 |
soma/provider: dedupe against the morfo compiler (Phase 2b minimal)
The Provider base class re-implemented every helper from the morfo
resolver verbatim — 9 functions plus its own `resolveMorfoProps` walk.
The runtime already routes through `compileMorfo` + `evalAttrPlan`
since Phase 2a; the Provider is now wired the same way.
What changed in soma/provider/provider.svelte.ts:
- Drop the local `findMorfoPart`, `shouldEmitMorfoEntry`,
`resolveMorfoPrimitiveSource`, `resolveMorfoSource`,
`resolveMorfoDataValue`, `resolveMorfoAriaValue` (~75 LOC of literal
duplication of resolver.ts).
- Drop the manual `resolveMorfoProps` walk (~28 LOC). New body is
seven lines: spread `compiledPart.staticAttrs`, then `evalAttrPlan`
each `dynamicAttrs` plan and skip undefined results.
- Drop the `createAttrs(spec.morfo)` call from the constructor; read
`compiled.parts.attrs[part]` directly. No semantic change.
- Drop the `findMorfoPart` walk for the part metadata; read
`compiled.parts.byKebab.get(part)` (O(1)).
- `_morfoPartMeta: MorfoPart | undefined` becomes
`_compiledPart: CompiledPart | undefined`. Same role, richer info.
Net diff: provider.svelte.ts goes 287 → 149 lines (-138).
What stayed identical: the public API (`baseProps` getter, `assertProps`
helper, `resolveMorfoProps(bindings)` signature, the dual constructor
overload for legacy subclasses without a morfo spec) plus the
behavioral contract — same context registration, same data-attr
contract validation, same prop snapshot shape for subclass
`\$derived.by` blocks. Subclasses are not touched.
Side effect: `resolveMorfoProps` now emits ARIA boolean values as the
literal strings 'true'/'false' (via `evalAttrPlan`'s `aria-boolean`
mode) instead of JS booleans. Aligns the Provider with the morfo
runtime's behavior (resolver.ts already returned strings; only
provider.svelte.ts had been emitting booleans). Svelte stringifies
both identically when spread onto an element, so the rendered DOM is
unchanged. All 209 UIX tests still green.
Also re-export the compile.ts surface (`compileMorfo`, `evalAttrPlan`,
`CompiledMorfo`, `CompiledPart`, `AttrPlan`, `KeyboardPlan`,
`ActionPlan`, contracts, etc.) from `\$uix/morfo` so consumers don't
have to dig into the file. Fix a stale comment that still pointed at
the deleted `\$uix/lib/types`.
Phase 2b proper (kernel rewrite with the 8 abstractions:
ComponentInstance / PartScope / PartRegistry / SourceRegistry /
ActionDispatcher / SemanticEmission / LayoutFacts / BehaviorRegistry)
is parked on purpose. None of those abstractions has two concrete
consumers today; deduplicating against the existing compiler is the
honest first move and unblocks any future kernel work without churn.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
7fe9cbffc4 |
morfo: wire MorfoRuntime to consume CompiledMorfo (Phase 2a)
The runtime is now the first production caller of `compileMorfo`. Each
per-tick effect previously walked the raw morfo tree; now it reads
pre-resolved plans off the compiled output.
What changed in runtime.svelte.ts:
- Single `compileMorfo(morfo)` call at the top (cached by WeakMap, so
multiple component instances of the same morfo share the result).
- `findMorfoPart` linear scan → `compiled.parts.byKebab.get(...)` O(1).
- `createAttrs(morfo)` walk → `compiled.parts.attrs` (pre-built).
- `findEvent` linear scan → `compiled.actions.byName.get(...)` O(1).
- `resolvePartAttrs` per-effect walk over `data` + `aria` arrays →
`{ ...staticAttrs, ...evalAttrPlan(plan, bindings) for plan in dynamicAttrs }`.
`staticAttrs` (literals + role) are pre-resolved at compile time;
only `dynamicAttrs` is re-evaluated per tick.
- Keyboard matching: `matchesKeySpec(specString, event)` re-parsed
the string every keystroke. Now the compiler emits `KeyboardPlan`
with a precomputed `ParsedKey`; matching is a flat field compare.
- Event dispatch uses `ActionPlan` (compiled view) instead of raw
`MorfoEvent`. `event.target.target` partRef-unwrap → `action.target`.
What was added in compile.ts:
- `evalAttrPlan(plan, bindings)` — evaluates one AttrPlan: short-circuits
on falsy condition, then branches once on `mode` instead of on
`source.kind` × `values.length` × `value.kind` per tick. Reuses the
pure helpers in resolver.ts (`shouldEmitMorfoEntry`,
`resolveMorfoSource`).
- Bug fix in `parseKey`: do not trim the literal key segment. `' '`
(Space) was being collapsed to empty string. The pre-existing
`matchesKeySpec` had a comment warning about this; the refactor
needed to preserve the same invariant. Caught by an existing
runtime test (Switch keyboard with Enter+Space).
Verified: 209/209 UIX tests green (morfo + soma + sema + eidos +
active-uix). The remaining 2 TS errors in runtime.svelte.test.ts
(lines 366 and 385) are pre-existing test casts and unrelated.
resolver.ts is no longer called from runtime.svelte.ts directly except
for the two pure helpers `shouldEmitMorfoEntry` (used by evalAttrPlan
and by keydown's condition check) and `MorfoBindings` (the binding
shape). The non-pure walk functions (`findMorfoPart`,
`resolvePartAttrs`) remain in resolver.ts for any consumer that still
imports them, but the runtime no longer does.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
ba487dfc62 |
soma/form: align with the new ecosystem
The form component still imported from paths that were deleted in the
active-uix cleanup:
- form.svelte: \$lib/sium/svelte (SiumProvider)
- form-auto-fields: \$lib/sium/core (Schema, SchemaKind, SiumShape)
\$langs (LangInstance)
Changes:
- form.svelte: drop the SiumProvider context fallback. The form
receives the schema via the `schema` prop (or none, for forms with
only `defaults`). The old fallback let a parent context inject the
schema implicitly; that was a single-call-site coupling and the
new ecosystem has no equivalent context yet. Re-introduce it later
via a soma-level scope only if a second consumer asks for it
(2-of-3 rule).
- form-auto-fields.svelte:
\$lib/sium/core → \$sium (Schema, SchemaKind, SiumShape are
re-exported from the package barrel)
\$langs (LangInstance) → \$lang (ActiveLang). The CompatibleLang
Pick<…, 'ts' | 't'> shape stays
identical since ActiveLang
carries the same surface.
Verified: svelte-check errors went 46 → 42 (the 4 form errors are gone;
remaining 42 are pre-existing in other components unrelated to the
cleanup). No form tests exist anymore (both were deleted in earlier
phases as casualties of the same import drift).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
8df2e9f748 |
uix: relocate Layer + PartRef to src/uix/types.ts
When src/uix/lib/types.ts went down with the rest of src/uix/lib/ in phase A of the cleanup, the Layer and PartRef cross-layer primitives went with it. Three files still imported them via `'../lib/types'`: - src/uix/sema/types.ts - src/uix/morfo/types.ts - src/uix/morfo/compile.ts Move the two types to src/uix/types.ts (top-level shared, sibling to all UIX layers — no layer "owns" a cross-layer primitive), translate the doc comment to English, and update the three importers to `'../types'`. No semantic change. Verified: svelte-check went from 49 errors to 46 (the 3 caused by the deletion are gone); the remaining 46 are pre-existing and unrelated. Vitest: morfo + eidos + sema = 136/136 green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
c69b8be32a |
cleanup phase E follow-up: drop dead form test + fix doc rot
- Remove src/uix/soma/components/form/components/form.svelte.test.ts — same casualty as the auto-fields test removed in phase B: imports from \$langs and \$lib/sium/* (both deleted). - Update arts/session/ssr.ts JSDoc example: \$lib/app → \$active-app. Note: form.svelte and form-auto-fields.svelte themselves still import from \$lib/sium/* and \$langs. They will not compile against the new ecosystem and need a rewrite or deletion as a separate decision — flagged but not changed here. Also note: src/arts/cache/test/engine-cache.test.ts fails with "defineActiveCache is not a function". The test file is untracked (was never committed); the failure predates this branch and is not caused by the cleanup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
0a3914086c |
cleanup phase B: delete obsolete UI layers + legacy lib
Removed in this commit:
- src/uix/air/ — old visual layer (~2.5k files)
- src/uix/terra/ — reference primitives (~765 files)
- src/lib/ — legacy root: actx, ling, logr, ext, glob,
util/dias, util/funcs, sium status notes,
uiux (~334 files). Replaced by src/arts/,
src/libs/, src/svrs/.
- src/uix/soma/components/form/components/form-auto-fields.svelte.test.ts
— only file in the codebase still importing
from \$uix/air. Form auto-fields will be
rebuilt with soma primitives if/when needed.
Verified post-deletion: zero matches for `from '\$lib(?!s)`, `\$glob`,
`\$langs`, `@/ling`, `@/logr`, `@/glob`, `@/actx`, `@/uiux`, `\$terra`,
`\$uix/(air|lib|terra)` across the remaining src/. The new ecosystem
(arts/, libs/, svrs/, uix/{active-uix, soma, sema, eidos, morfo}) is
self-contained.
Aliases in svelte.config.js / vite.config.ts still reference the
deleted paths — that's the next commit (phase C).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
90622e5d58 |
cleanup phase A: delete src/uix/lib + src/routes
`src/uix/lib/` (13 files): orphaned DOM utility layer; replaced by `src/libs/dom/` in the new ecosystem. Zero inbound imports. `src/routes/` (459 files): test routes for the dead terra/air/soma demos, plus a few stragglers under sema/eidos/sium. Demos will be re-authored fresh under `web/routes/` once the SvelteKit config is updated. No production code paths were touched — only the legacy reference implementation and its test pages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
83d2e0d843 |
eidos: lint a CSS file against its morfo contract
Consumes `compileMorfo(morfo).contracts.cssSelectors` to classify every
`[data-*]` selector in an eidos CSS file:
- morfo-backed: the runtime writes the attr; eidos reacts to it.
- eidos-only: the marker is present (`[data-toggle]`) but at least
one `data-*` is not declared on the morfo part. Legitimate design
tokens (`data-color`, `data-size`, `data-variant`) and drift cases
(`data-dialog-header` — a part the morfo no longer declares) both
land here; the lint tool reports them so authors can decide.
- invalid: the attr IS declared on the morfo, but with a value
outside the declared enum. Bug.
Also reports `unusedFromContract`: selectors the compiler promises
that no eidos rule consumes — the inverse direction of drift.
Two CLI entry points:
- `scripts/eidos-lint.ts <component>` — single-component report
- `scripts/eidos-lint-all.ts` — sweep across every eidos component
Initial sweep finds 0 invalid, 134 eidos-only, 82 unused contract
selectors across 11 components. Real drift hotspots: dialog/drawer
`header`/`footer` (eidos parts the morfos don't declare), popover's
`data-side × data-last-action` compounds, tooltip's
`data-state='delayed-open'`. Deliberate left as-is for the user to
triage — fixes belong in a separate commit.
Tests cover toggle (clean baseline), dialog (drift detector for
header/footer + dead `data-disabled`), and a synthetic invalid case.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
2c32c842bd |
morfo: introduce compile.ts — Phase 1 of the UIX refactor
`compileMorfo(morfo)` returns a `CompiledMorfo` with pre-resolved plans,
dependency analysis, and frozen contracts. Memoised by morfo identity via
`WeakMap` so the runtime pays compile cost once per morfo (HMR-friendly).
What the compiled form replaces:
- `createAttrs(morfo)` walk → `parts.attrs` (kebab → marker)
- `findMorfoPart(parts, target)` linear search → `parts.byKebab` map
- `resolvePartAttrs()` source.kind branching every effect → split into
`staticAttrs` (literals + role hoisted at compile time) and
`dynamicAttrs[]` (`AttrPlan` with explicit `mode`)
- keyboard string parsing each tick → `KeyboardPlan` with `ParsedKey`
- `flattenMorfoParts` walk per `registerContract` call →
`contracts.dataAttrsByPart` + `contracts.cssSelectors`
- per-part `deps.{states,props,parts,needsTranslations}` for narrow
effects; aggregate `requiredSources` for cross-cutting audits
Mode taxonomy (`AttrMode`): `html-presence`, `aria-boolean`, `enum`,
`string`, `data-state-derived` — the runtime branches once on a
discriminated union instead of re-walking source/condition/values.
Tests cover: parts walking + tree links, static/dynamic split, keyboard
parse, actions, CSS selector contracts, required-source aggregation,
per-part deps, mapRef inner-source extraction, cache identity, frozen
output, duplicate-kebab errors. 28 tests, all green.
No runtime callers wired up yet — that's Phase 2 (kernel + runtime
consumes CompiledMorfo). Old `resolver.ts` / `create-attrs.ts` paths
remain functional for the existing runtime.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
217d6969e4 |
active-uix: uix.runtime(morfo, sources) auto-injects dom + semantic
Adds a single convenience method on `ActiveUix` so soma providers stop
duplicating the `dom: this.soma.dom, semantic: this.soma.semantic`
boilerplate at every `createMorfoRuntime` callsite.
Before (8 providers, identical lines):
this.runtime = createMorfoRuntime(dialogMorfo, {
dom: this.soma.dom, // ← duplicated everywhere
semantic: this.soma.semantic, // ← duplicated everywhere
states: { … },
props: { … },
events: { … }
})
After:
this.runtime = this.soma.uix.runtime(dialogMorfo, {
states: { … },
props: { … },
events: { … }
})
The implementation is a thin convenience — internally `uix.runtime()`
calls `createMorfoRuntime(morfo, { dom: this.dom, semantic: this.semantic,
…sources })`. No registry, no scope ids, no per-instance lifecycle to
track. Per-provider runtime ownership unchanged. The user explicitly
chose this minimal fix over a "single shared runtime + scope handles"
design after weighing the tradeoffs.
Migrated callsites: avatar, collapsible, dialog, drawer, popover,
switch, toast, toggle (8 providers, ~10 callsites including toast's
two — root + item). The `createMorfoRuntime` named import dropped from
each (only `MorfoRuntime` type kept where the field is annotated).
3 new tests on `active-uix.svelte.test.ts`:
- returns a MorfoRuntime with auto-injected services
- caller does not pass dom or semantic
- attach mode uses the external app's dom + semantic
Verified: vitest 17/17 active-uix (was 14), 211/211 UIX, smoke 68/68,
morfo-check 66/66.
|
5 months ago |
|
|
9009cbfe8c |
active-uix: standalone mode no longer wraps active-app
Earlier design mistake: `createActiveUix(options)` was internally
calling `createActiveApp(...)` to compose the UIX services. That
violated the contract — active-uix was supposed to be either a
substitute for active-app (when no app exists) or a complement (when
an external app injects services).
Fix: standalone mode now instantiates each UIX service directly via
the canonical factories — no `active-app` involved:
- core: createEngineLogger, createEngineBus, createActiveTimers,
createActivePrefs (with empty schema)
- lang: createActiveLang(schema, defaultLocale, fallbackChain)
- dom: createActiveDom
- frontend: createActiveFrontend (auto-applies to <html>)
- format: createActiveFormat (clock from timers)
- semantic: new EngineSemantic(opts)
active-uix manages every owned lifecycle: dispose() tears them down
in reverse dependency order. Attach mode is unchanged — wraps an
external app, validates UIX services, doesn't touch app.dispose().
Type changes:
- `ActiveUix.app: ActiveApp | undefined`. Present only in attach
mode (it's the underlying app); `undefined` in standalone (no
app exists).
- `ActiveUix.bus: EngineBus` (generic envelope). Standalone gets
a fresh bus, attach gets the App's bus with its event map. UIX
components don't care about typed events; consumers that do
re-narrow at the call site.
- Removed `prefs?` and `extraServices?` from `ActiveUixOptions`.
Apps that need a custom prefs schema or services beyond UIX
should use attach mode (compose `createActiveApp` themselves).
New tests cover both modes: standalone (8 tests including service
disposal verification, idempotent dispose, format opt-out,
componentLangs registration toggle), attach (6 tests including
missing-services error, identity-equal service proxying, no
disposal of external app), and cross-mode parity (1 test).
Verified: vitest 15 files / 211 tests, smoke 68/68, morfo-check 66/66.
|
5 months ago |
|
|
83b841a7ca |
active-uix: introduce UIX layer between active-app and components
Decouples the UIX layer (soma / morfo runtime / sema / eidos) from
the App composition layer (`active-app`). UIX components no longer
import `\$active-app` directly — they read services through
`getActiveUix()`. Two boot paths produce the same `ActiveUix`:
- **Standalone** — `createActiveUix({ lang, semantic, … })`
bootstraps an `ActiveApp` internally with the UIX services plus
any `extraServices`. Owns the app — `dispose()` propagates.
- **Attach** — `attachActiveUix(externalApp)` wraps an app the
application composed itself (typically when it has services
beyond UIX: cache, session, http, …). The external app must
declare the UIX services; helper `defineUixServices(options)`
returns the slice ready to spread into
`createActiveApp({ services })`. Does NOT own the app.
In both cases components see only `ActiveUix`. They have no way to
tell which boot path was used.
Module:
src/uix/active-uix/
├── index.ts barrel ($active-uix)
├── types.ts ActiveUix, ActiveUixOptions, AttachActiveUixOptions
├── services.ts defineUixServices() + UIX_REQUIRED_SERVICES
├── active-uix.svelte.ts createActiveUix() + attachActiveUix() + ActiveUixImpl
├── components/uix.svelte <Uix uix={...}> wrapper (sets context + creates Soma)
└── svelte/context.svelte.ts setActiveUix() / getActiveUix() / tryGetActiveUix()
Surface exposed by `ActiveUix`:
- UIX services: lang, frontend, format (optional), dom, semantic
- Core: logger, bus, timers, prefs
- Config: somaPortalTo
- `app` escape hatch for advanced integration code (NOT for UIX
components)
Wiring done by active-uix at boot:
- `App.lang.extend('components', componentLangs)` — soma component
translations always available without per-app boilerplate.
- Frontend service auto-configured with `applyDom: true` +
`target: () => document.documentElement` so theme/dir/density
land on `<html>` automatically.
Migration:
- `Soma.create()` reads `getActiveUix()` instead of `getActiveApp()`.
Soma class no longer imports `\$active-app`.
- 3 layouts (test/eidos, test/sium, test/soma) replace
`createActiveApp + setActiveApp + <Soma>` with
`createActiveUix + <Uix>`. Boilerplate drops ~20 lines per layout.
- 7 sium pages replace `getActiveApp() + app.lang.setLocale(…)`
with `getActiveUix() + uix.lang.setLocale(…)`.
Validation:
- `attachActiveUix(app)` throws `ActiveUixMissingServiceError` if
the external app didn't declare lang/frontend/dom/semantic.
- `getActiveUix()` throws `ActiveUixNoContextError` if no `<Uix>`
ancestor mounted.
`\$active-uix` alias wired in svelte.config.js + vite.config.ts (both
client and server projects).
Verified: vitest 197/197, smoke 68/68, morfo-check 66/66, playwright
probe (4 routes including dialog with sound) zero page/console errors.
|
5 months ago |
|
|
5898cc58c7 |
libs/reactive: export ActiveProps and StateProps types
Follow-up to
|
5 months ago |
|
|
4bf6dea420 |
remove backward-compat re-export shims
Per architectural feedback: when relocating code to its correct layer,
do the migration completely — update every consumer directly and
delete the original. No re-export barrels at the old paths.
Removed shims:
- `src/uix/soma/attrs/` — was re-exporting `createAttrs` /
`registerContract` / `assertContract` / `getContract` from
`$uix/morfo` and the bool/data-attr formatters from `$libs/dom`.
All 66 importing files updated to import from the canonical
locations directly.
- `src/uix/soma/reactive/` — `reactive.svelte.ts` was a thin
re-export of `$libs/reactive`. The 79 consumers (every soma
provider + layers + types) now import primitives directly from
`$libs/reactive`. The `opts.ts` Props↔Opts bridge moved to
`src/uix/soma/provider/opts.ts` (its natural home — provider
plumbing, not reactive primitives).
- `attachRef` / `RefAttachment` re-export from
`src/uix/soma/provider/provider.svelte.ts` and from
`src/uix/soma/index.ts`. Both removed. Consumers
(`floating.svelte.ts`, `date-field-provider.svelte.ts`) now
import directly from `$libs/reactive`.
Also:
- Moved `ActiveProps<T>` / `StateProps<T>` types to
`src/libs/reactive/types.ts` — they're trivial generic helpers
over `Active<T>` / `State<T>` and shouldn't have lived in soma.
- Stripped soma's root barrel (`src/uix/soma/index.ts`) of every
re-export of libs/morfo concerns. It now only re-exports
actually-soma surfaces (Provider base, mergeProps, css helpers,
id, types, Soma class).
Result:
- `grep -r '\$soma' src/uix/morfo` → 0 matches
- `grep -r '\$uix' src/libs` → 0 matches
- `src/uix/soma/attrs/` and `src/uix/soma/reactive/` no longer exist
- Every import statement now reflects the real architectural layer
Verified: vitest 19/20 files / 214 tests (1 pre-existing air dead-
branch fail using `bun:test`), smoke 68/68, morfo-check 66/66.
|
5 months ago |
|
|
d9b6521020 |
fix dependency direction: morfo no longer imports from soma
The previous DOM consolidation commit left a wrong-direction edge —
`src/uix/morfo/runtime.svelte.ts` imported `createAttrs` from
`$soma/attrs` and `attachRef`/`RefAttachment` from `$soma/provider`.
Morfo is the declaration layer; soma is one of its consumers; morfo
must not depend on soma.
Moves:
- `createAttrs` (`src/uix/soma/attrs/create-attrs.ts`) →
`src/uix/morfo/create-attrs.ts`. It's a pure morfo→data-attr-name
derivation; its natural home is alongside the morfo declarations it
derives from. Soma is a consumer like any other.
- `contracts.ts` (registerContract / assertContract / getContract)
→ `src/uix/morfo/contracts.ts`. Same reasoning: contracts derive
from a morfo's `data: [{ values }]` enum.
- `attachRef` + `RefAttachment` (extracted from
`src/uix/soma/provider/provider.svelte.ts`) →
`src/libs/reactive/attach-ref.svelte.ts`. It bridges Svelte's
`{@attach}` system with the `State` reactive box; both ends are in
libs/reactive, so it lives there.
- Bool/data-attr formatters from `src/uix/soma/attrs/helpers.ts`
(boolToStr, boolToEmptyStrOrUndef, boolToTrueOrUndef,
getDataOpenClosed, getDataChecked, getAriaChecked) →
`src/libs/dom/attrs.ts`. Pure DOM-attr value formatters with no
morfo dependency — they're libs/dom material.
Critical bug fix uncovered by the move:
`src/uix/soma/reactive/reactive.svelte.ts` was a parallel
implementation of the reactive primitive system, with its OWN
`ActiveSymbol` / `WritableSymbol` instances — different from the
ones in `$libs/reactive`. As soon as `attachRef` (now in libs)
tried to detect a soma-created `State` via `isState`, the guard
returned false and the function fell into the setter-fn branch,
throwing "ref is not a function" in 10+ form tests.
Fix: rewrite `soma/reactive/reactive.svelte.ts` as a thin re-export
of `$libs/reactive`. They now share the same symbol identities, so
every cross-layer guard works consistently.
Compatibility:
- `$soma/attrs` is now a backward-compat barrel that re-exports from
`$uix/morfo` (createAttrs, contracts) and `$libs/dom` (helpers).
Existing soma providers continue working unchanged.
- `$soma/provider/provider.svelte.ts` re-exports `attachRef` /
`RefAttachment` from libs for any importer that still uses the
legacy path.
Verified post-cleanup:
- `grep -r '$soma' src/uix/morfo` → 0 matches (no morfo→soma)
- `grep -r '$uix' src/libs` → 0 matches (no libs→uix)
- vitest 14 files / 197 tests
- smoke 68/68
- morfo-check 66/66
|
5 months ago |
|
|
9b07cead6b |
uix: consolidate DOM + relocate morfo runtime to its layer
Three architectural cleanups in one cut:
1. **morfo runtime relocates to its own layer.** The MorfoRuntime + the
pure morfo→attrs resolver (and their tests) move from
`src/uix/soma/morfo/` → `src/uix/morfo/`. They were never
soma-specific — they interpret a morfo declaration on behalf of any
consumer. Soma was just the only consumer at the time. The new
barrel at `$uix/morfo` re-exports `createMorfoRuntime`,
`MorfoRuntime`, `MorfoRuntimeSources`, `RegisterPartOpts`,
`SourceMap`, `SemanticEmitter`, `TriggerOptions`, plus the resolver
helpers (`findMorfoPart`, `resolvePartAttrs`, `shouldEmitMorfoEntry`,
etc.).
2. **`src/uix/adom/` is gone.** The runtime pieces (`createActiveDom`,
`BodyScrollLock`, `DOMContext`, `RovingFocusGroup`, viewport) all
live at `$adom` (`src/arts/adom/`) which is the canonical
ecosystem-level ActiveDom. The `apply.ts` types and helpers
(`DomAttrValue`, `applyChange`, `removeAttrs`, `StructuralChange`)
live in `$libs/dom` and are re-exported from `$adom`. The 4 last
`$uix/adom` import sites updated to `$adom`.
3. **`src/uix/soma/dom/` is gone.** The 14 utilities (`isBrowser`,
`getDocument`, `isHTMLElement`, focus + tabbable helpers, etc.) all
exist as a superset in `$libs/dom`. The 12 soma consumers updated
their imports from `'../dom'` / `'$soma/dom'` → `'$libs/dom'`. No
functional change — purely import-path consolidation.
`src/uix/active-uix/` is preserved as an empty placeholder for the
future cross-layer composition class that will set up morfo + soma +
sema + eidos together.
The relocated runtime now imports cleanly:
- `ActiveDom`, `DomAttrValue` ← `$adom`
- `EngineSemantic`, sema helpers ← `$uix/sema`
- `Active`, `State` ← `$libs/reactive` (was `$soma/reactive`)
- `createAttrs` ← `$soma/attrs` (cross-layer; createAttrs is morfo
logic that lives in soma for now — slated for relocation in a
follow-up)
- `attachRef`, `RefAttachment` ← `$soma/provider/...` (idem)
Verified: vitest 14 files / 197 tests, smoke 68/68, morfo-check 66/66.
|
5 months ago |
|
|
a6e2c1e443 |
sema: align vocabulary with libro_semantica (signal, shift, loss)
Updates the canonical semantic vocabulary to match the latest
formulation in src/docs/libro_semantica_completo.txt:
Families (7, was 6):
contact, commit, signal, handle, emerge, shift, sustain
- `alert` → `signal` (rename: same perceptual function — attention /
salience — but the book's name reflects the broader scope including
notify, warn, emphasize, announce).
- `shift` (NEW, transitional): frame change. Modal mount, navigation,
mode change. Differs from emerge in that it reorganizes planes
(heavier backdrop, deeper shadow) — "cruce de umbral" not just
"aparición". Per book rule, the frame doesn't carry intent on its
own; evaluation belongs to a companion `signal` event.
Intents (6, was 5):
neutral, affirm, fulfill, risk, threat, loss
- `loss` (NEW): consummated negative consequence (negative valence,
low activation, posterior). Differs from `threat` (anterior, high
activation, convokes action): loss registers, doesn't convoke. Per
book §11.5 "la amenaza no es la pérdida".
sema-map.ts perceptual values:
- `signal` family entry: kept the prior `alert` numbers verbatim
(semantics is the same — attention/salience) under the new name.
- `shift` family entry: motion 260ms ease-in-out 0.98→1, sound
pitch 500 ascending gain 0.18 (deliberately quieter than emerge's
0.20 to not compete with companion signal), presence with stronger
backdrop (0.5) + shadow (blur 32 y 12 op 0.2) emphasizing
plane reorganization.
- `loss` intent deltas: descending sound contour (-150 pitch),
contraction (scale.to -0.04, motion duration ×0.95 — brief but
grave), color toward purple-grave (hue replace 280, lower
saturation/intensity), withdrawn presence (backdrop 0.05, no glow).
ARIA `role="alert"` (W3C live region) is unrelated and untouched in
soma/announce, soma/toast and morfo announce/field/form. The toast
morfo gains `loss → role:'status' aria-live:'polite'` mapping —
loss is consummated so it shouldn't interrupt.
Verbs added per the book's worked examples: signal {warn, notify,
emphasize}, shift {enter-mode, exit-mode, navigate}, commit {save,
discard, delete}, contact {press, click, hover}, handle {pick, carry,
drop}, emerge {reveal, show, hide}.
Verified: vitest 9 files / 99 tests, smoke 68/68, morfo-check 66/66.
|
5 months ago |
|
|
3faa4473f2 |
eidos/accordion: fix trigger rotating 90deg when open
The air → eidos refactor stripped the `.air-accordion-*` class hooks but
left 5 rulesets with their selector empty (just whitespace before `{`).
The most visible damage was line 161:
[data-accordion-trigger][data-state='open'] {
transform: rotate(var(--eidos-accordion-indicator-rotation));
}
Empty selector after the trailing space → the rule matched the trigger
button itself instead of the intended `.air-accordion-trigger-indicator`
descendant. So expanding any item rotated its title 90deg.
The other four orphaned rules (trigger-main flex layout, trigger-label,
indicator size/transition, content-inner padding) were dormant noise but
also dropped — the morfo doesn't declare an Indicator part and the demo
doesn't render the inner wrapper, so there's nothing to style.
Content padding is now applied directly to `[data-accordion-content]`
since there's no inner wrapper. Animation-height includes padding, which
matches the natural scrollHeight measurement.
Verified:
- playwright probe: trigger `transform: none` in both closed and open
state (was `matrix(...)` rotating 90deg before)
- smoke 68/68
|
5 months ago |
|
|
930d4c8c16 |
soma: emit perceptual signal on open in dialog/drawer/popover
Diagnosis: clicking "open" on dialog/drawer/popover produced no sound
(or any semantic feedback). Cause: every overlay trigger was bypassing
`runtime.trigger(...)` with a direct `handleOpen()` because the morfo's
declared target part (overlay content) doesn't yet exist in the DOM
when the trigger fires — and `runtime.trigger` was throwing when the
target part had no registered ref.
Fix:
- `MorfoRuntime.trigger(name, opts?)` now accepts `{ fallbackTarget?:
HTMLElement }`. When the declared target part is unmounted, the
fallback is used as the visual signal target so the perceptual
signal still fires. Sound / vibra channels don't care about the
target; the visual channel writes its `data-event*` to the trigger
briefly, which is the natural locus of the user's action anyway.
- Dialog / Drawer / Popover triggers now go through `runtime.trigger`
with the trigger element as `fallbackTarget`. Each provider's
`events` map gains an `open` (or `present`) entry that runs
`handleOpen()` as the runtime's handler step — preserving the
prewrite → emit → handler order.
- Popover's toggle uses `close-dismiss` for the close direction so
both directions emit a perceptual signal, not just open.
Soma class: `soma.dom` and `soma.semantic` are now non-optional again
(matching pre-migration semantics — every layout declares both
services). `soma.format.*` slices stay optional.
Verified end-to-end with a Playwright probe that spies AudioContext
construction:
Open dialog → 2 oscillators (was 0)
Press Escape → +2 (was 2)
Click overlay → +2 (was 0)
Gates: vitest 7 files / 78 tests, smoke 68/68, morfo-check 66/66.
|
5 months ago |
|
|
86f01f5d8e |
sema: revive resolver + signatures + real SoundChannel
Restores the resolver-driven architecture that was deleted in the
channel-modules refactor (commit
|
5 months ago |
|
|
9338be2662 |
soma+layouts: migrate App composition to createActiveApp (Phase 4 part 3)
Replaces the legacy `$lib/ext/app` `App.create({ langs, presentation })`
composition pattern with the new ecosystem's `createActiveApp({ services
})` across all three test layouts (eidos / sium / soma) and rewires Soma
+ morfo runtime to consume the new surface.
What changed:
- Soma class (uix/soma/core/soma.svelte.ts) now reads from `getActiveApp()`
in $active-app context and adapts its historical names over the new
ecosystem: `soma.langs → app.lang`, `soma.presentation → app.frontend`,
`soma.dates/nums/money/units → app.format.{dates,numbers,currency,units}`.
All 12 soma component providers consume Soma unchanged.
- New context bridge: $active-app exports `setActiveApp(app)` /
`getActiveApp()` / `tryGetActiveApp()` (mirrors `setBus` / `getBus`
from $bus). Throws `AappNoContextError` (new code APP_ERR_NO_CONTEXT)
when no App is in scope.
- 3 layouts (test/eidos, test/sium, test/soma) replace
`App.create({ langs, presentation })` with `createActiveApp({ services:
{ lang, frontend, format, dom, semantic } })` + `setActiveApp(App)` and
call `App.lang.extend(...)` post-construction for runtime translation
modules.
- 7 sium test pages migrate `App.get()` → `getActiveApp()`,
`app.langs` → `app.lang`, `app.setLocale(x)` → `app.lang.setLocale(x)`.
- Morfo runtime exports a tiny `SemanticEmitter` type (`Pick<EngineSemantic,
'emit'>`) so consumers and tests don't have to satisfy the full
EngineSemantic surface.
- soma/core/langs.ts and sium plural import migrate `LangNode` /
`p` from `$lib/ext/lang/*` → `$libs/lang`.
After this commit no consumer outside `src/lib/` imports from
`$lib/ext/app`, `$lib/ext/lang`, or `$lib/ext/presentation`. The legacy
shims under `src/lib/ext/` remain for now (Phase 5 will delete them).
Verified:
- svelte-check: only 2 pre-existing errors (event.ts, runtime test)
- vitest: 6 files / 69 tests green (sema + morfo runtime)
- smoke: 68/68 soma routes 200 OK
- morfo-check: 66/66 morfos validate against demo DOM
- playwright: /test/sium/{login,booking,color} + /test/eidos/toggle
render full content with 0 page errors / 0 console errors
- eidos recipe check: dialog provider chain end-to-end OK
|
5 months ago |
|
|
a7adfc43b1 |
sema: add defineEngineSemantic factory for createActiveApp composition
Adapts EngineSemantic to the AppServiceFactory contract so apps composed
with `createActiveApp` can declare semantic as a service:
services: {
semantic: defineEngineSemantic({ visual: { defaultHold: 400 } })
}
The factory lives in `src/uix/sema/` (not in `arts/active-app/service-
factories/`) because semantic coordination is a UIX-layer concern, not
generic app infrastructure like cache / auth / perm.
EngineSemantic itself stays unchanged — the factory just wraps it,
declares no core/service deps, and disposes the engine on teardown.
|
5 months ago |
|
|
746ce8dbe8 |
sema: rename SemanticEngine → EngineSemantic + .destroy() → .dispose()
Aligns sema's engine class with the new ecosystem's Engine* / Active*
naming convention (per src/arts/README.md):
- Engine* — public methods over private state. No reactive surface.
- Active* — Engine* extended with $state + onChange listeners.
Sema's SemanticEngine is purely imperative: a channel registry + an
async dispatch. No $state, no listeners, no loading/lastError. So by
convention it must be Engine*, not Active*.
Renames (mechanical):
SemanticEngine → EngineSemantic
SemanticEngineOpts → EngineSemanticOptions (also aligned to
EngineSiumOptions / EngineHttpOptions name)
.destroy() → .dispose() (uniform with the rest
of the ecosystem; only one call site, in
engine.test.ts)
Touched code: engine.ts, exports.ts, emit.test.ts, engine.test.ts +
README.md (kept consistent). Also sweep src/lib/ext/app/{defaults,
app.svelte}.ts which still mention the old name in fallback wiring.
Air's docs (AIR_*.md) and the legacy CONTINUITY note kept untouched
(dead branch).
This commit is purely a rename. The next commit (Phase 4 part 2) adds
the defineEngineSemantic factory and migrates soma + the 3 layouts to
createActiveApp.
Verification: sema 44/44, smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
c3dbd47f5b |
morfo/schema: migrate $lib/sium/core → $sium (Phase 3)
Drop-in path change. The new $sium barrel ($arts/sium) re-exports the exact same surface the legacy $lib/sium/core provided: object, array, string, boolean, literal, union, discriminated, optional Schema (type) SiumValidationError Three import statements updated. Zero behavior change. Verification: morfo tests 21/21, smoke 68/68, morfo-check 66/66. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
b532b3a1e4 |
migrate $lib/util/* → $libs/* in soma + test pages (Phase 2)
Mechanical sweep of legacy util imports to the new $libs/* equivalents.
Drop-in: surfaces are compatible (verified per-module before sed).
$lib/util/dias → $libs/days (vendored dias, exact match)
$lib/util/colors → $libs/color (same exports)
$lib/util/arrays → $libs/arrays (same)
$lib/util/strings → $libs/strings (same)
$lib/util/funcs → $libs/funcs (same)
$lib/util/objs → $libs/objs (same)
$lib/util/numbers → $libs/numbers (same)
28 files touched: 6 soma source files (color, color-field, color-picker
providers + external/{colors,dates} adapters) + 22 test pages
(date/time/range pickers, calendar, color, sium booking/color/kitchen-sink).
Skipped intentionally:
- src/uix/terra/** and src/uix/air/** (dead branches per memory).
- src/lib/** internal references (legacy folder itself).
- Documentation .md files (Phase 6 cleanup).
Verification: smoke 68/68, morfo-check 66/66.
Note on the bash sed escaping: first attempt with double-quoted
patterns produced `$lib$libs/days` because `\\$lib` inside double
quotes only matched the trailing `/util/dias` portion. Fixed by
switching to single-quoted sed expressions ('s|\$lib/util/dias|
$libs/days|g') so bash leaves both `$` literals alone for sed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
62eb00fe8a |
deprecate src/lib + wire new ecosystem aliases (Phase 0+1)
Phase 0 — mark legacy as deprecated:
- Add @deprecated JSDoc header to all 27 top-level barrels under
src/lib (ext/{app,lang,dates,money,nums,units,presentation},
{actx,ling,logr,glob,sium,util}, root index.ts).
- Add src/lib/README.md with the migration map: legacy module →
new equivalent under src/arts + src/libs + src/svrs.
- Zero behavior change. Only IDE strikethrough at usage sites.
Phase 1 — wire new ecosystem aliases in vite.config.ts and svelte.config.js:
- 18 new $arts/* aliases: $active-app, $adom, $auth, $bus, $cache,
$connection, $format, $frontend, $http, $lang, $logger, $orca,
$perm, $prefs, $session, $sium, $storage, $timer.
- $libs (src/libs), $locale (src/libs/locale), $svrs (src/svrs).
- $reactive rewired from src/lib/util/reactive to src/libs/reactive
(export surfaces verified compatible — same symbols, drop-in).
- Legacy aliases ($lib, $glob, $langs, @/ling, @/logr, @/glob,
@/actx, @/uiux) preserved for backwards compat during migration.
- SvelteKit auto-regenerates .svelte-kit/tsconfig.json paths from
svelte.config.js, so TS resolution updates without manual edits.
The migration plan (Phases 2-6) lives in src/lib/README.md:
Phase 2 — sweep src/lib/util/* imports to $libs/* (drop-in).
Phase 3 — morfo/schema.ts: 3 imports to $libs/standard-schema.
Phase 4 — soma's App.create() composition to createActiveApp with
explicit service slots (including semantic for sema). Only step
that changes architecture.
Phase 5 — sweep ~82 test pages to use new aliases.
Phase 6 — remove src/lib/{glob,ling,actx} after verifying no
consumers; investigate sium and util/{dates,times,timers}.
Verification: smoke 68/68, morfo-check 66/66. svelte-check baseline
unchanged for the alias rewire (no Cannot-find-module errors). The
2 dialog-provider possibly-undefined errors at lines 113-114 are
pre-existing (this.soma vs this.soma! after Soma.get() returning
Soma | undefined) — not introduced by this commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
800e794cea |
eidos batch: Accordion + Tabs + Checkbox recipes + demos
Mechanical translation from air's CSS (--air-* → --eidos-*, --_air-* →
--_eidos-*, drop .air-* class gates):
- eidos/components/accordion.css (205 lines): variants surface/ghost,
sizes sm/md/lg, indicator-placement, motion-disabled. Selectors
align with morfo: data-accordion / data-accordion-{item,header,
trigger,content}.
- eidos/components/tabs.css (222 lines): variants line/pills/surface,
sizes xs/sm/md/lg, orientation. Selectors align: data-tabs /
data-tabs-{list,trigger,content}.
- eidos/components/checkbox.css (183 lines): states checked /
unchecked / indeterminate, color variants (primary/success/warning/
danger/info/neutral), sizes. Selectors align: data-checkbox /
data-checkbox-{indicator,group,group-label}.
Demos at /test/eidos/{accordion,tabs,checkbox}. Layout nav + index
page updated.
Verification: smoke 68/68, morfo-check 66/66, all 3 pages 200.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
b5453af9c4 |
popover: extend morfo with events + soft-migrate provider + exit tinting
Same harmonization pattern as Drawer (which mirrored Dialog):
Morfo (`morfo/components/popover.ts`):
- Added 6 events: present + 5 close-* (save/cancel/dismiss/
dismiss-outside/after-fail). present + close-cancel/dismiss/
dismiss-outside use family='emerge'; close-save uses commit/fulfill;
close-after-fail uses alert/threat.
- Added data-last-action to Content's data array.
- scope: ['soma', 'sema'].
Eidos (`eidos/components/popover.css`):
- Tint rules: combine [data-state='closed'] + [data-side] +
[data-last-action] to color the trigger-facing edge (opposite to
the popover side) during exit. saved → success, failed → danger,
cancelled/dismissed/dismissed-outside → neutral.
Soma (`soma/components/popover/popover-provider.svelte.ts`):
- PopoverProvider injects MorfoRuntime + dismissWith(action).
- Registers content part on mount.
- Wires close paths through runtime.trigger():
Close.onclick → 'close-cancel'
Escape (Dismissal) → 'close-dismiss'
InteractOutside → 'close-dismiss-outside'
- PopoverProvider class re-exported from $soma/components/popover.
Demo (`test/eidos/popover/+page.svelte`):
- Tinted-exit section with Save/Cancel/Fail buttons via local
_dismiss-button.svelte (mirror of Dialog/Drawer pattern).
Verification: smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
106b972075 |
drawer: extend morfo with events + soft-migrate provider + exit tinting
The Drawer's "show" semantic was missing from the morfo, so close paths
went straight to handleClose() without the prewrite → emit → handler
cadence. User flagged this — drawer DOES have a present/dismiss
semantic, equivalent in surface to Dialog.
Morfo (`morfo/components/drawer.ts`):
- Added `events[]` mirroring Dialog: present, close-save,
close-cancel, close-dismiss, close-dismiss-outside, close-after-fail
- Added `data-last-action` (saved/cancelled/dismissed/dismissed-outside/
failed) to Content's data so the prewrite has a declared slot
- scope: ['soma', 'sema'] now (sema participates via signal emit)
Eidos (`eidos/components/drawer.css`):
- Reserved a 3px transparent border on the LEADING edge of Content
per side (the edge facing the user, opposite to the slide-out
direction): right→border-left, left→border-right, top→border-bottom,
bottom→border-top
- Tint rules: combine [data-state='closed'] + [data-side] +
[data-last-action] so the right edge gets colored during the close
animation (saved → success, failed → danger, cancelled/dismissed →
neutral)
Soma (`soma/components/drawer/drawer-provider.svelte.ts`):
- DrawerProvider injects a MorfoRuntime (same pattern as Dialog soft-
migration). Registers content part. Wires events.{close-save,
close-cancel, close-dismiss, close-dismiss-outside, close-after-fail}
to handleClose().
- dismissWith(action) public method — same API as Dialog.dismissWith.
- Replaced 4 close paths with runtime.trigger():
Close.onclick → 'close-cancel'
Escape (modal) → 'close-dismiss'
Escape (non-modal) → 'close-dismiss'
Overlay.onclick → 'close-dismiss-outside'
Drag-to-dismiss (gesture) keeps direct handleClose() — the visible
slide-out IS the close, no 240ms hold makes sense.
- DrawerProvider class re-exported from $soma/components/drawer.
Demo (`test/eidos/drawer/+page.svelte`):
- New "Exit tinting by causa" section with Save/Cancel/Fail buttons
that call dismissWith() through a local _dismiss-button.svelte
(same pattern as Dialog's).
Verification (playwright):
save t=83ms lastAction=saved hold=240ms (commit family)
cancel t=119ms lastAction=cancelled hold=240ms (emerge)
fail t=127ms lastAction=failed hold=600ms (alert)
Smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
d76f4eaf11 |
test/eidos: 2x2 grid with breathing room for tooltip + popover side demos
The previous layout (`repeat(4, 1fr)` with 0.5rem gap) packed the four sides in a horizontal row. Floating's collision detection saw the tooltip on `side='right'` had no room (the next button was 8px away) and shifted the placement, making the popover/tooltip appear far from its trigger. Switch to 2x2 grid with 4-7rem gaps and 3rem padding so each trigger has enough surrounding space for the tooltip/popover to render at the requested side without the floating layer flipping or shifting. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
5f6373e200 |
eidos/tooltip: recipe + per-side fade animations + demo
Translates `air/components/tooltip/tooltip.css` (small — 31 lines, only
Content + sizes styled) and adds:
- Arrow fill (matches tooltip bg)
- Per-side entrance/exit animations keyed off [data-side] (Floating
layer) and [data-state]. Distance is smaller than Popover (2px vs
4px) — tooltip is lighter visually.
- State handling for tooltip's three-value [data-state]:
'closed' → exit animation
'delayed-open' → entrance animation (hover after openDelay)
'instant-open' → no animation (keyboard focus, deliberate
invocation; the user wants it now, not after a
slide)
Demo (test/eidos/tooltip): 4-side grid + sizes (sm/md/lg).
Verification: smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
d31bbc23f0 |
eidos/popover: recipe + per-side fade/scale animations + demo
Translates `air/components/popover/popover.css` (small — 67 lines, only
Content + Close styled) and adds three eidos extensions:
1. Trigger envelope ([data-popover-trigger]) — air relied on the
Button recipe via .air-button class. Eidos applies a baseline
button-style envelope here, consuming Capa 2 directly.
2. Overlay fade ([data-popover-overlay][data-state='open'/'closed'])
— for modal popovers. The morfo declares Overlay as optional;
consumer renders it when modal=true.
3. Per-side entrance/exit animations keyed off [data-side]
(top/right/bottom/left) and [data-state]. Soma's Floating layer
writes data-side based on resolved placement. The popover slides
into place from the trigger edge — fade + small translate (4px)
+ scale (0.985 → 1). Air's "revelation" pattern: ~180ms entrance
/ 120ms exit.
Demo (test/eidos/popover): basic + 4-side grid. Each side button
anchors a Popover.Content with the matching `side` prop so the
animation direction matches placement.
Verification: smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
abf197a37c |
eidos/drawer: recipe + slide-in/out per side + demo (modal & non-modal)
Translates `air/components/drawer/drawer.css` to `eidos/components/
drawer.css` (mechanical: --air-* → --eidos-*, drop .air-* gates) and
adds the entrance/exit animations air's recipe was missing — air drove
drawer transitions through `air.semantic.contextIn/Out` Svelte
transitions, which don't exist in the morfo-driven path. Without them
the drawer snapped in/out instantly.
Per-side keyframes:
right → translateX(100%)
left → translateX(-100%)
top → translateY(-100%)
bottom → translateY(100%)
Plus overlay fade keyed off data-state. All ~240ms entrance / 180ms
exit (moderate / normal). The Presence layer waits for
getAnimations().finished so the content stays mounted during exit.
Demo (test/eidos/drawer):
- Modal section: 4 side buttons, render Drawer.Overlay (backdrop
+ focus trap, page non-interactive while open).
- Non-modal section: 4 side buttons, omit Drawer.Overlay + pass
modal={false}. No backdrop. Page behind stays interactive. Soma's
Dismissal layer auto-adjusts (interactOutsideBehavior default
flips from 'ignore' to 'close').
The Drawer morfo doesn't declare `events` yet, so no runtime.trigger()
cadence + no exit-tinting like Dialog. Recipe + animations only.
Verification (playwright probe):
top t=140ms translateY(-372) → t=406ms 0
right t=99ms translateX(452) → t=389ms 0
bottom t=94ms translateY(372) → t=390ms 0
left t=111ms translateX(-349) → t=428ms 0
Smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
b096237d3e |
soma/dialog: dismissWith(action) public API + tinted demo section
Expose a programmatic dismiss method on DialogProvider that fires the
matching close-* event through the runtime, and use it from the eidos
Dialog demo to make the exit-tinting visible.
Soma:
- DialogProvider.dismissWith('save'|'cancel'|'fail'|'dismiss'|
'dismiss-outside') maps to the corresponding morfo event
(close-save, close-cancel, close-after-fail, close-dismiss,
close-dismiss-outside) and fires runtime.trigger().
- DialogProvider class re-exported from $soma/components/dialog so
consumers can call DialogProvider.get() inside a child component.
Demo (test/eidos/dialog):
- New "Exit tinting by causa" section with three buttons that close
the same dialog via three different events:
Save → close-save → border-top tinted success (240ms hold)
Cancel → close-cancel → border-top tinted neutral (240ms hold)
Fail → close-after-fail → border-top tinted danger (600ms hold)
The 600ms fail hold is sema's family='alert' default — the dialog
stays open longer for the user to register the failure before close.
- Uses a small _dismiss-button.svelte child component that captures
DialogProvider.get() at component init time. The inline onclick
pattern (Dialog.DialogProvider.get()?.dismissWith(...)) doesn't
work from the parent route's scope because Svelte's getContext is
init-time-only — the route is the parent of Dialog.Provider, not
a child. The _dismiss-button is rendered inside Dialog.Content so
its init-time context lookup resolves correctly.
Verification (playwright probe):
save t=144ms lastAction=saved hold=240ms
cancel t=116ms lastAction=cancelled hold=240ms
fail t=117ms lastAction=failed hold=600ms (full alert family)
Smoke 68/68.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
bb8c8657e1 |
soma/dialog: soft-migrate close-* events through MorfoRuntime
Inject a MorfoRuntime side-channel in DialogProvider that fires the
prewrite → semantic.emit → handler cadence for the close-* events
declared in the dialog morfo. The legacy provider machinery
(assertProps + resolveMorfoProps + Provider class hierarchy) stays —
runtime is alongside, not replacing.
Wiring:
- DialogProvider creates the runtime in its constructor with
`events.{close-save, close-cancel, close-dismiss,
close-dismiss-outside, close-after-fail}` all delegating to
handleClose(). The runtime gets dom + semantic from soma + state
sources for `open` + prop sources for `disabled`/`modal`.
- DialogContentProvider registers the content part with the runtime
when its constructor runs, so trigger() can resolve
`event.target = partRef('content')` to a real DOM node.
- DialogTriggerProvider.onclick keeps direct handleOpen() — the
'open' event in the morfo targets content, but content isn't
in the DOM until AFTER open flips. Chicken-and-egg.
- DialogCloseProvider.onclick → trigger('close-cancel').
- DialogContentProvider's dismissal callbacks:
onEscapeKeydown → trigger('close-dismiss')
onInteractOutside → trigger('close-dismiss-outside')
What this unlocks:
Each close path now writes `data-last-action` (saved/cancelled/
dismissed/dismissed-outside/failed) BEFORE flipping data-state,
visible during the exit animation. The dialog recipe's tint rules
in eidos finally fire in vivo.
Verification (playwright check-eidos-recipes):
@8ms post-click lastAction=cancelled state=open (prewrite)
@241ms lastAction=cancelled state=open (signal hold)
@378ms lastAction=cancelled state=closed (exit)
@493ms opacity=0.017 (exiting)
The 240ms wait between click and exit is sema's family='emerge' hold.
The user can override per-event via `morfo.events[].hold` if 240ms feels
too long for a dialog cancel. Smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
084322d3db |
eidos/dialog: add [data-dialog-trigger] envelope (button-style)
Air's dialog relied on the Button recipe (`.air-button` class on
trigger). Eidos has no class composition, so the trigger was inheriting
only the archetype's cursor + focus ring, leaving it with the
browser-default button look.
Add a baseline envelope in dialog.css consuming Capa 2 tokens directly
(--eidos-color-{surface-raised, surface-overlay, border-default,
content-primary}, --eidos-control-height-md, --eidos-radius-md,
--eidos-space-3, --eidos-duration-fast). Hover and data-state='open'
both shift to surface-overlay. Disabled fades to 0.5.
Trigger isn't a separate component with its own Capa 3 tokens — it's
a part of Dialog using the system surface. Direct Capa 2 consumption
is appropriate here (same as archetype rules).
Verification: smoke 68/68, morfo-check 66/66; visual confirmed via
playwright screenshot — Open dialog/Open small/Open large render with
proper button envelope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
a50b18d1f1 |
eidos: drop legacy hand-rolled tokens.css, consume air-translated tokens
Replace the V1.5 hand-rolled motion/easing/tint/opacity/focus-ring
tokens with the air-translated equivalents already in eidos/themes/
base/_static.css and eidos/themes/base/light.css.
Substitutions applied to archetypes.css, events.css, components/
dialog.css:
--eidos-motion-fast → --eidos-duration-fast (120ms)
--eidos-motion-base → --eidos-duration-slow (320ms)
--eidos-easing-standard → --eidos-ease-default
--eidos-easing-decelerate → --eidos-ease-out
--eidos-easing-emphasized → --eidos-ease-spring
--eidos-tint-neutral → --eidos-color-neutral-element
--eidos-tint-affirm → --eidos-color-success-element
--eidos-tint-fulfill → --eidos-color-success-element
--eidos-tint-risk → --eidos-color-warning-element
--eidos-tint-threat → --eidos-color-danger-element
var(--eidos-opacity-hover) → 0.85 (single-site, archetype-internal)
var(--eidos-opacity-disabled) → 0.5 (single-site, archetype-internal)
Focus ring tokens (--eidos-focus-ring-{color,width,offset}) unchanged
— same names already exist in air's _static.css/light.css.
`affirm` and `fulfill` collapse to the same `success-element` because
air's intent vocabulary doesn't distinguish them. The morfo's
data-intent attr still preserves the distinction at the structural
layer; the visual mapping is just the same color.
Updated scripts/check-eidos-recipes.ts to navigate to the per-component
demo URLs (/test/eidos/{toggle,switch,dialog}) since the index page
no longer renders the recipes directly.
Verification: smoke 68/68, morfo-check 66/66. Playwright entrance/exit
curves intact (opacity interpolates over the configured durations).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
fa6b01c319 |
test/eidos: split into per-component demos with shared layout
Restructure the demos to mirror /test/soma's pattern: index page plus one demo per recipe, all under a shared layout that loads the eidos CSS chain and the Soma + App scaffolding once. test/eidos/ ├── +layout.svelte Soma + App + sticky nav bar ├── +page.svelte index (links + foundation explanation) ├── toggle/+page.svelte colors / sizes / variants / states ├── switch/+page.svelte colors / sizes / disabled ├── collapsible/+page.svelte ├── toast/+page.svelte fire by intent (5 intents) └── dialog/+page.svelte default + size variants Each demo exercises the variants air's CSS exposes (data-color, data-size, data-variant) by adding the attrs directly to the Provider element — they're a-la-carte since the morfos don't declare them yet. Future work: extend the morfos to declare these attrs as first-class props. Verification: all 6 routes return 200; smoke 68/68 (only covers /test/soma — eidos routes not in scope), morfo-check 66/66. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
b8dec62b35 |
eidos phase 2 batch 2: Switch, Collapsible, Toast translated
Switch (151 lines):
- mechanical: --air-* → --eidos-*, drop .air-* gates
- selectors match morfo as-is ([data-switch], [data-switch-thumb],
[data-state='checked'], [data-disabled])
- a-la-carte variants (data-color, data-size) dormant unless consumer adds
Collapsible (decorated parts only):
- mechanical translation
- dropped air's height-keyframes: they referenced
var(--terra-collapsible-content-height) (a JS-measured value from
the dead terra layer) and soma's Collapsible uses HTML `hidden`
(display:none) which kills CSS animations anyway. Left undecorated;
needs Presence layer integration in soma to enable real transitions.
Toast (substantive remap):
- selector remap due to part naming difference between air and morfo:
air [data-toast-toaster] → eidos [data-toast-viewport]
air [data-toast] (the card) → eidos [data-toast-item]
air [data-toast-message] → DROPPED (no equivalent — soma has
Title + Description direct, not a
separate "message" element)
air .air-toast-status → DROPPED (no status icon part)
air .air-toast-main → DROPPED (no content wrapper part)
- intent vocabulary remap (air data-type ↔ morfo data-intent):
error → threat
warning → risk
success → fulfill (and affirm — same palette)
info → neutral
- data-position on viewport stays a-la-carte (not in morfo)
Verification: smoke 68/68, morfo-check 66/66, focus rings + dialog
entrance/exit working per playwright.
Note: [data-dialog-trigger] has no explicit rule (air relied on
.air-button recipe). The trigger renders with browser defaults +
archetype interactive cursor. To be addressed in a follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
e48f227f89 |
eidos phase 2: Toggle + Dialog recipes translated from air
Replace the V1.5 hand-rolled recipes with the translated air originals
adapted to the morfo-driven architecture.
Mechanical translation:
- sed --air-* → --eidos-* (tokens) and --_air-* → --_eidos-* (private)
- drop `.air-{part}` class gates everywhere — eidos applies universally
to whatever data-attrs the morfo emits
Substantive adjustments per recipe:
Toggle:
- kept the existing morfo data-attrs ([data-toggle], [data-state='on'],
[data-disabled]) — match air's selectors directly
- air's a-la-carte variants ([data-color], [data-size], [data-variant],
[data-block], [data-icon-only]) stay dormant: not in toggle morfo,
only fire if a consumer adds them manually
- 229 lines of palette × variant × size variants vs 40 lines hand-rolled
Dialog:
- air's pattern: animation keyframes keyed off [data-state='open'/'closed']
(NOT Presence-layer markers); Soma's Presence waits getAnimations().
finished, so this works
- air ONLY animates the overlay — content snaps in/out instantly, sub-
perceptual. Added eidos-only @keyframes for content entrance/exit
(opacity + scale) so Presence has something to wait for and the user
can perceive the transition
- kept data-last-action exit tinting (border-top by causa during
closing animation) — air doesn't have this concept, dialog morfo
declares the prewrite that writes the attr before data-state flips
Verification:
- smoke 68/68, morfo-check 66/66
- playwright entrance: opacity 0 → 1 over ~120ms (air's overlay-in token)
- playwright exit: opacity 1 → 0 over ~280ms (air's overlay-out token)
- both interpolate, not sub-perceptual; user can tweak via tokens
Note: the existing eidos/tokens.css (legacy hand-rolled) and
eidos/components/{toast,switch,collapsible}.css (still hand-rolled)
continue to load alongside; phase 3 will translate the rest.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
0ebc9a9d83 |
eidos: phase 1 — translate air's foundation (contracts, tokens, themes/base)
Translates the foundation of air's visual system into eidos:
- eidos/contracts/ public token interfaces (4 root + 64 components)
- eidos/tokens/ per-component token values (62 components + motion)
- eidos/themes/base/ light + dark + _static + motion (4 files)
Mechanical translation: `--air-*` → `--eidos-*` everywhere, relative
imports preserved. The CSS is currently NOT wired into the legacy air/
folder, which stays for reference until phase 4 cleanup.
The `--eidos-*` prefix is documented in eidos/index.css with the two
reasons it earns its keep:
1. Authorship clarity at debug time (DevTools can identify the layer)
2. Override discipline (consumers know they're targeting public API)
Phase 1 only adds the foundation without touching component recipes.
The hand-rolled recipes (toast, dialog, toggle, switch, collapsible)
and the legacy hand-rolled tokens.css continue to load alongside the
new tree. Phase 2 will replace recipes with air-translated equivalents
and reconcile the duplicate motion/tint tokens.
Architectural note: air targeted terra and gated its CSS with
`.air-{component}` class on a wrapper. Eidos targets soma+morfo and
applies universally to the data-attrs the morfo declares — no class
gates, no Svelte wrappers. The morfo layer is the one that decides
which `data-*` attrs exist, which is the substantive shift between
the two systems.
Verification: smoke 68/68, morfo-check 66/66, focus rings + dialog
entrance/exit still working per Playwright check (curve unchanged).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
e4d131b58d |
sema + morfo: per-event hold override (declarative in morfo)
The hold of a component's event is now declared by the component itself,
not buried in sema's family fallback table.
Resolution precedence (in priority order):
1. signal.hold imperative, caller passes at emit time
2. morfo events[].hold declarative, component-level (this change)
3. sema family fallback 'emerge' → 'brief', 'alert' → 'noticed', ...
Sema's family table becomes the technical fallback when neither caller
nor morfo specifies. The numbers come from a single perceptual scale in
sema/durations.ts (subliminal/glimpse/brief/noticed/insistent/persistent),
so any change to the scale propagates to the family fallback automatically.
Public surface:
sema: SEMA_DURATIONS, SemaDurationLabel, SemaDurationSpec,
resolveSemaDuration() — exported from $uix/sema.
morfo: MorfoEvent.hold?: SemaDurationSpec — number or label.
soma: MorfoRuntime.trigger() reads event.hold, resolves the label →
ms via resolveSemaDuration(), passes it as signal.hold.
Tests:
- sema/durations.test.ts (4): scale values + resolver
- soma runtime (3 new): hold-less event sends no signal.hold (sema
falls back), numeric hold propagates, label hold resolves to ms.
Verification: sema 44/44, soma runtime 25/25, smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
25dbce936b |
eidos: dialog entrance/exit transitions (perceptual anchoring)
The recipe was missing transitions on the Presence-layer markers
(data-starting-style / data-ending-style). The dialog snapped into
view and snapped out — duration 0ms is sub-perceptual.
Add fade + scale on Content (320ms = --eidos-motion-base = "brief")
and fade on Overlay. Both interpolate via the Presence lifecycle:
open → element mounts with [data-starting-style] briefly →
CSS shows opacity:0 + scale(0.96) →
marker removed next rAF →
transition runs to opacity:1 + scale(1)
close → [data-ending-style] applied →
CSS animates opacity:0 + scale(0.96) →
Presence waits getAnimations().finished →
element unmounts
Verification (Playwright):
@8ms entrance: opacity 0.028, scale 0.972 (interpolating)
@80ms exit: opacity 0.46, data-ending-style still present
Both opacity AND transform animate so getAnimations().finished has
something to wait for in either direction. Easings: standard for
opacity, decelerate for transform (pops in cleaner).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
dce320d7ff |
eidos: focus ring uses box-shadow (project kills outlines globally),
dialog overlay self-positions
Two bugs surfaced by Playwright verification at /test/eidos:
1) Focus ring: archetypes.css used `outline: ...` for the focus-visible
ring. The project's layout.css has a Tailwind layer/components rule
`*:not(body):not(.focus-override) { outline: none !important }` that
kills every outline. The eidos rule was never visible.
Switch to a two-layer box-shadow ring (inner transparent gap +
outer color). Box-shadow follows border-radius, so the ring also
looks right on pills (Switch) and rounded corners (Toggle, Dialog
buttons). Ring also stands out against accent-filled states because
it sits OUTSIDE the element, not on top of it.
2) Dialog overlay: the recipe relied on [data-archetype='overlay']
from archetypes.css for `position: fixed; inset: 0`, but Dialog isn't
migrated to MorfoRuntime yet — its provider uses legacy createAttrs
which doesn't read morfo's `archetype` field. The overlay element
had `position: static, height: 0`, so the backdrop was invisible.
Inline `position: fixed; inset: 0` directly in the recipe. Component
recipes shouldn't depend on archetype rules for foundational behavior;
archetype is a transversal extra, not a foundation.
Adds scripts/check-eidos-recipes.ts — Playwright script that opens
/test/eidos, tabs into Toggle/Switch, opens the dialog, prints
computed styles + saves screenshots to g:/tmp/eidos. Used to catch
both bugs; reproducible for future visual checks.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
d682506df2 |
eidos: focus ring full opacity + darker hue for accent-bg contrast
The ring color was hsl(220 90% 55% / 0.6) — same hue as the default button accent at 60% alpha, so when the Toggle is in 'on' state or the Switch is checked (both accent-filled), the ring against the active background was almost invisible. Bump to hsl(220 95% 45%) full opacity. Slightly darker than the accent default, full alpha so it stands out against accent-filled states. Note: `:focus-visible` only fires on keyboard focus (Tab), not click — expected browser behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
375e769428 |
eidos: V1.5 — Toggle + Switch component recipes
Two trigger-family recipes. Both Provider parts carry data-archetype=
'trigger', so cursor + hover dim + focus ring come from archetypes.css
for free; recipes layer in component-specific visuals.
Toggle (`[data-toggle]`):
- button envelope (padding, border, radius, white bg)
- data-state='on' → filled accent (--eidos-toggle-accent default blue)
- data-invalid → red border (uses --eidos-tint-threat)
Switch (`[data-switch]` + `[data-switch-thumb]`):
- 44×24 pill track, padded
- data-state='checked' → accent track + thumb translateX(travel)
where travel = width - thumb-size - 4px (track padding × 2)
- thumb shadow, cursor inherited from track (overrides archetype's grab)
Eidos test page drops its :global([data-toggle]) / [data-switch]
scoped CSS — recipes now suffice.
Public CSS API:
--eidos-toggle-{bg, bg-on, color, color-on, border, border-on, radius,
padding, accent}
--eidos-switch-{track-width, track-height, track-bg, track-bg-checked,
track-border, thumb-size, thumb-bg, accent}
Verification: smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
14233af4a7 |
eidos: drop --_eidos-exit-tint indirection, inline in dialog recipe
The custom property was added in V1 anticipating multiple recipe consumers, but only Dialog uses last-action tinting. Premature abstraction — single-use indirection that obscures what's happening. Move the 5 selectors directly into dialog.css. events.css drops the machinery (replaced with a 3-line note pointing to the recipe). If a second consumer with the same exit-tint pattern shows up later, factor out then — not before. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
286191339e |
eidos: V1.5 — Dialog component recipe + last-action coverage
Second per-component recipe. Composes archetype interactive defaults
(trigger, overlay, close, content, focus-visible) with the visual
envelope (button shape, modal card, backdrop) and the asymmetric exit
tinting from events.css.
The recipe consumes `--_eidos-exit-tint` via a top accent stripe on
[data-dialog-content], so close-save tints fulfill, close-after-fail
tints threat, close-cancel/dismiss/dismissed-outside tint neutral. The
private var stays inside eidos — recipes consume it; consumers don't.
events.css extends the data-last-action coverage to dismissed and
dismissed-outside (both map to neutral tint, like cancelled).
Public CSS API: --eidos-dialog-{overlay-bg, overlay-z, content-bg,
content-radius, content-padding, content-shadow, content-max-width,
content-z, button-bg, button-bg-hover, button-border, button-border-hover}.
Default z-indices (overlay 50 / content 51) are sensible defaults the
integrator can override via tokens — Dialog without z-index would be
useless out of the box.
Test page (/test/eidos) gets a Dialog section so the recipe is visible
end-to-end.
Verification: smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
e69e0cba80 |
sema: namespace signal attrs under data-event-* (channel never touches state)
Supersedes the save/restore patch (
|
5 months ago |
|
|
b66039adb1 |
sema: VisualChannel preserves structural data-intent on cleanup
The channel writes `data-intent` from `signal.intent` during the hold,
but `data-intent` is also the structural attr the morfo of components
like Toast.Item writes from `v.propRef('intent')`. Removing it on
cleanup erased the structural value — the toast lost its tinting after
the announce signal expired.
Fix: save the prior `data-intent` value before overwrite, restore it on
cleanup. General principle — a channel should clean only what it wrote.
Other signal attrs (`data-event*`, `data-event-family`) are signal-
specific so no collision.
Tests: structural data-intent survives the hold; mismatched signal vs
structural intent (edge case) restores the structural value.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
4f0108f839 |
eidos: V1.5 — Toast component recipe
First per-component recipe in eidos/components/. Composes tokens + archetypes + intent palette into a finished Toast visual: - viewport stack layout (no positioning — integrator's call) - card with intent-tinted left border (reuses --eidos-tint-*) - absolute close button so layout doesn't depend on a header wrapper - typography for title / description, secondary-button style for action - swipe state (data-swipe='move'/'cancel') translates the card - loading state adds an accent stripe via inset shadow Toast.Item carries archetype='item' which sets cursor:pointer + user- select:none in archetypes.css; the recipe overrides those because a notification is passive, not selectable. Possible future morfo decision: introduce a 'card' or 'notification' archetype. The eidos test page drops its :global([data-toast-*]) scoped CSS — the recipe now suffices. Stale '~13 ms / 1 rAF' text replaced with the correct per-family hold (600ms for alert). Wired via @import './components/toast.css' from eidos/index.css. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
3d9a834886 |
docs: align cross-layer docs with channel-based Sema
Cleanup of stale references to the pre-refactor Sema model across architectural docs: - EngineSemantic → SemanticEngine (registry + dispatch) + VisualChannel (built-in materializer); engine no longer "depends on Dom" - "1 rAF + 1 hold frame (~13ms)" → perceptually-anchored hold (240ms emerge/commit/handle, 600ms alert/sustain, 120ms contact) - Promise semantics: sequential strict — cleanup BEFORE resolve, so structural commit lands AFTER perceptual feedback completes - Toast cadence trace updated: prewrite folded into VisualChannel.handle; CSS uses @keyframes (animation), not transition Touches: src/uix/README.md, src/uix/active_architecture.md, src/uix/sema/README.md, src/uix/eidos/README.md, src/uix/morfo/README.md, src/uix/soma/SOMA_ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
b4ceb1bb09 |
docs: SOMA_ARCHITECTURE — update sema timing reference after channel refactor
The "data-event*" bullet still mentioned the old "~13ms" timing from before the perceptual anchoring fix and the channel refactor. Updated to reflect current behavior: VisualChannel writes the attrs for a configurable hold (240ms emerge/commit/handle, 600ms alert/sustain by default per family, configurable per signal or globally). No code changes — single doc fix. |
6 months ago |
|
|
5871cbca6e |
sema: refactor into channel modules — engine becomes registry + dispatch
Reorganización estructural según el spec acordado: cada canal perceptivo
vive como módulo simétrico dentro de `chans/`. El engine queda mínimo
(registry + dispatch); la lógica de escribir `data-event*` al DOM,
mantener hold y retirar pasa al `VisualChannel`. La API pública que ven
los providers (`semantic.emit(signal)`) se mantiene idéntica.
Estructura nueva
src/uix/sema/
├── engine.ts registry + dispatch (ya no conoce DOM ni hold)
├── signal.ts SemanticSignal + nuevo campo opcional `id?`
├── exports.ts barrel actualizado (chans + drop publish + drop perception)
└── chans/
├── types.ts interfaz Channel { id, handle, dispose? }
├── visual.ts VisualChannel — escribe data-event*, hold, cleanup
├── visual.test.ts 9 tests del canal aislado
├── sound.ts SoundChannel placeholder (V1 no-op)
└── vibra.ts VibraChannel placeholder (V1 no-op)
Cambios al engine
- Constructor: `new SemanticEngine(opts?)`. `opts.visual` controla el
built-in (false / VisualChannelOptions / Channel custom). `opts.dom`
desaparece — ya no es responsabilidad del engine.
- Métodos: `register(channel)`, `getChannel(id)`, `emit(signal)`,
`destroy()`. Nada más.
- emit despacha a TODOS los canales registrados:
- Canales no-visuales (sound, vibra, futuros): fire-and-forget. Errores
se loguean pero no propagan (Sema es ornamental).
- Canal visual: el único cuya Promise se awaitea — comparte plano DOM
con el commit estructural posterior.
- emit genera el `id` de la ocurrencia y lo pasa a todos los canales,
garantizando coherencia cross-canal para tooling futuro.
Cambios al VisualChannel (lógica heredada del emit() anterior)
- 5 atributos: `data-event` + `data-event-id` + `data-event-phase`
(siempre) + `data-event-family` y `data-intent` (opcionales si vienen
en el signal). Los opcionales son la proyección al DOM de la metadata
semántica que Eidos consume.
- Hold defaults internos por familia (no exportados):
emerge/commit/handle: 240ms · alert/sustain: 600ms · contact: 120ms
Justificación técnica (rangos típicos de CSS transitions), no escala
perceptiva universal. Override per signal vía `signal.hold` o global
vía `new SemanticEngine({ visual: { defaultHold } })`.
- Semántica secuencial estricta: cleanup ANTES del resolve.
Eliminado
- `src/uix/sema/perception.ts` — escalas perceptivas no son concepto
cross-canal. La tabla de defaults por familia se movió al VisualChannel
como detalle interno con justificación técnica.
- `engine.publish()` y todo el sistema legacy:
- `SemanticEventDecl`, `SemanticComponentContract`, `SemanticPublishContext`,
`PublishedSemanticEvent`, `SemanticEventFilter`
- `onEvent()` y subscribers
- `applyPrewrites()` interno
- dependencia opcional de `ActiveDom` por construcción
- Tests de `publish()` reemplazados por tests del nuevo registry/dispatch.
Bootstrap (App + defaults)
- `src/lib/ext/app/app.svelte.ts:52` — `new SemanticEngine({ dom: this.dom })`
→ `new SemanticEngine()`
- `src/lib/ext/app/defaults.ts:45` — mismo cambio
Tests
- emit.test.ts ajustado: sin createActiveDom, sin SEMA_PERCEPTION/HOLD_DEFAULTS,
test "throws without dom" eliminado, números literales (240, 600).
Añadido test "resolves immediately when visual: false". 7 tests.
- engine.test.ts reescrito: register / getChannel / emit dispatch a múltiples
canales / id propagation / id override / fire-and-forget para non-visual /
error en canal no aborta dispatch / visual: false desactiva built-in /
custom Channel reemplaza built-in / destroy. 13 tests.
- chans/visual.test.ts nuevo: 5 attrs vs 3 / signal.hold respetado /
family default fallback / global default fallback / constructor defaultHold /
cleanup / sequential strict (resolve tras cleanup) / id field. 9 tests.
- 39 tests sema verdes en total.
Documentación
- src/uix/sema/README.md — reescrito alrededor del modelo de canales
- src/uix/eidos/events.css — comentario actualizado, referencia a chans/visual.ts
- src/uix/active_architecture.md — sección Sema reescrita
Componentes NO requieren cambios — la API `semantic.emit(signal)` es
idéntica externamente. Toast / runtime.trigger / providers funcionan igual.
Verificación
- 121/121 tests focused suite (sema 39 + adom + lib/dom + morfo + soma/morfo)
- 66/66 morfo-check
- 68/68 smoke routes
- svelte-check: 155 errors (baseline, sin nuevos)
Pendientes en doc del spec marcados como "no-objetivos":
- Implementación real de SoundChannel / VibraChannel (placeholders V1)
- Arbitraje propio del VisualChannel (deuda heredada del emit anterior)
- Cancelación si el target se desconecta durante el hold
|
6 months ago |
|
|
e664b59c96 |
sema: perceptually-anchored hold + sequential strict emit semantics
Two architectural fixes surfaced by the eidos demo:
1. The previous default hold (1 rAF + 1 hold frame, ~30ms) was a
technical timing decision — invisible to humans. Signals must be
anchored to perceptual scales so they can actually be perceived.
2. The previous lifecycle resolved emit AFTER 1 rAF (caller's structural
change ran in parallel with cleanup, ~30ms later). That's an implicit
parallelism between event and state. The author has no way to say
"wait for the announcement to be perceived before changing state".
Sequential strict semantics (NEW)
- emit writes the signal, awaits the full author-specified hold, cleans
up, then resolves. State change happens AFTER cleanup — strictly
sequential.
- `signal.hold` is in milliseconds anchored to `SEMA_PERCEPTION` scales:
subliminal 50ms below conscious threshold
glimpse 120ms minimum perceptible without effort
brief 240ms comfortable acknowledgement
noticed 600ms sustained signal
insistent 1200ms demands attention
persistent 3000ms until acknowledged
- Defaults per family in `SEMA_HOLD_DEFAULTS`:
emerge: brief sustain: noticed
contact: glimpse commit: brief
alert: noticed handle: brief
- Authors override per event via `SemanticSignal.hold` based on:
- their CSS animation duration (so it completes before cleanup)
- the perceptual scale they want
- critical attention requirements (use insistent/persistent)
Files
- src/uix/sema/perception.ts (new) — SEMA_PERCEPTION + SEMA_HOLD_DEFAULTS
+ SEMA_OBSERVE_FRAME_MS constants with rationale anchored on Bloch's
law, Material/Apple HIG motion guidelines, toast UX literature.
- src/uix/sema/signal.ts — `hold` JSDoc updated to explain ms semantics +
defaults + override pattern.
- src/uix/sema/engine.ts — emit() refactored from rAF chain to
setTimeout(holdMs); cleanup happens BEFORE resolve (sequential strict);
`nextFrame()` helper removed; `resolveHoldMs(signal)` added with the
3-tier fallback (signal.hold → family default → SEMA_PERCEPTION.brief).
- src/uix/sema/exports.ts — re-exports SEMA_PERCEPTION + SEMA_HOLD_DEFAULTS
+ SEMA_OBSERVE_FRAME_MS + SemaPerception type.
- src/uix/sema/emit.test.ts — rewritten around fakeTimers + setTimeout
(no more rAF queue); new test verifies cleanup runs BEFORE resolve.
7/7 tests green.
- src/uix/sema/README.md — new "Duración del signal" section with the
full perception scale + per-family defaults tables + override pattern.
- src/uix/eidos/events.css — comment updated to explain the new hold
windows; rule `[data-event^='dismiss']` etc. clarified.
- src/uix/active_architecture.md — replaced "~13ms" prose with explicit
perception-anchored scales; added explanation of "perceptual by design,
not framerate".
- src/uix/soma/components/toast/toast-provider.svelte.ts — new $effect
that fires `present` + `announce` events when item ref attaches
(one-shot guarded), so toasts emit signals on appear. The flash on
/test/eidos is now visible thanks to (a) the perceptual hold and (b)
the wired events.
Channel parallelism note
- Hold is the DOM channel duration only.
- Future sound / vibra / motion engines subscribe to sema events
independently; each owns its own timing relative to the event id.
- Sema does not coordinate cross-channel sync — that's an integrator
decision per app.
Verification
- 22/22 sema tests green.
- 22/22 morfo runtime tests green.
- 66/66 morfo-check + 68/68 smoke (verified before commit).
- Probe shows announce signal on toast appear lasting ~600ms (alert
family default) — visible in DevTools attribute panel.
- svelte-check unchanged at 155.
Pre-existing failure in src/uix/air/internal/canvas/layout.test.ts
(`bun:test` import) is in the dead-branch air layer and unrelated.
|
6 months ago |
|
|
44a499d052 |
eidos: V1 minimum viable — tokens + archetype rules + event reactions
First eidos code. Closes the cross-layer cycle that's been documented but
unproven: an actual CSS layer that consumes the morfo + sema contracts
and applies styles transversally without enumerating components.
src/uix/eidos/
- tokens.css — motion durations (quick/fast/base) + easings (standard /
emphasized / decelerate) + opacity scale (hover/disabled/faded) +
intent tints (neutral/affirm/fulfill/risk/threat) + focus ring tokens.
~30 lines. No theming dimension yet — when it arrives, these are the
base theme.
- archetypes.css — transversal selectors keyed by `data-archetype`.
Covers trigger, overlay, content, indicator, thumb, track, close,
action, item, option. Plus a universal `:focus-visible` ring rule that
applies to every archetype-classified element. Single attribute
selector specificity — components can override freely.
- events.css — reactions to perceptual signals during their ~13ms life.
`[data-event^="dismiss"]` fades out, `[data-event^="announce"]` pulses
with intent-tinted box-shadow, `[data-last-action]` tints exit
animation by causa.
- index.css — entrypoint that imports the above in cascade order.
Consumer: `import '$uix/eidos/index.css'`.
src/routes/test/eidos/+page.svelte (demo)
- Sets up Soma + App context inline (independent of /test/soma layout).
- Imports `$uix/eidos/index.css` and demonstrates the cross-layer story:
- Toggle + Switch buttons share trigger styles via `[data-archetype="trigger"]`
without per-component CSS.
- Collapsible expands/collapses with state-driven attrs.
- Toast intent buttons fire `data-event="announce" data-intent="risk|threat|affirm"`,
which eidos's events.css briefly tints via box-shadow during the signal window.
- Component-specific layout (positioning, switch thumb transform) lives in
the page's `<style>` block — eidos provides only the cross-component
baseline, NOT per-component recipes.
What this proves
- The runtime emits `data-archetype` correctly in browser (not just in
unit tests).
- A single CSS rule can target ALL triggers across all components
without enumeration. Add a new component with `archetype: 'trigger'`
and it inherits the styling automatically.
- Sema's `data-event*` signals live in the DOM long enough for CSS to
react (the ~13ms window measured by `scripts/probe-toast-dismiss.ts`
is observable as a brief box-shadow flash on toasts).
- The cross-layer cycle Morfo declara → Soma transcribe → Sema emite →
Dom aplica → Eidos lee runs end-to-end.
What V1 deliberately excludes
- Theming dimension (no `[data-theme]` overrides yet).
- Per-component recipes (each component will get its own CSS later).
- Keyframes / @starting-style / animation primitives.
- Sound / vibra (those are separate engines that subscribe to sema).
Verification
- /test/eidos returns 200; manual browser check shows triggers share
hover dim + focus ring across components, toasts flash intent tint on
appear.
- 68/68 smoke routes (smoke only scans /test/soma — eidos route is
outside that scope, manually verified).
- svelte-check unchanged at 155 (pure CSS additions).
|
6 months ago |
|
|
760575c419 |
docs: src/uix/active_architecture.md — cross-layer vision document
Synthesizes the four-layer architecture (morfo · soma · sema · eidos) into
a single readable doc. Where the per-layer READMEs are operational
references, this is the "north star" for understanding the why and how
the pieces articulate.
Structure (14 sections):
1. The thesis in one line — four layers with explicit contracts
2. The problem solved — what conventional frameworks mix together
3. The four layers — Morfo (DNA), Soma (behavior), Sema (semantic
channel), Eidos (visual)
4. How they articulate — the transcription chain (six pieces, one
responsibility each)
5. The causal chain of an interaction — concrete walkthrough of a Toast
dismiss with timing
6. The primitives that pass between layers — DOM attrs catalog +
archetypes + verbs vocabularies
7. Hard rules — the 12 invariants that keep the system coherent,
including the 2-of-3 rule
8. The autoría / transcripción distinction — the lens for deciding what
lives where
9. What this architecture is NOT — explicit non-goals
10. Current state (2026-04-26) — what's built, designed, pending
11. Acknowledged risks — over-layering, name discipline, responsibility
invasion, lack of precedent
12. Why it may be worth it — explainability, less drift, validation,
extensibility
13. The summary phrase — "Morfo declara · MorfoRuntime transcribe ·
Provider aporta · Effects sincronizan · Semantic emite · Dom aplica
· Eidos lee"
14. For deeper reading — links to per-layer docs
Also adds a top-of-page pointer in src/uix/README.md so the vision doc is
the obvious entry point for new readers.
No code changes — pure documentation.
|
6 months ago |
|
|
28f93ab089 |
docs: cross-layer articulation — eidos README + 2-of-3 rule across all layer docs
Closes the documentation loop on the cross-layer extension pass: morfo
now articulates between soma, sema, and (future) eidos. The "2-of-3 rule"
formalizes when an extension to morfo is justified vs when it should
stay as provider logic.
src/uix/eidos/README.md (new)
- Documents eidos's role and what it consumes from morfo + sema BEFORE
any code exists, so the contract is preparedly clean when implementation
starts.
- Catalogs which morfo fields eidos reads (parts, archetype, states,
data values, events, prewrite, focus, supportsNesting) and which it
ignores (computed state, runtime internals, layers).
- Documents the DOM-as-channel pattern: sema writes data-event* on emit;
eidos reacts to selectors like `[data-event^="dismiss"]`.
- Establishes the boundary with `air` (dead branch reference, not base).
src/uix/README.md (top-level)
- §8 Reglas de dependencia: adds the 2-of-3 rule table making the
morfo-extension contract explicit, plus a list of canonical vocabularies
(archetypes, verbs).
- §10 Reading order: includes eidos README + lib/dom + clarifies which
layers are dead branches.
src/uix/morfo/README.md
- New "Archetypes" section documenting the 24-verb vocabulary, the
Provider-as-trigger vs Provider-as-container distinction, and the rule
for adding new archetypes (≥2 components share the role).
- New "The 2-of-3 rule" section with the same table as the top-level,
listing which extensions did/didn't make it past the rule and why.
- `parts[].archetype` mentioned in the "What morfo contains" list.
src/uix/sema/README.md
- New "Vocabulario canónico de verbs" section listing SEMA_VERBS by
family and the `{verb}-{variant}` composite naming convention.
- Documents `validateEventName()` as advisory tooling.
src/uix/soma/SOMA_ARCHITECTURE.md
- partProps documentation now mentions data-archetype emission.
- New "Cross-layer hooks que soma emite por la regla 2-de-3" section
listing the data-* attrs soma writes that sema and eidos consume.
- Reading-order links updated.
No code changes — all docs.
|
6 months ago |