makepad-ports/skills/makepad-ui-craft/PORTS-AUDIT.md
andodeki e09f6bbb51 Add makepad-ui-craft skill pack: Emil Kowalski + Jakub Krehel doctrine translated to Makepad 2.0
- skills/README.md: provenance + index
- SKILL.md: merged design-engineering doctrine, Makepad-native (animator, script_mod!)
- EASING.md: CSS cubic-bezier -> Ease.Bezier translation verified against dev@b41e740
- RECIPES.md: press scale 0.96, toast enter/exit, sheet settle w/ velocity handoff, stagger, reduced motion
- PORTS-AUDIT.md: Before/After audit of rider/koboyo/insurance with file:line refs
2026-09-01 18:36:57 +00:00

79 lines
8.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.

# Ports audit — `rider`, `koboyo`, `insurance` vs. makepad-ui-craft
Reviewed statically against `SKILL.md` (Emil Kowalski + Jakub Krehel doctrine, Makepad-translated),
walking every state the code defines: hover, down, open/close, toast, drag, settle. No live
replay was run (headless sandbox) — everything below is from the source at the cited lines;
anything not inspectable is marked **Not verified**.
**Headline:** all three crates contain **zero `Animator` tracks** (`grep -c Animator */src/app.rs`
→ 0, 0, 0). Every state change in ~3,900 lines of UI is a hard snap via `set_visible` /
`refresh_chrome`, and every plain-View "button" commits its action on `finger_down`. The model
layers (drag physics, thresholds, velocity) are genuinely good — the *presentation* layer is
where the slop allegations land.
---
## What's already right (don't "fix" these)
| Location | What | Principle |
|---|---|---|
| `koboyo/src/canvas_view.rs` pan/draw/zoom-at-cursor | 1:1, unsmoothed direct manipulation | Apple §2 — touch and content move together; 100+×/day → no animation. Correct. |
| `insurance/src/model.rs:SheetDrag` | 1:1 drag with grab-offset, velocity thresholds (0.2 up / +0.28 down px·ms⁻¹), peek clamp, 8px tap tolerance | Apple §12, gesture hysteresis. The math is the right foundation for §4 below. |
| `insurance/src/app.rs` (FingerDown capture via `hits_with_capture_overload`) | pointer capture on the grab handle | Apple §2 pointer capture rule |
| `rider` PageFlip with no page transition | navigation in a prototype, tens of times/day → restraint is defensible | Emil frequency table (a subtle 0.2s crossfade would also be fine; heavy slides would not) |
| Rider/insurance toast timers stopped & regenerated on re-show (`toast_gen`) | no stale-timer flicker | Sonner "handle edge cases invisibly" |
---
## insurance (`insurance/src/app.rs`, `model.rs`)
| Severity | Location | Before | After | Why |
| --- | --- | --- | --- | --- |
| HIGH | `app.rs:14271531` (all nav tiles, quick actions, `dl_btn`, `fab_btn`, chips) | `finger_down(actions).is_some()` → action fires on press | Feedback on `FingerDown` (press animator), commit on `FingerUp` only if `is_over`; RECIPES §1 | Apple tap contract: highlight on down, commit on up, cancel by dragging away. Commit-on-down makes every mis-touch irreversible. |
| HIGH | whole crate (0 animators) | No pressable has any down/hover state | `down: instance` + 0.96 SDF scale, `snap(1.0)` in / `Forward {duration: 0.15}` `OutQuad` out | Emil: buttons must feel like the UI is listening; feedback is the foundation everything else sits on. |
| MEDIUM | `app.rs:13131321`, `12521258` | Toast = `set_visible(true)` … timer … `set_visible(false)` | Enter 0.3s rise+fade `Bezier(0.23,1,0.32,1)`, exit 0.2s same path down, `OutQuad`; RECIPES §3 | Toasts are occasional → standard animation; pop-in/pop-out reads as broken; exit softer than enter, same direction (spatial consistency). |
| MEDIUM | `app.rs:15801585` + `model.rs::finish_drag` | Release → `finish_drag` decides mode → `refresh_chrome` snaps the sheet to it | Project momentum (`v/1000·0.998/0.002`), pick nearest snap, settle from current pos carrying release velocity (RECIPES §4); interruptible by a new FingerDown | Apple §46: the seam between drag and animation is where "fluid" lives; a snap discards the finger's velocity the model already measured. |
| MEDIUM | `app.rs:262265` (Health tile: outer `radius: 20`, `padding top 14`, inner 38px view `radius: 13`) and sibling tiles at `273+` | Nested radii unrelated to padding (concentric would be outer = 13 + 14 = 27, or inner = 6) | Recompute per `outer = inner + padding`; audit every `Card`(20)/`IconTile`(15)/inner(13,19) nesting the same way | Jakub: mismatched nested radii are the most common "feels off". Side padding not visible at the cited line — flagged from top-padding math; **verify each tile**. |
| LOW | `app.rs` throughout (e.g. `:31`, `:108`, `:122`, `:170``#7b5cf6`, `#8a74ff`, `#f7a982`…) | Brand/status hex literals repeated per-widget | Hoist to shared tokens in the `script_mod!` `mod` object / theme; components reference roles | Jakub better-colors: primitives never applied directly; unthemable and drift-prone. |
| LOW | `app.rs:139`, `:264` etc. (`🔔`, `❤`, `⚡`-style glyph labels) | Emoji as icons | SDF-drawn or asset icons, one asset recolored per state, outline default / fill active | Emoji ignore palette + stroke weight and render differently per platform; can't take hover/disabled states. |
**Verdict: Block** (2 HIGH). The model layer is approve-quality; the chrome needs the press
contract and the sheet settle before this reads as crafted.
## rider (`rider/src/app.rs`)
| Severity | Location | Before | After | Why |
| --- | --- | --- | --- | --- |
| HIGH | `app.rs:11101214` (nav tiles, pills, `back_found`, `wtc_pill`, suggestion rows) | Commit on `finger_down`; real `Button`s at `:1200`, `:1215` correctly use `clicked()` but have no press state | Same fix as insurance: press animator + commit on up-if-over | Same tap contract; inconsistent commit semantics between Views and Buttons in one screen also violates "same look = same behavior". |
| MEDIUM | `app.rs:1004`, `1279`, `882` | Toast pop-in/out via `set_visible` | RECIPES §3 toast track | Same as insurance. |
| MEDIUM | whole crate | No hover/down states on any pressable | RECIPES §1 | Emil responsiveness contract. |
| LOW | chat `PortalList` (new incoming message) | Messages appear with no entry | 0.2s fade + 8px rise, `OutQuad`, only for *newly appended* rows — never on scroll-back | Prevent jarring appearance; chat arrivals are occasional. **Not verified** whether the list distinguishes new vs. re-bound rows. |
| LOW | driver-found / arriving screens (state transitions via `refresh`) | Full-snap screen state swaps | Keep instant (frequency defensible), or a single 0.2s crossfade | Restraint is acceptable here; flag only for consistency with whatever insurance ships. |
**Verdict: Block** (1 HIGH — commit-on-down), rest is polish.
## koboyo (`koboyo/src/app.rs`, `canvas_view.rs`, `model.rs`)
| Severity | Location | Before | After | Why |
| --- | --- | --- | --- | --- |
| HIGH | `app.rs:824883` (rail buttons, `t_lock`, `t_shapes`, `t_connect`, `t_all_tools`, `drawer_close`, `zoom_minus`…) | Commit on `finger_down`, no press state | Press animator + commit on up-if-over. Exception: tools that *arm a drag* (pen, lasso) may act on down — that's direct manipulation, document it | Tap contract; tool rail is high-frequency so keep the feedback minimal (opacity/color ≤0.1s), but it must exist. |
| MEDIUM | `app.rs:608618` (`set_drawer`), `:268` | 112-tool drawer appears/disappears via `set_visible` | Slide+fade, 0.25s in `Bezier(0.32,0.72,0,1)`, 0.18s out `OutQuad`, same edge both ways | Drawers are occasional → standard animation; also keyboard focus is set before the surface exists visually — focus after the enter starts. |
| MEDIUM | `app.rs:601605` (`fly_shapes`, `fly_connect`, `menu_panel`, `exp_panel`, `view_panel`) | Popover panels pop via `set_visible` | 0.150.2s scale 0.97→1 + fade, **origin at the triggering rail button** (SDF anchor, not center) | Emil origin-aware popovers: grow from the trigger, never from nothing, never from center. |
| MEDIUM | `model.rs:407` (`reset_zoom`), `:419`/`:722` (`fit_to_bbox`/`zoom_to_fit`) | Button-triggered camera jumps snap instantly | Animate camera (pan+zoom together) over ~0.25s `InOutCubic` / `ExpDecay {d1: 0.82, d2: 0.97}`, interruptible by any new gesture | Occasional, spatial context-preserving move — the one camera motion that *should* animate (wheel/pinch zoom stays instant). |
| MEDIUM | `app.rs:558`, `:589` | Toast via `set_visible` | RECIPES §3 | Same as the other crates. |
| LOW | tool rail / drawer rows | Emoji-or-text glyphs as tool icons, no active-state fill convention | One drawn asset per icon, recolor per state, outline default / fill active | Jakub icons rule. **Not verified** per-row — inspect `drawer_rows`. |
**Verdict: Block** (1 HIGH), with the note that koboyo's canvas *gesture* layer is the most
craft-correct code in the repo — the gap is entirely in the chrome.
---
## Fix order (highest leverage first)
1. **One shared `Pressable` base widget** (RECIPES §1) adopted by all three crates — clears every
HIGH at once and gives the repo a house press-feel (`0.96`, snap-in/0.15s-out, commit-on-up).
2. **One shared toast widget** with the enter/exit track — clears three MEDIUMs.
3. **Insurance sheet settle** (RECIPES §4) — the marquee interaction; the model already measures
everything the settle needs.
4. Koboyo drawer/popover enters (origin-aware), camera-fit animation.
5. Token pass (hex → theme roles), radius concentricity pass, icon pass.