The in-house positioning engine (relocated to its own art in 58683b03) is
renamed floating -> ethereal: src/arts/floating -> src/arts/ethereal, alias
$floating -> $ethereal (vite + svelte config), all soma wrappers + the demo
repointed. The reactive wrappers stay in soma/layers/floating (soma's
floating-overlay concept); the *library* is ethereal.
Also lands the cross-browser validation + perf characterization the rename
batch was verified against:
- engine-dom parity suite runs on Chromium + WebKit + Firefox via
UIX_CROSS_BROWSER=1 (env-gated in vite.config; default chromium). 411 runs
green — exercises the isWebKit-gated paths Chromium never ran.
- PERF.md: honest write-up. Bundle measured (own 6.1 KB vs @floating-ui
8.2 KB gzip -> ~2 KB net win). Speed numbers labelled CPU-work, NOT
perceived wall-clock; the +1-frame latency documented as a real cost.
- web/routes/demos/ethereal: visual A/B demo vs @floating-ui.
@floating-ui untouched — flag still OFF, P4 not executed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The positioning engine is pure collision geometry over `$adom` — a runtime
artifact, not soma-specific. Moves it out of `soma/layers/floating/engine/` into
a new `arts/floating` art (`$floating`), exactly parallel to `$motion`: both soma
(the JS positioning path) and eidos (the CSS-anchor path, future) build on it,
so the shared pure core belongs in arts, not buried in one consumer.
Moved to `$floating` (git renames, history preserved): geometry, rects, clipping,
overflow, supports, compute, auto-update, flag, types, the 7 middleware, and the
two parity test suites. `placement.ts` and the contract types `Measurable` /
`Middleware` / `MiddlewareData` move too — `$floating` is now self-contained
(depends only on `$adom`, never back on soma). New `arts/floating/index.ts` barrel
is the public surface. Alias `$floating` added to vite.config.ts + svelte.config.js.
Stays in `soma/layers/floating/` (reactive composition): use-floating.svelte.ts
(runes), floating.svelte.ts (providers/context), shell.ts, safe-polygon.ts,
utils.ts, the reactive types, index.ts. These now import `$floating`; soma's
types.ts/index.ts + soma/types/index.ts re-export the placement/contract types.
Two latent type gaps the typed `$floating` surface exposed (the soma loose-factory
shim had hidden them) are fixed: `DetectOverflowOptions` now declares `boundary`
(the middleware genuinely accept it; compute's read phase extracts it), and a
`size` test's `apply` returns void.
`@floating-ui` is UNTOUCHED — P4 deliberately NOT executed: the dep stays installed,
the fui imports + the `USE_OWN_ENGINE` flag (still OFF) remain in the wrappers, the
runtime still positions via floating-ui. Verified: 350 synthetic + 137 real-DOM
parity cases green from the new location, soma overlay providers green, check clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add `arts/perf` — a dev-only forced-reflow detector on the Long Animation Frames
API. Turns Chrome's opaque "[Violation] Forced reflow while executing JavaScript
took Nms" into an attributed report: which script forced how much synchronous
style+layout (`forcedStyleAndLayoutDuration`). It catches the actual runtime bug
regardless of static pattern — what a grep guard can't do (the codebase has ~120
legitimate layout reads across ~48 components; the fault is the temporal
sync-read-after-write ordering, not the read itself).
`createActivePerf({ threshold, onReport, log })` owns the only PerformanceObserver
the framework creates; inert where LoAF is unsupported (non-Chromium). Discoverable
as `uix.perf`, opt-in via `createActiveUix({ reflowDetector: import.meta.env.DEV })`;
`ActivePerf` (stateful → Active*) is disposed by the composition root.
- src/arts/perf/{types,active-perf,index}.ts + README + 6 tests
- $perf alias (vite.config.ts + svelte.config.js)
- ActiveUix.perf getter + reflowDetector option
- disabled-dom stub gains measure() (completes the dom.measure interface)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First consumer of uix.color. The theme generator's on-solid text pick
(white vs dark) now decides by APCA (|Lc| >= 60) instead of WCAG 2
(< 3:1), with a WCAG 2 ratio kept as a conservative cross-check — white
must clear BOTH or the contrast slot flips to onSolidContrast. APCA is
accurate in the mid-tones where WCAG 2 mis-estimates (the risk=orange
case). Reproduces the documented base behavior (only risk flips) via a
better metric; generated/base.css unchanged (the pick lives in the
runtime theme block).
- render-css: import apcaLc / oklchToGammaRgb / safeParseColor /
wcagContrastRatio from $color; replace the local WCAG pick; drop the
now-orphaned local wcagRelativeLuminance + wcagContrastRatio.
- color: add safeParseColor (null instead of throw for var()/color-mix
theme values the engine can't introspect).
- wire $color alias (vite.config.ts + svelte.config.js + CLAUDE.md).
Verified: color 20/20, eidos 162/165 (3 pre-existing words failures),
active-eidos-config contrast asserts pass, npm run check 0 new errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Relocate the motion runtime out of eidos into a runtime art (src/arts/motion,
$motion), exposed as uix.motion and consumed by BOTH soma (Presence.motion ->
motion.run) and eidos (delegates + registers presets) -- dissolving the
soma->eidos coupling. Remove DialogProps.runMotion / eidos.motionRunner; the
bridge is now EngineMotion.run (reads data-animation-style). Delete the 4
relocated dead files (lib/motion/{types,runtime,runner,presets/js}.ts); the
preset DATA (presets/css.ts) stays in eidos. Regenerate generated/base.css.
F6 - token rigor (Carbon): tokenize the raw firma durations (slower/deliberate/
emphatic/sustained holds, escalating by announce intent severity), add
--motion-distance-xl (30px shared-axis), --motion-scale-through (0.92), the
emphasized easing, and productive/expressive sets ([data-motion-set=expressive]).
F7 - extensibility + typegen: app-extensible, type-safe preset-name registry
(EidosMotionPresets, mirroring SemaChannelSignatures); MotionPresetName =
keyof EidosMotionPresets | none | (string & {}).
Also sweeps other in-progress working-tree edits (web/routes/temas/grafito).
Verify: npm run check -> 1 pre-existing error (grafito), 0 new; motion 22/22,
active-uix 25/25, Presence 2/2, Dialog 3/3. Pre-existing words-track failures
unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
svelte.config.js / vite.config.ts:
- Drop dead aliases: \$lib, \$glob, \$langs, @/ling, @/logr, @/glob,
@/actx, @/uiux, \$terra (their target dirs were removed in phase B).
- Add \$reactive at the top level (was previously hidden under the
"legacy" block; the target src/libs/reactive is current).
- Set kit.files.routes to "web/routes" — demos move out of src/ and
will be re-authored from scratch alongside the new architecture.
- vite.config.ts: extract the alias map into a single `aliases` const
shared by both Vite resolve and the server-test project, instead of
duplicating it.
web/routes/+layout.svelte + +page.svelte: minimal SvelteKit boot so
`npm run dev` and `npm run build` find a routes tree. Real demos
(active-uix, soma, morfo, sema, eidos) will be added as sibling routes
once the runtime is wired.
Verified: `npx svelte-kit sync` regenerates .svelte-kit/tsconfig.json
without any of the dead aliases. .svelte-kit/types/web/routes/ is
populated, confirming the new routes path is active.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
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>
soma — new headless component system replacing terra:
- Provider base class with opt-in context injection
- Reactive system (state, readableActive, writableActive)
- Attrs system (createAttrs, contracts, helpers)
- Props system (mergeProps with on-prefix detection, no hardcoded event list)
- DOM utils (consolidated core, focus/, env)
- Keyboard, events, CSS, ID, config modules
- Layers: scroll, observers, visually-hidden, arrow, portal, text-selection,
dismissal (merged escape+dismissible), presence, focus-scope
- Collapsible component as first soma validation
air components:
- Pagination, Editable, PinInput (air-native), RatingGroup (air-native),
Stepper, FileUpload wrappers over terra
- Terra defaultContent refactor for children snippet pattern
- Avatar, Meter, Progress, Separator migrated to air-native
- Reactive utilities extracted to $lib/util/reactive (shared)
- $soma and $reactive aliases added
Architecture:
- soma is independent: no imports from terra or $lib/util/reactive
- Terra exports deprecated for migrated components
- README fundacional documenting all design decisions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add comprehensive library system for internationalization, logging,
formatting, and UI state management:
- ling: i18n library with translation keys, plural rules, and JSON serialization
- logr: logging system with levels, filters, history, and transports
- glob: number (numr), date (dati), currency (curr), and unit (unit) formatters
- actx: application context integrating all modules with Svelte 5 reactivity
- uiux: reactive UI state for theme, toasts, modals, menus, and breakpoints
Features:
- 247 passing tests across 4 test suites
- Svelte 5 runes ($state) for reactive context
- Lazy initialization for optional modules
- Type-safe configuration with TypeScript
- Locale-aware formatting using Intl APIs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>