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...

220 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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.