# `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 (0–10, 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 8–9 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 (1–2 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 (2–3 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 (3–5 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 (2–4 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.