12 KiB
Continue with — STUMBLES S2–S6 + catalog coherence (hand-off 2026-07-03)
Kickoff for a new session: "Lee docs/process/continue-with-stumbles-s2-s6.md y continúa." Everything below is verified against the code at commit
116594e0. Working rules unchanged: respond in Castilian, docs/comments in English; NEVER touchwords/palabras/chronos/media-player(foreign WIP); commit per batch,git reset -q+ stage only your own files; commit messages via-F file(backticks in-mrun command substitution — see the memory note). Each systemic fix ships its guard.
Where things stand (verified)
component:audit: 82 PASS / 48 NEEDS-WORK / 0 BROKEN. The remaining NEEDS-WORK is almost all docs completeness (E-2.3 missing README, F-1.x sections) and demos (D-*, deferred to the web phase) — NOT functional.npm run check: ~64–65 errors, ALL pre-existing/foreign (heroscrolling + animation demos, arts/connection, libs/reactive — the parallel runed/tabbable port drifted the tree; my baseline earlier this session was 59). None in the maintained catalog files I touched. Confirm with a stash-test if a number moves: my changes were net-neutral-to-negative on the count.npm run docs:check: 0 errors, 11 warnings (all foreign/known targets).vitest src/uix/eidos: 289/289 green (the long-red suite was triaged).- Uncommitted foreign WIP in the tree (do NOT commit): the runed/tabbable
0-dep port —
src/libs/dom/*,src/libs/reactive/index.ts,src/arts/adom/index.ts,package-lock.json, deletedCONTINUE*.md/src/audit-opus-4-6-26.md. Another session owns these.
Done this session (committed, on alpha-0.1-sec-dom)
- C7 size-bundle (
6001d9bb,e83ca6ea): 34 recipes consume the bundle coordinate; guard forbids the raw primitive; theming/notes size-canon row ⚠️→✅. - Eidos test triage (
bbd9c467): 15 long-red tests → 289/289. Real fixes inside: pin-input readonly-bg declared, 5 dead tokens removed, time-pickersizeclamped to PopoverSize (check 61→59). - onion-menu + surface-subtle bug (
46d68ff2): default export; and--color-surface-subtlewas a PHANTOM token → table/tree-grid headers rendered with no background → repointed to--color-surface-muted(verified in browser). - Functional flags + C10 reframe (
730812c0): 4 service E-2.2 exceptions (format-date/number, relative-time, trans), alert-dialog R-1.1, card A-1.3. C10 reframed: tier = audit verdict — stable=PASS (82), preview=NEEDS-WORK on docs/demo only (~35, incl. mature button/table — a README gap is not immaturity), experimental/WIP=real functional gap or foreign. C10 folds into C8. - S1 — generated vocabularies appendix (
116594e0):npm run docs:vocabularies→docs/canon/vocabularies.mdfrom the consts (STUMBLES #1). NewARCHETYPE_DESCRIPTIONSin morfo/types.ts; docs-check I7 freshness guard (negative-tested); linked from README/CANON/morfo/component-audit.
NEXT — implement S4 from this VERIFIED plan (S2, S3 DONE)
A read-only scoping workflow (scope-stumbles-s2-s6) mapped each against the
real code. S2/S3/S4 returned high-confidence plans (below). S5 and S6 did the
research but did not emit a structured plan — re-scope them first (relaunch
the workflow for just those two, or scope by hand; their prompts are in the
committed workflow script …/workflows/scripts/scope-stumbles-s2-s6-*.js).
S3 — part-absent morfo condition — ✅ DONE (11504043)
Shipped as { when: 'part-absent', part }, the exact inverse of part-present,
across the four condition sites (types union + JSDoc, schema discriminated
member, compile collectConditionDeps, resolver shouldEmitMorfoEntry) plus
schema/compile tests and the morfo.md condition list. One deviation from
the plan below: the plan claimed checkCondition "already covers it (doesn't
discriminate)" — it DOES discriminate (schema.ts had c.when === 'part-present'
hard-coded), so the unknown-part guard was extended to part-present || part-absent
so an unknown-part part-absent throws too. npm run check = 59 errors (none
mine); the pre-existing red morfo test hoists role into staticAttrs (Dialog
content role intentionally undeclared, dialog-001) is unrelated → flagged
separately. Original plan preserved below for provenance.
The common ARIA pattern "aria-label only when there is no Label part" is not
declarable. Add the inverse of part-present. Four surgical edits + a test,
mirroring part-present exactly:
src/uix/morfo/types.ts(MorfoCondition union, ~L169–175): add{ when: 'part-absent'; part: string }; update the JSDoc.src/uix/morfo/schema.ts(conditionObjectSchema discriminated union, ~L143–153): addobject({ when: literal('part-absent'), part: string() }). ThecheckConditionpart-reference validator (~L556–573) already covers it (it doesn't discriminate) — no change there.src/uix/morfo/compile.ts(collectConditionDeps, ~L666–681): add thecondition.when === 'part-absent'branch →partRefs.add(condition.part)(same as part-present).src/uix/morfo/resolver.ts(shouldEmitMorfoEntry, ~L39–50): add the branch →return !Boolean(bindings.parts?.[condition.part])(inverted part-present at L44).- Test (
compile.test.ts/ runtime test): schema accepts it; unknown-part throws; compiler collects the dep; resolver returns the value when the part is absent/falsy andundefinedwhen present. Integration: a Dialog-like aria-label gated on{ when: 'part-absent', part: 'label' }. - Risk: semantics are "part not in bindings.parts OR falsy value" (not strictly DOM-absent). Verify provider init populates the parts map for every part (even optional-unrendered) so absence is unambiguous. Non-breaking (no existing morfo uses it).
S2 — "Continuous components" doctrine in docs/architecture/sema.md — ✅ DONE (1da36ca6)
Shipped as a new ## Continuous components (drag, swipe, hold) section after
the emit contract (before ## The per-family intent policy), covering: the
single runtime.trigger door + void vs await (hold opt-out/in), coincident
vs post for a moving value (Slider worked example), rAF throttling of
continuous pointer emits vs unthrottled keyboard commit-set, and "a continuous
event's cascade rule must not override the primitives the per-emit overrides
control". Every claim adversarially fact-checked against the Slider/Drawer
providers + morfo + sema pack (zero discrepancies). docs:check = 0 errors.
Original plan preserved below for provenance.
Pure doc addition, grounded in the REAL Slider/Drawer code (verified answers to the three open questions):
- (a) turn hold:
runtime.trigger()returns a Promise that resolves AFTER emit + the perceptual hold (runtime.svelte.ts:742–749, 765–768).void trigger()= fire-and-forget;await trigger()blocks until the hold ends. - (b) sanctioned path: ONLY
runtime.trigger()— there is NOemitEvent()in the codebase. Slider uses trigger exclusively (handle-pick L297, handle-drag L263/322/330, commit-set L155); Drawer likewise (drag-start/progress/end L661/645/679). - (c) throttling: keyboard
commit-setfires on EVERY keydown (SliderThumbProvider.onkeydownL485–522) — no built-in throttle. Throttling is drag-only, via rAF (DrawerflushDragProgress~88ms L604/624–648; SliderqueueHandleDragL244–260). The Slider sema cascade for handle-drag sets onlychannels:['sound','haptic']and does NOT override pitch/gain, so per-emit overrides stay the source of truth (sema/components/slider.ts:30–32). - Deliverable: new
## Continuous components (drag, swipe, hold)section after "## The emit contract" (~L121) covering: sequencecoincidentvspost(perceptual+structural indivisible vs commit-then-signal), route-through-trigger doctrine, rAF throttling for continuous gestures, and "a cascade rule must not override the primitives that per-emit values control". Slider = worked example. Keep prose on the invariant ("what the Promise represents"), point to code for tuning constants (don't hard-code ms). Verify withdocs:check.
S4 — Gesture.rotate layer (STUMBLES #3, the flagged gap)
New angular gesture layer mirroring the existing ones in
src/uix/soma/layers/gesture/ (GestureBase → GestureDrag/GestureResize;
AxialDrag is the single-axis peer). Consumers: Knob, AngleSlider, hue ring,
clock dial.
layers/gesture/types.ts: addRotateState(angleDelta°, angleAbsolute 0–360, angularVelocityDeg),RotateReleaseState(+snappedTo, dismissed),GestureRotateOpts(extends GestureBaseOpts:center(Active<{x,y}|'element'>),referenceSize,direction?,snapPoints?,snapToSequential?,dismissDegrees?,velocityThreshold?,wrap?=true, onRotate/onRelease/ onDismiss/onSnapChange),GestureRotateState,AngularSnap(number = 0–1 fraction of 360 / >1 degrees /'45deg'/'45°'/'50%').layers/gesture/gesture.svelte.ts:GestureRotate extends GestureBase. Center detection ('element'→ getBoundingClientRect center vs explicit coords); angle viaatan2(dy,dx)normalized [0,360);unwrapDeltafor the 0/360 crossing (|delta|>180 ? delta∓360); velocity on the unit circle (VelocityTracker, 100ms window / 50ms staleness — reuse the existing tracker); override handlePointerDown/onMove/onRelease/onCancelled;snapToAbsoluteDegreesresolveSnapAtwith the sequential-neighbour guard (|i-current|>1blocked, like AxialDrag). ExportGesture.rotate(opts)matching drag/resize.
layers/gesture/index.ts: export the new types.- Test (
gesture.test.ts): center detection; angle unwrap across 0°/180°; velocity (stationary/flick-capped/stale); snap (closest, sequential guard, velocity projection); dismissal by absolute offset; single-pointer only. - Risks: 0/360 unwrap correctness (normalize delta to [-180,180] before
distance in snap, esp. with wrap so 350°↔10° are adjacent); angular velocity
via unit-circle derivative underestimates at large radii; no CW/CCW direction
locking in v1 (defer). All dom listeners via
dom.listenlike the peers.
S5 / S6 — re-scope, then implement
- S5 (soma→eidos CSS-var contract, STUMBLES #7): the agent researched
--drawer-progress-style vars but didn't emit the plan. Re-scope to get 2–3 concrete options (e.g. a morfocssVarsdeclaration validated against provider writes + eidos reads; or an eidos-lint cross-check of provider-written vs recipe-consumed vars) with a recommendation. Design-open — get the tradeoffs before implementing. - S6 (minor frictions): four doc notes to author from the real defs —
state<T>()vs$state(pass-by-ref vs local),roleon a DOM-rendering Provider (required vs omittable),Without<>/PrimitiveDivAttributesdefinitions, gesture pointermove/up ownership. Land them in soma.md / soma-architecture.md.
After the S-series — remaining coherence plan
docs/process/PLAN-component-coherence.md is the master. Left:
- C8 (heaviest): 28 missing READMEs (E-2.3) + incomplete F-1.x sections. This is where the deferred exceptions land: announce/clipboard R-1.5 (live-region / composed-focus), natural-time-picker R-2.1 (fixed-tone sky) + A-3.1, card/card-group/drag-drop/virtual-list. Write real Baseline/Comparativa/ Decisiones/Gaps (per-component reference research) — no skeletons.
- C9: motion eventful gap (which interactives should express an event signature; criterion: high-frequency = sober, D.5).
- Real gaps tracked, NOT silenced: float-panel A-3.7 (28 mutating
keyboard actions, 6 events — morfo-completeness question); card-group has
no morfo (morfo-first violation,
KNOWN_MISSING_MORFOin lint.test.ts); natural-time-picker A-3.1.
Verify-before-done checklist (unchanged doctrine)
npm run check (compare to the drifting foreign baseline, not 59) · relevant
vitest scope · component:audit --only {kebab} per touched component ·
npm run docs:check after doc/vocab changes · browser check for anything
visual (Playwright from repo-root; the preview MCP is flaky — memory note).