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