You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/process/continue-runed-tabbable-por...

7.3 KiB

Continue — runed + tabbable → own libraries (0-dependency)

Status 2026-07-03: F1 + F2 + F3 DONE and verified. The port is complete — runed + tabbable are fully removed and reimplemented in-house. (F1+F2 committed as d0f41fb2; F3 commit follows.) Goal: remove the two runtime deps that break the "framework depends on nobody" doctrine — runed@0.37.1 and tabbable@^6.2.0 — by porting them into our own code (same move as float → $ethereal). Decision: port the whole runed library, not just what we use, but fold into existing homes (no parallel API) and rebuild DOM-reactive utilities on ActiveDom (window-correct, never defaultWindow).

Locked architecture (where each piece lives)

Family Home Notes
Pure reactive runes (no DOM) $libs/reactive grows the existing alias
DOM-reactive runes $adom (src/arts/adom) rebuilt on ActiveDom, pattern { dom } like FocusScope
Utilities that already exist in $adom reuse, don't duplicate listen=useEventListener, observeResize=useResizeObserver, observeMutation=useMutationObserver, observeIntersection=useIntersectionObserver, activeElement, requestFrame=rAF
tabbable focus engine $libs/dom/tabbable-core.ts consumed by existing tabbable.ts wrapper, surfaced via $adom
PersistedState $storage (src/arts/storage) localStorage/sessionStorage
useSearchParams + kit subexport DROP app is adapter-static, SvelteKit-coupled
boolAttr already exists as boolToEmptyStrOrUndef in $libs/dom/attrs.ts — do NOT re-port
extract / MaybeGetter / get reuse framework toValue / MaybeActiveOrGetter / Getter

DONE — F1 (dropped the deps) + F2 (pure runes)

F1 — created: src/libs/dom/tabbable-core.ts, src/libs/reactive/watch.svelte.ts (watch/watch.pre/watchOnce), src/libs/reactive/context.ts (Context), src/arts/adom/element-size.svelte.ts (ElementSize, rebuilt window-correct). Rewired ~33 watch + 3 Context imports (runed → $libs/reactive), floating (ElementSize → $adom), focus-scope (tabbable → $adom). Removed both from package.json.

F2 — created in $libs/reactive: previous, is-mounted, on-cleanup, interval (was useInterval), debounce+Debounced (was useDebounce), throttle+Throttled (was useThrottle), state-history, finite-state-machine, resource. Renamed the use* prefix away (user OK'd). Added Setter<T> to types.ts. Barrels updated: $libs/dom/index, $libs/reactive/index, $adom/index.

DONE — F3 (DOM-reactive runes → $adom)

Refined the approach from the original { dom } idea to follow the committed ElementSize precedent: each utility is window-correct by resolving the window from its node via getWindow(node) (node-bound) or an optional window/document option (global-scope ones) — no threaded dom param, no runed defaultWindow. Raw observers/listeners live in the arts/adom implementation layer (allowed there, like active-dom.svelte.ts itself). All 11 ported, type-clean (check=59), server tests green (barrel loads). PersistedState NOT ported — the $storage art already replaces it (its README names runed/PersistedState as the thing it supersedes), same call as boolAttr→boolToEmptyStrOrUndef.

runed utility target file maps onto notes
ElementRect $adom/element-rect.svelte.ts observeResize + observeMutation like ElementSize; getBoundingClientRect via dom
ActiveElement $adom/active-element.svelte.ts listen(focusin/focusout) + getActiveElement (already in $libs/dom/core, shadow-piercing) createSubscriber
IsFocusWithin $adom/is-focus-within.svelte.ts ActiveElement + node.contains
IsIdle $adom/is-idle.svelte.ts listen + debounce (F2) events on window/document
IsDocumentVisible $adom/is-document-visible.svelte.ts listen(visibilitychange)
IsInViewport $adom/is-in-viewport.svelte.ts observeIntersection
onClickOutside $adom/on-click-outside.svelte.ts listen(pointerdown/click/blur) + debounce + watch biggest; iframe detection; getActiveElement/isOrContainsTarget already in $libs/dom
PressedKeys $adom/pressed-keys.svelte.ts listen(keydown/keyup/blur/visibilitychange) createSubscriber
ScrollState $adom/scroll-state.svelte.ts listen(scroll/scrollend) + AnimationFrames + debounce large; getComputedStyle via dom.getWindow
TextareaAutosize $adom/textarea-autosize.svelte.ts observeResize + watch + tick creates hidden textarea; use dom for document
AnimationFrames $adom/animation-frames.svelte.ts dom.requestFrame/cancelFrame loop fps-limit, pause/resume, reactive fps
PersistedState SKIPPED already in $storage createActiveStorage+localAdapter/sessionAdapter/broadcast supersede it

Still open (optional, not blocking): fold the useful runed extras into $adom's existing observer methods where they add value (not new API): once/pause/resume + Firefox threshold fix in observeIntersection; takeRecords() in observeMutation.

How to re-obtain runed source (pruned from node_modules)

runed is gone from node_modules. To read source again: npm install runed@0.37.1 --no-save (does NOT touch package.json), read node_modules/runed/dist/utilities/<name>/<name>.svelte.js (+ .svelte.d.ts for types) and internal/utils/*. Gotcha: the --no-save reinstall + the other chat's running vite dev server caused an EBUSY lock and a transient svelte-check crash; fix with npm install then rm -rf node_modules/runed node_modules/.runed-*. GitHub mirror: github.com/svecosystem/runed/tree/main/packages/runed/src/lib. (A full F3 source dump may still exist at the previous session's scratchpad/runed-f3-source.txt, but scratchpad is session-scoped — don't rely on it.)

Verification baselines

  • npm run check = 59 errors — all pre-existing (connection, nav-menu, time-picker, file-upload, editable/grid-list tests, web/routes demos, temas). 0 in ported files. Any of MY files appearing = regression.
  • npm run test = 3920 pass / 24 fail — all 24 pre-existing WIP: contracts (13, menu-dial half-built), range-calendar (7, "incidencia 2026-05-20"), morfo compile (1), chronos (1), calendar (1), soma-attr-audit (1, 5s timeout flaky).

Gotchas (carry into F3)

  • toValue + union types: toValue's overloads don't unwrap a union-typed MaybeActiveOrGetter<T> arg (it picks the T→T overload and returns the union). Cast the result: toValue(x) as number / as T. Do NOT modify toValue (684 consumers).
  • Prettier per subtree: $libs/dom and arts/adom's active-dom.svelte.ts are written without semicolons and already fail prettier --check pre-existingly; $libs/reactive + arts/adom newer files use semicolons. Write new files with semicolons and run prettier --write on them (they'll be compliant even if some neighbors aren't).
  • Layer rule: soma/components import DOM only via $adom, never raw npm or $libs/dom. Reactive utilities via $libs/reactive.
  • .svelte.ts extension required for any file using runes ($state/$derived/$effect).

Memory pointer

memory/project_runed_tabbable_port_2026-07-03.md has the full running log.

Powered by TurnKey Linux.