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-with-stumbles-s2-s...

14 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 touch words/ palabras/ chronos/ media-player (foreign WIP); commit per batch, git reset -q + stage only your own files; commit messages via -F file (backticks in -m run 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, deleted CONTINUE*.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-picker size clamped to PopoverSize (check 61→59).
  • onion-menu + surface-subtle bug (46d68ff2): default export; and --color-surface-subtle was 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.md from the consts (STUMBLES #1). New ARCHETYPE_DESCRIPTIONS in morfo/types.ts; docs-check I7 freshness guard (negative-tested); linked from README/CANON/morfo/component-audit.

NEXT — S2, S3, S4 ALL DONE; re-scope S5/S6 (and wire the Knob)

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): add object({ when: literal('part-absent'), part: string() }). The checkCondition part-reference validator (~L556–573) already covers it (it doesn't discriminate) — no change there.
  • src/uix/morfo/compile.ts (collectConditionDeps, ~L666–681): add the condition.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 and undefined when 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 NO emitEvent() 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-set fires on EVERY keydown (SliderThumbProvider.onkeydown L485–522) — no built-in throttle. Throttling is drag-only, via rAF (Drawer flushDragProgress ~88ms L604/624–648; Slider queueHandleDrag L244–260). The Slider sema cascade for handle-drag sets only channels:['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: sequence coincident vs post (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 with docs:check.

S4 — Gesture.rotate layer (STUMBLES #3, the flagged gap) — ✅ DONE (18b0d44e)

Shipped in src/uix/soma/layers/gesture/ as the fourth specialization (GestureRotate extends GestureBase): angleAbsolute (0°=up, CW+, [0,360), CSS-rotate convention), angleDelta (seam-unwrapped, multi-turn), angularVelocityDeg (radius-independent via a dedicated tracker fed the accumulated angle), AngularSnap detents (wrap-aware), dismiss-by-degrees, center: 'element'|{x,y}. 9 tests, 0 type errors in the layer. Deviations from the plan below (all deliberate): angle convention taken from the REAL Knob consumer (atan2(dx,-dy) not atan2(dy,dx)); referenceSize omitted (no meaning for a radius-independent angle); AxialDrag is in layers/manipulation not the gesture layer (plan conflated it); CW/CCW lock deferred as planned. Two release-time snap bugs found by adversarial review were fixed pre-merge (px→deg projection scale + capped below a half-turn; sequential guard made circular under wrap). A real consumer now exists AND is wired: the Fable-built Knob was relocated out of src/uix/active-uix/knob/ into its per-layer homes (morfo/langs/sema/soma/eidos) and rewired to consume Gesture.rotate (c13cad74): 162→59 type errors (0 in any knob file), 4 provider tests, no regressions; morfo now uses the S3 part-absent for aria-label; knob added to recipe-css-contract WIP_TRACKS until its recipe tokens land (css still md-only, no variants — the remaining polish). Original plan below.

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: add RotateState (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 via atan2(dy,dx) normalized [0,360); unwrapDelta for 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; snapToAbsoluteDegrees
    • resolveSnapAt with the sequential-neighbour guard (|i-current|>1 blocked, like AxialDrag). Export Gesture.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.listen like 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 morfo cssVars declaration 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), role on a DOM-rendering Provider (required vs omittable), Without<>/PrimitiveDivAttributes definitions, 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_MORFO in 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).

Powered by TurnKey Linux.