makepad-ports/makepad_ports_assessment.md
andodeki 7ddd75fb70 Makepad 2.0 ports: rider, koboyo, insurance (+ HTML sources, assessments)
Three single-file HTML apps ported to Makepad 2.0 (dev b41e740) script_mod! DSL:
- rider (ride.html): 21 unit + 9 integration + 9 headless UI tests
- koboyo (koboyo.html): 29 + 14 + 8
- insurance (insurance.html): 28 + 14 + 8
All 140 tests green. Includes headless-Linux platform patch, full README,
setup script, and three brutal codebase assessments with execution plans.
2026-08-04 05:07:18 +00:00

367 lines
23 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.

# `makepad-ports` (rider / koboyo / insurance) — Brutal Professional Assessment
The third assessment in this series, this time of **our own work**: the three
Makepad 2.0 port crates built in this workspace from `ride.html`,
`koboyo.html`, `insurance.html`. Same rubric, same severity scale, no
home-team discount.
**Evidence base:** every claim below was re-verified against the files in
`/home/user/makepad-ports/` today (grep/LOC quoted inline), plus this
session's build and test logs.
| Metric | Value |
|---|---|
| Total code | 8,652 lines across 3 crates (`wc -l`, 2026-07-26) |
| Largest files | `insurance/src/app.rs` **1,592 lines**, `koboyo/src/model.rs` 1,357, `rider/src/app.rs` 1,291 |
| Tests | 140 green: rider 21+9+9, koboyo 29+14+8, insurance 28+14+8 (unit+integration+UI) |
| Test wall time | ~7 min for the three UI suites, serial |
| Upstream patches carried | 6 (5 headless-Linux platform fixes + 1 registration) — not upstreamed |
| Compiler warnings in our crates | **2, unfixed** (`makepad-example-koboyo` lib) |
| Correctness gate | none — no CI, rebuilds happen manually via `setup_makepad.sh` |
Scorecard (010, as *product code*, not as "a good attempt"):
| Axis | rider | koboyo | insurance | Suite |
|---|---|---|---|---|
| Architecture | 6.0 | 6.5 | 5.5 | **5.0** (no shared kit — see A1) |
| Code quality | 6.5 | 6.5 | 6.0 | **6.3** |
| Bugs / correctness | 6.5 | 5.5 | 5.0 | **5.5** |
| Performance | 6.0 | 5.5 | 6.0 | **5.8** |
| Design / fidelity | 7.0 | 6.5 | 5.5 | **6.3** |
| Security / privacy | 6.0 | 6.5 | 5.0 | **6.0** (mostly N/A surface — see §6) |
| Test strategy | 7.5 | 7.5 | 7.0 | **6.5** (quantity masking coverage holes — §7) |
| **Overall** | **6.5** | **6.4** | **5.8** | **6.0 — a strong prototype suite that is *not* yet an engineering project** |
Credit, once: the three-layer test pyramid (pure model → black-box →
headless UI) is real and green from a cold rebuild; the model-first split
worked exactly as designed (every behavioral bug this session was catchable
in unit tests before any app boot); the drag physics of insurance's sheet is
a faithful, testable port rather than a vibe; and the README documents its
own failure modes, which is rarer than it should be. Now the problems.
---
## 1. Architecture
- **A1 (High) — Zero code reuse across three crates that are 70% the same
program.** The toast system (`show_toast` + generation + `Timer` + view
toggle) is copy-pasted in all three apps (`grep -l "fn show_toast"`
rider, koboyo, insurance). Palette primitives (`Ink`/`Mut`/`Wht` labels),
`Card`/`RoundBtn` shells, tab-bar-with-active-dot pattern,
"refresh_chrome re-syncs everything", the NavEffect/Fx side-effect idiom —
all reimplemented two to three times each, with **three incompatible
variations**: rider returns a `NavEffect` struct, insurance uses bare
method calls, koboyo uses `CanvasAction` events. There is no shared
`ports-ui-kit` crate and no ARCHITECTURE.md stating the conventions, so
the "rulebook" lives in the author's head. Crate #4 will invent a fourth
toast.
- **A2 (High) — The single source of truth is honored in words, not in
data.** Model constants (e.g. `POLICY_CARDS`, `BUY_CARDS`, tab labels)
contain the canonical strings, but the DSL markup in `app.rs` **retypes
nearly all of them as literals**. Nothing ties `"Edit Your Policy Details"`
in the DSL to `POLICY_CARDS[0].title`; only the subset a UI test happens
to assert is pinned. The suite's core claim — "model owns truth, UI is
thin" — is actually only true for the dynamic sections (chat list, tool
drawer, chip/page/chip styling). Everything static can drift silently.
- **A3 — The app↔model sync is a full O(everything) refresh per event.**
`refresh_chrome` restyles tab dots, re-evals chip colors, re-sets heights
and margins **on every single action**, via *interpreted*
`script_apply_eval!` calls. Correct, but it's the nuclear option that
hid a coupling smell: handlers don't know what *their* action dirtied, so
everything must be dirtied. This pattern will not survive a screen with
200 dynamic elements.
- **A4 — Event handling uses the same two-lane mess I criticized
upstream.** insurance's grab drag uses
`event.hits_with_capture_overload(cx, area, false)` while every other
control uses `finger_down(actions)` — two disjoint input models in one
app, chosen because the first attempt (`Event::FingerDown`) doesn't even
compile against makepad 2.0. The port inherited upstream's API
fragmentation without adding a local abstraction over it.
- **A5 — Platform/chrome architecture is per-app improvisation.** rider:
inline overlays for status bar/home indicator/toast. insurance: seven
hand-ordered `flow: Overlay` layers where z-correctness depends on
declaration order (the backdrop↔sheet boundary bug bit exactly here).
koboyo: rail/flyout/zoom widgets positioned against a hardcoded
`(500,350)` center (`VIEW_C`, app.rs:30). No shared "chrome scaffold"
exists, which is why these bugs keep recurring per-app.
- **A6 — The workspace is not a project.** No root `Cargo.toml` for the
ports (they must be *injected* into the upstream workspace to build), no
CI, no `rust-toolchain.toml`, one setup shell script as the entire build
system. "Clone makepad, apply patch, rsync crates in" is a bootstrap
hack that has quietly persisted into being the architecture.
## 2. Code quality
- **Q1 — DSL files are walls.** 1,291/1,020/1,592 lines of `script_mod!`
markup each, single `startup()` bodies, content inlined. Components were
extracted only where forced by mechanics (PortalList rows, custom
widgets); cards that repeat 4× (insurance category tiles, sheet action
tiles, agent rows) are copy-paste twins with one differing literal each.
insurance's `app.rs` at 1,592 lines is the worst file in the suite and it
grew that way in one sitting.
- **Q2 — Test files have the same duplication.** UI tests repeat the
boot-one-app-per-test pattern with hand-copy-pasted journey prefixes (in
insurance, reaching the sheet's Agents tab requires the same 3-step
preamble as 2 other tests); there are no shared journey helpers.
- **Q3 — Two unfixed warnings** in koboyo's lib (observed in the green
build log: "`makepad-example-koboyo` (lib) generated 2 warnings"). The
suite ships with warnings in a repo whose README demands upstream warning
hygiene. Physician, heal thyself.
- **Q4 — Magic geometry without provenance.** `VIEW_C=(500,350)` is a
hardcoded half of a 1000×700 assumption; insurance's FAB at
`margin{bottom: 47}` is `14+74-62/2+…` computed nowhere; rider status-bar
offsets are tuned literals. These are the numbers you change when they
break, not because you understand them.
- **Q5 — Naming/doc drift between crates.** `NavEffect` vs `Fx` vs none;
`refresh_chrome` in two apps vs ad-hoc refreshes in koboyo; IDs
(`pg_home` vs `page_home` vs views without page prefix). No lint config,
no fmt check, no CONTRIBUTING.
- **Q6 — Comments document the *what*, rarely the *which-approximation*.**
Reader cannot tell from the code that `#6a48f0` is standing in for a
3-stop radial gradient, that the QR view is fake, or that the backdrop is
opaque where the HTML is 36% translucent… without reading the README.
The approximation ledger belongs in the code at the point of deviation.
## 3. Bugs (verified, severity-ranked)
- **B1 (Critical, build): `setup_makepad.sh` clones a floating HEAD and
patches it with a diff cut against a pinned commit.** Line 27:
`git clone --depth 1 --branch dev …`, then applies a patch whose context
is `b41e740`. The next upstream commit can silently break either the
patch or, worse, *apply* while shifting behavior. It worked three times
in a row this week **by luck of upstream not moving**. Fix is mechanical
and currently absent: `git fetch --depth 1 origin b41e740… && git checkout FETCH_HEAD`.
- **B2 (High, koboyo): phone-mode is implemented, tested… and dead.** The
model's breakpoint logic has unit coverage
(`breakpoint_matches_media_query`, `journey_breakpoint_matches_html`) but
`app.rs` wires only the desktop chrome; there is no phone bottom-bar in
the UI (grep-verified: no phone bar ids in app.rs, no resize handler).
A user-facing feature that exists only as passing tests.
- **B3 (High, insurance — and latent in rider): the app does not fit its
own specified form factor.** The HTML is a 390×824 phone; the port runs
390×1280 with **no scrolling at all**, because that's what made the
center-only click tests deterministic. On the actual 824 px viewport,
Home/Buy content is unreachable below the fold — and there is not even a
failing test to tell you, because every UI test runs at 1280 px. We
diagnosed *source* content overflowing at y=1054 (old run) and then
changed the window instead of the app.
- **B4 (Medium, insurance): shipped the exact dead-affordance problem we
condemned in the HTML review, with extra steps.** `search_input` is a
live `TextInput` users can type into that filters nothing; `filter_btn`
is an inert label that looks like a control; `bell_*` exist ×4 with ids
but zero handlers; `hero_arrow` is an id'ed `RoundedView` with no action;
the policies/buy "View All" sec-links are static text styled like links.
In the insurance.html assessment we called inert controls "a mock
wearing a product's clothes" — the port preserves all of it and calls it
fidelity.
- **B5 (Medium, insurance): the benefits-chip UI test is vacuous.** It
clicks Motor, clicks Featured, and asserts… that "Driving Score" is
still visible — i.e., that clicking a chip didn't delete the screen.
Selection state is visually inexpressible in the locator API, so the only
end-to-end verification of the most interactive widget on the screen is
the color of a rounded rect nobody reads. Model tests cover logic; the
*rendering* of selection has no effective verification.
- **B6 (Medium, suite): zero handling of window resize in all three
apps.** No `WindowResize` handling (grep-verified); rider/insurance fixed
390-ish windows, koboyo's zoom centered on a hardcoded point.
- **B7 (Medium, insurance): backdrop opaqueness vs source.** Set to solid
`#10101a` to dodge a hit-test boundary problem discovered late (sheet top
edge == backdrop center at peek); source is 36% translucent. A design
regression introduced by a testability workaround, currently undocumented
in the code.
- **B8 (Low, rider): the "map" is a procedural doodle.** Fixed streets,
fixed park, fixed route polyline hardcoded in a pixel shader
(`MapView` draw_bg). It looks like San Diego only from a distance. Fine
for a demo; catastrophic if anyone mistakes it for a functional map
surface.
- **B9 (Low): emoji-as-iconography** throughout insurance/koboyo DSL
(`🔔🛡💬📍👨🏾…`). Headless and many Linux font stacks render missing-glyph
boxes; no fallback configuration anywhere; UI tests can't see it (they
assert text presence, and a tofu box "renders" fine).
- **B10 (Low): data provenance.** Mock "agents" carry real-world Kenyan
given names + surnames and an ID number/plate lifted from the HTML with
zero synthetic-data marking — same PII-adjacency flagged in the
insurance.html assessment.
- **B11 (Low): patch rot.** The 5 platform patches have no upstream PR
tracked, no expiry condition, and live as an unversioned `.patch` file;
Phase 0.5 of the upstream plan literally lists upstreaming — it hasn't
happened.
## 4. Performance
- **P1 — Every click runs interpreted script.** Dynamic styling goes
through `script_apply_eval!`, i.e. the makepad *script VM*, per state
change — including **per pointermove during sheet drags** (insurance
margin updates). It works (and shows the VM's point), but the drag
hot-path should mutate the walk directly (`widget.apply_over(...)`-style)
or pre-computed animator keys; interpreted eval at 120 events/s is the
opposite of free.
- **P2 — koboyo canvas redraws the world per repaint.** No viewport culling
or spatial index is visible in `canvas_view.rs`'s draw loop; undo history
is capped at 60 (good) but element count is uncapped and untested — the
suite's "infinite canvas" has never met 10,000 elements. No benchmark
exists anywhere in the ports.
- **P3 — Full-tree refresh per action** (A3): each `go()` also re-points
PageFlip, rewrites tab colors and re-evals option cards — trivial at
this scale, pattern-costly forever.
- **P4 — Test suites don't share boots.** 25 UI tests → ~25 child app
boots: ~7 minutes wall, serially. As a regression gate it's already past
the "I'll just skip it" threshold.
- **P5 — PageFlip instantiates all pages eagerly** (9 pages in rider) —
acceptable at demo size, worth noting as the non-scaling default.
## 5. Security & privacy
Limited surface — no network, no eval, no persistence, no secrets. Honest
findings are few but real:
- **S1** — The studio-protocol test channel boots app binaries over
stdin/stdout with no isolation; dev-only, but keep it there: none of
that protocol should ever ship in a release profile (same warning given
upstream).
- **S2** — Synthetic-PII hygiene (B10): names/IDs must read as fixtures.
- **S3** — Text inputs (`msg_input`, `search_input`, drawer search) are
rendered-only today; the *pattern* for wiring them later must be set
before someone interpolates them anywhere capable — currently no such
rule exists in the repo.
- **S4 (positive)** — The ports add **no new** unsafe code beyond what
custom draw shaders require via the framework's own derives; the only
memory unsafe reachable is upstream's S1 convention, which we obey (and
tested around) rather than fixed.
## 6. Design & fidelity
- **D1 — "Faithful port" is an overclaim governed by an unwritten
contract.** The README lists the big approximations, but there is no
per-element fidelity ledger: solid fills stand in for 3-stop gradients
(hero/drive/ID/avatars), backdrop opacity changed, QR is fake, the car is
6 rounded rects, portrait/avatars/QR are decorational, map is fictional,
emoji ≈ SVG icons, status-bar clock is static, bell dots have no unread
backing, search is fake. indistinguishable-from-source screenshots would
surprise nobody reviewing this for the first time.
- **D2 — Inconsistency *between* the three ports is jarring.** rider:
dark theme, custom map shader, chat widget. koboyo: dual theme + fully
custom shader widgets + gesture engine. insurance: light theme, plain
views, emoji. Same author, same week, three visual/technical dialects —
because A1 means every crate reinvents its kit. A casual reviewer should
be able to tell these came from one framework; today they can't.
- **D3 — Design-fidelity choices were made for testability, not UX, and
are presented as product decisions** (1280-tall window; opaque backdrop).
The tradeoffs were correct *for the goal of green tests* — they are
mislabeled as the app design.
- **D4 — The ports inherit, verbatim, the sources' UX sins** (three
conflicting "Quick Actions", insurance's identity-confused IA, chips
that filter nothing) with no "port vs fix" policy documented. We audited
insurance.html for these on Monday and shipped them on Tuesday.
- **D5 — No runtime theming token layer**: colors are hundreds of inline
literals; a brand change is a regex across 3,900 DSL lines (koboyo's
model-driven dark mode proves we knew better and didn't generalize it).
## 7. Test strategy — the suite's pride, honestly examined
- **T1 — 140 tests, but the distribution flatters.** A large block of
unit/integration assertions are `"literal" == other_literal` parity mints
— valuable as drift guards for a *port*, but they test the keyboard more
than the software. The count overstates behavioral coverage.
- **T2 — "Integration" tests integrate nothing.** Every
`tests/integration.rs` imports only `model::*`: they're a second unit
tier with journeys. The actual integration seam — app.rs ↔ model — is
covered **only** by the 89 UI tests per crate, i.e. ~5% of the suite
guards the largest risk surface.
- **T3 — The physics port is verified against itself.** `SheetDrag` tests
assert thresholds *we transcribed*; there is no golden trace comparing
against the HTML's actual JS behavior across a randomized gesture corpus
(a transcription typo at 26px would produce a green suite of a wrong
port).
- **T4 — No visual/golden testing.** Gradients→solids regressions, tofu
emoji, misalignment: invisible to the suite. `makepad_test` screenshots
exist only on failure and are never diffed against goldens.
- **T5 — No negative/stress paths in UI tests**: double-tap storms,
mid-drag cancels, timer races (toast during sheet drag), text-input edge
cases. Happy paths only.
- **T6 — The form-factor lie (B3) is a test-architecture failure first**:
there is no viewport-fit assertion ("no interactive widget rect outside
0..824") that would have failed loudly instead of quietly resizing the
window.
- **T7 — No CI.** All 140 greens exist because a human ran the script and
reported back. Nothing stops tomorrow's edit from breaking them.
## 8. Execution plan — turn the demo suite into a project
### Phase 0 — Repo → project (12 days)
| # | Task | Acceptance |
|---|---|---|
| 0.1 | **Pin the clone** (B1): fetch `b41e740` by sha, checkout FETCH_HEAD; add `rust-toolchain.toml`; root workspace that *drives* the upstream injection instead of a script that does it blind | Fresh run byte-deterministic; upstream HEAD bump is a choice, not an accident |
| 0.2 | Wire the 2 koboyo warnings to zero + `RUSTFLAGS=-Dwarnings` equivalent in the script | Suite compiles warning-free |
| 0.3 | One-command gate: `make check` = fmt + clippy + all 140 tests; document the ~7 min budget | A stranger can verify everything in one step |
### Phase 1 — De-duplicate into a kit (23 days)
| # | Task | Acceptance |
|---|---|---|
| 1.1 | Extract `ports-ui-kit`: palette primitives, Card/RoundBtn/TopBar/TabBar+dot, ToastHost (show/gen/timer/view), chrome overlay scaffold, NavFx idiom, UI-test journey helpers | Each `app.rs` shrinks ≥30%; toast exists exactly once (A1/Q2) |
| 1.2 | Single source for static content: boot-time `refresh` writes model constants into DSL labels (extend the existing dynamic path), OR generate the repetitive tiles from data | Model const ↔ visible text parity asserted suite-wide (A2); deleting a const deletes it from screen |
| 1.3 | Kill the interpret-per-event paths: drag translates via direct walk mutation; refresh_chrome gets a dirty-flag map per handler (A3/P1) | Sheet drag does zero `script_apply_eval!` calls per move |
### Phase 2 — Fidelity & correctness fixes (35 days)
| # | Task | Acceptance |
|---|---|---|
| 2.1 | Fidelity ledger per crate (`FIDELITY.md`): element → faithful/approximated/inert + reason; move to-source deviations into code comments at the site (Q6/D1) | A reviewer can audit fidelity without tribal knowledge |
| 2.2 | Dead affordances policy (B4/D4): wire or remove — bells→toast or deleted, filter deleted, search filters categories or goes, inert "View All"s removed | Interactive inventory = 0 inert elements; mirrors the rule we set for the HTML |
| 2.3 | Viewport truth (B3/B6): real scroll containers for insurance pages; form-factor UI test at 390×824 asserting every interactive widget's rect is inside the viewport; resize handling for all three apps; koboyo `VIEW_C` → measured center on resize (B8) | insurance runs at 824 px fully reachable; resize storm test green |
| 2.4 | Wire koboyo phone chrome (B2) or delete the model feature + its tests (both acceptable — pick one and say which) | No tested-but-dead features |
| 2.5 | Backdrop translucency restored without the hit bug (B7): shrink backdrop geometry to exclude the sheet slab | Matches source visuals; sheet drag + backdrop click tests both green |
| 2.6 | Emoji → drawn vector icons at least for chrome (B9: tab bar, bell, shield, lock) — rider already proves drawn shapes survive headless | Zero emoji in chrome paths; tofu risk eliminated |
### Phase 3 — Test architecture (24 days, overlapping)
| # | Task | Acceptance |
|---|---|---|
| 3.1 | Golden traces for gesture physics (T3): scripted corpus of drags/flings/taps with expected transitions captured from the HTML's JS (run the original in a headless browser once, freeze expectations) | SheetDrag verified against source behavior, not our transcription |
| 3.2 | Golden screenshots per page & sheet state (T4): hash or SSIM-threshold against checked-in references; document update flow | Visual regressions fail loudly |
| 3.3 | Model↔DSL parity test (A2): UI test asserting every string in the model's public tables appears in the widget tree exactly where expected | Static-drift class of bug erased |
| 3.4 | Negative + stress journeys (T5): double-tabs, drag-cancel, toast-during-drag, resize mid-sheet, input fuzz (emoji/RTL/long strings) | Suite grows <20% and targets seams, not literals |
| 3.5 | Chip selection made end-to-end observable (B5): overlay-free state presentation — e.g. drive-card title/battery sourced from per-chip data — then assert *that* | Feature verified through pixels, not faith |
### Phase 4 — Upstream & product decisions (weeks)
1. **Upstream the 6 patches** with the headless CI leg (B11); delete them
locally on merge; until then the patch file gets a header: purpose,
expiry, owner.
2. **Product question, answered in writing**: are these ports throwaway
fidelity demos (then stop polishing and archive), or apps (then D2/D4 —
one design language, the IA sins fixed, and real scroll + resize land
first)?
3. **Performance floor** (P2/P4): koboyo canvas culling + 10k-element
benchmark; UI-suite boot sharing or parallelism to <90 s/crate; a
`criterion` bench for SheetDrag/zoom math because there is none today.
4. **Privacy pass** (S2): all people-shapes renamed to `EXAMPLE_*`
fixtures; a `SYNTHETIC_DATA.md` stating no real persons are referenced.
### Definition of done
1. Stranger runs one command and gets 140+ green, deterministic, under 5
minutes, zero warnings.
2. Deleting a model constant visibly deletes UI (single source reality).
3. insurance at 390×824, koboyo at 375×667, rider at 390×700 — all fully
operable, with a test that proves it.
4. Fidelity ledger says exactly what is not the source and why, element by
element.
5. No inert interactive-looking element in any of the three apps.
---
### Bottom line
As *proof that Makepad 2.0 can host real apps*, this suite succeeds — three
different app shapes, one DSL, all behavior captured in testable models,
140 green tests from a cold machine. As *engineering*, it is three
well-tested prototypes sharing neither kit, conventions, CI, nor a
reproducible build, with its two most user-visible cheats (tall window,
dead affordances) introduced by the test strategy itself. The single most
important next commits are embarrassingly small: pin the clone, fix two
warnings, extract the toast. Everything after that is deciding — in
writing — whether these are demos or products, because the current repo
insists on being graded as the latter while being built as the former.