makepad-ports/plans/000-craft-crate.md
andodeki 3cfeaa289c Add implementation plans for makepad-ui-craft (Emil's improve-animations plan format)
plans/README.md: index, execution order 000->005, executor ground rules (test gate, DSL landmines)
000: shared craft crate (easing/duration/color tokens, Pressable, CraftToast, motion math + units)
001: HIGH press contract - feedback on down, commit on up-if-over, 0.96 scale (all 3 crates)
002: MEDIUM toast enter/exit, interruptible, reduced-motion aware (all 3 crates)
003: MEDIUM insurance sheet momentum projection + velocity-handoff settle, rubber-band
004: MEDIUM koboyo drawer slide, origin-aware popovers, animated camera fit
005: LOW polish - color tokens, concentric radii, real icons, tabular figures
2026-09-01 18:50:05 +00:00

81 lines
4.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 000 — Create the shared `craft` crate
- **Status**: TODO
- **Commit**: d50006e
- **Severity**: infra (prerequisite for 001005)
- **Category**: Cohesion & tokens
- **Estimated scope**: 1 new crate (~5 files), 1 patch edit, 3 Cargo.toml edits
## Problem
Three crates re-implement chrome independently with zero shared motion language: no easing
tokens, no shared pressable, three copy-pasted toast blocks, brand hex repeated per widget
(`insurance/src/app.rs:31,108,122,170``#7b5cf6`, `#8a74ff`, `#f7a982`…). Every subsequent
plan needs one home for the house values, or the fix itself becomes three drifting copies.
## Target
New workspace member `makepad-ports/craft/` (crate name `makepad-example-craft`, copied to
`examples/craft` by `setup_makepad.sh`, same layout as the other three), exporting:
1. **`script_mod` tokens** stored on the `mod` object (the only cross-file sharing mechanism):
- `mod.craft.ease_out_strong` = `Ease.Bezier {cp0: 0.23, cp1: 1.0, cp2: 0.32, cp3: 1.0}`
- `mod.craft.ease_in_out_strong` = `Ease.Bezier {cp0: 0.77, cp1: 0.0, cp2: 0.175, cp3: 1.0}`
- `mod.craft.ease_sheet` = `Ease.Bezier {cp0: 0.32, cp1: 0.72, cp2: 0.0, cp3: 1.0}`
- `mod.craft.ease_polish` = `Ease.Bezier {cp0: 0.2, cp1: 0.0, cp2: 0.0, cp3: 1.0}`
- durations: `dur_press_release: 0.15`, `dur_hover: 0.1`, `dur_popover: 0.2`,
`dur_drawer: 0.25`, `dur_toast_in: 0.3`, `dur_toast_out: 0.2`
2. **`Pressable`** widget (Rust: `#[derive(Script, ScriptHook, Widget, Animator)]`) — full spec
in plan 001; defined here, adopted there.
3. **`CraftToast`** widget — full spec in plan 002.
4. **`reduce_motion`**: `pub fn play_or_cut(reduce: bool, cx, w: &mut impl AnimatorImpl, st)`
helper (RECIPES.md §6) + a `#[rust] reduce_motion: bool` convention documented in the crate
docs. Default `false`.
5. `src/model.rs`-style pure module `motion.rs` with unit-testable math:
- `pub fn project(v_px_per_s: f64, rate: f64) -> f64``(v/1000.0)*rate/(1.0-rate)`
- `pub fn rubberband(over: f64, dim: f64) -> f64``over*dim*0.55/(dim+0.55*over.abs())`
- `pub fn settle(from: f64, target: f64, v0: f64, lambda: f64, t: f64) -> (f64 /*pos*/, bool /*done*/)`
— exponential settle: `x = (d + (v0 + λd)t)·e^(λt)`, done when `|x| < 0.5 && |v0·e^(λt)| < 10.0`
## Repo conventions to follow
- Crate skeleton: copy `makepad-ports/insurance/` structure (`src/{lib,main,app,model}.rs`,
`tests/`), Cargo.toml identical shape (`makepad-widgets = { path = "../../widgets", version = "2.0.0" }`).
- Workspace registration: extend `patches/makepad-dev-headless-linux.patch` where it already
inserts members (`+ "examples/rider",` etc.) with `+ "examples/craft",`; keep the patch
applying cleanly against dev @ `b41e740` (`git apply --check`).
- Consumers add `makepad-example-craft = { path = "../craft" }` and call
`makepad_example_craft::script_mod(vm)` before their own `script_mod` in `App::run`.
- Widget registration exemplar: the `let ChatList = #(ChatList::register_widget(vm)) {…}`
pattern noted in `makepad-ports/README.md` bug 3.
## Steps
1. Create `craft/Cargo.toml`, `src/lib.rs` (exports `script_mod`, `motion`, widgets),
`src/motion.rs` with the three functions + `#[cfg(test)]` unit tests (below).
2. Create `src/widgets.rs`: `Pressable` + `CraftToast` Rust structs and their
`script_mod!` block registering `mod.craft.*` tokens and both widgets.
3. Edit the patch: add `"examples/craft"` to `workspace.members`; update `setup_makepad.sh`
copy step to include `craft`.
4. Add the dep + `script_mod` call to `rider`, `koboyo`, `insurance` (no behavior change yet).
5. Unit tests in `motion.rs`:
- `project(1000.0, 0.998)``499.0` (±0.5); `project(0.0, _) == 0.0`; sign follows `v`.
- `rubberband` is monotonic, `< over`, and `rubberband(0.0, d) == 0.0`.
- `settle` reaches `done` with `pos` within `0.5` of `target` for
`(from=200, target=0, v0=800, λ=12)` before `t = 1.0`; position is continuous at `t=0`
(`pos(0) == from`).
## Boundaries
- Do NOT move any existing UI into the crate yet (that's 001/002).
- Do NOT touch `reference/`, `uploads/`, or the three apps' `model.rs`.
- Do NOT add third-party dependencies.
- If the patch no longer applies to dev @ `b41e740`, STOP and report — do not rebase upstream.
## Verification
- **Mechanical**: `cargo test -p makepad-example-craft --lib` green (new tests);
`cargo test -p makepad-example-{rider,koboyo,insurance} --lib --test integration` still
21/9, 29/14, 28/14; `git apply --check` passes on a fresh checkout.
- **Done when**: all four crates build, tokens are reachable from a consumer `script_mod!`
block as `mod.craft.ease_out_strong`, and the 140-test baseline is intact.