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.
321 lines
21 KiB
Markdown
321 lines
21 KiB
Markdown
# InsurePay ("nigig-pay" / `insurance.html`) — Brutal Professional Assessment
|
||
|
||
**Artifact reviewed:** `/home/user/insurance.html` — 998 lines, 56.8 KB, single
|
||
file containing HTML + 700 lines of bespoke CSS + ~150 lines of inline JS.
|
||
**Reviewer verdict:** a visually polished *design prototype* that is
|
||
**unshippable as a product** in its current form. Roughly a third of the UI is
|
||
inert decoration, the interaction layer has real correctness bugs, there is no
|
||
test of any kind, and it embeds realistic PII in public markup.
|
||
|
||
Scorecard (0–10, strictly as production code):
|
||
|
||
| Axis | Score | One-liner |
|
||
|---|---|---|
|
||
| Architecture | 3.5 | Single global script, DOM-as-state, stringly-typed routing |
|
||
| Code quality | 4.0 | Readable but duplicated, dead code, zero tests/tooling |
|
||
| Bugs / correctness | 4.0 | Gesture math, dead affordances, unreachable code paths |
|
||
| Performance | 6.0 | Self-contained & light, but backdrop-filter stacking and filter animation are GPU traps |
|
||
| Design / UX | 6.5 | Genuinely pretty; confusing IA, 40% interactive-looking elements do nothing |
|
||
| Security / privacy | 3.0 | No auth model, realistic PII hardcoded, no CSP; currently low attack surface *only* because nothing is wired |
|
||
| Accessibility | 2.5 | `user-scalable=no`, 10px labels, no focus management, no `aria-live`, no keyboard sheet control |
|
||
| **Overall** | **4.2** | **Demo-grade. Not production-grade.** |
|
||
|
||
Credit where due (one paragraph, then we get harsh): the file is fully
|
||
self-contained with zero dependencies and zero network cost; icons are inline
|
||
SVG (no icon-font garbage); the gesture code at least *attempts* real physics
|
||
(velocity, clamps, tap vs. drag disambiguation); sectional CSS comments are
|
||
disciplined; and hit targets are mostly ≥38 px. That ends the good news.
|
||
|
||
---
|
||
|
||
## 1. Architecture
|
||
|
||
**A1 — DOM is the state machine.** Screen visibility, chip selection, sheet
|
||
mode, and toast message are all encoded as DOM classes (`classList.toggle`)
|
||
with `data-go`/`data-toast`/`data-stab` strings as the only "API". The only
|
||
actual state variable in 150 lines of JS is `sheetMode`
|
||
(`insurance.html:925`). There is no model, no store, no events, no types.
|
||
Every behavior mutation has to know which classes to flip. This is why the
|
||
Makepad port had to *rebuild* the whole thing as `model.rs` — the original has
|
||
no extractable logic.
|
||
|
||
**A2 — Stringly-typed routing with silent failure.** `go(name)`
|
||
(`:885`) looks up `scr-<name>`; a typo in any `data-go` attribute
|
||
(`data-go="policy"` instead of `policies`) fails *silently* — no console
|
||
error, no fallback. There are 14 `data-go` sites and nothing validates them.
|
||
|
||
**A3 — No history/routing integration.** The four "screens" are div-switching.
|
||
The browser Back button exits the app entirely; there are no URLs, no deep
|
||
links, no refresh-safe state. For an app with auth and payments (renewals),
|
||
this alone disqualifies it.
|
||
|
||
**A4 — Content is hardcoded in markup.** Agent names/roles, policy numbers,
|
||
prices ($214/$112), claim amounts (+$234.00), support email — all baked into
|
||
HTML. There is no data layer, not even a JSON block. Any business change
|
||
requires editing markup; there is no seam where an API would attach.
|
||
|
||
**A5 — Four duplicated component families.** The bell button is copy-pasted
|
||
4× (identical 5-line SVG), the back chevron 3×, the "↗" arrow 5×, the renew
|
||
pill 2×, avatar stacks 2×. No `<symbol>`/sprite, no template, no JS component.
|
||
Change the bell → edit 4 places. This is copy-paste-driven development.
|
||
|
||
**A6 — Global scope pollution.** The script has no IIFE/module wrapper:
|
||
`screens`, `tabs`, `go`, `showToast`, `toast`, `sheetMode`, `dragging`, `vy`,
|
||
`moved` et al. are all globals. Any third-party script collides silently.
|
||
No `'use strict'`, no JSDoc types, no lint config, no `package.json`, no build,
|
||
no source control hygiene visible (no version, no license, no author).
|
||
|
||
**A7 — Error handling: zero.** `grab.setPointerCapture(e.pointerId)` (`:951`)
|
||
can throw `NotFoundError` in synthetic/edge environments; no try/catch anywhere
|
||
in the file. One thrown pointer error bricks the sheet gesture for the session.
|
||
|
||
**A8 — Decision ambiguity encoded as three same-named sections.**
|
||
"Quick Actions" exists on Home (2 rows), on Policies (2 policy cards), and in
|
||
the FAB sheet (4 tiles) — three *different* datasets under one label. "File a
|
||
Claim" appears 3 ways with **three different outcomes** (`+$234.00` reward
|
||
row, "Claim registration opened ✓", "Claim form opened…"). Same action,
|
||
different behavior depending on where you tap — an IA defect masquerading as
|
||
richness.
|
||
|
||
## 2. Code quality
|
||
|
||
- **Q1. Dead CSS and JS.** `.hide{display:none!important}` (`:36`) is defined
|
||
and never used. The FAB `click` toggle
|
||
(`:937: closeSheet()` branch) is unreachable code (see B1). The dots pager
|
||
(`.dots`) purports to page a carousel that doesn't exist.
|
||
- **Q2. ~40% of interactive-style elements are inert.** Concrete list: bell ×4,
|
||
filter button, hero-arrow, both bare "View All" links (policies/buy),
|
||
mini-handle, avatar, portrait, both QA rows, the agents duo-card, the
|
||
search input (accepts typing, filters nothing), all four `.sec-link`s on the
|
||
subpages. Users will tap things that look tappable and get nothing. Either
|
||
wire them or delete them — currently it's a mock wearing a product's clothes.
|
||
- **Q3. Magic values everywhere.** px-only sizing (no rem/em — ignores user
|
||
font preferences), un-tokenized spacing/radii, z-index 60/70/80/90/95 held
|
||
together by hope, gesture thresholds (−26, −12, 36, 18, 0.2, 0.28, 140, 8)
|
||
inline with no names.
|
||
- **Q4. `transition: all` anti-pattern** on chips and action tiles (`.chip`,
|
||
`.act`) — animates every property including ones you didn't mean to.
|
||
- **Q5. Cryptic class names:** `.tb`, `.dc`, `.qa`, `.idh`, `.bc`, `.stk`,
|
||
`.ag-row`, `.sup-row` — maintenance tax on every future edit.
|
||
- **Q6. No tests.** Nothing. Not a gesture unit test, not a snapshot. The
|
||
gesture thresholds are tunable constants whose regression would be invisible
|
||
until a user complains.
|
||
- **Q7. Localization-hostile.** Hardcoded English, `10/02/25` ambiguous date,
|
||
`$` hardcoded, `white-space:nowrap` toasts that will overflow in German,
|
||
physical (non-logical) CSS properties block RTL.
|
||
|
||
## 3. Bugs (ranked by severity)
|
||
|
||
- **B1 (High) — FAB can never close the sheet; half its code is dead.**
|
||
`fabBtn` lives *inside* `.tabbar` (`:777-797`). Opening the sheet adds
|
||
`.ghost` to the tabbar → `pointer-events:none` (`:283`). So
|
||
`sheetMode==='closed' ? openSheet() : closeSheet()` (`:937`) can *only ever*
|
||
take the open branch; the close branch is unreachable. The FAB pretends to be
|
||
a toggle and isn't one.
|
||
- **B2 (High) — Flick detection windows are wrong.** Velocity uses only the
|
||
last two pointermove samples (`vy = (cy-ly)/max(1, now-lt)`, `:959`). A fast
|
||
drag that decelerates at release registers vy≈0 and snaps *back*; a slow
|
||
drag with an accidental final twitch counts as a fling. Industry standard is
|
||
a trailing ~100 ms sample window. Users will experience random sheet snaps.
|
||
- **B3 (Medium) — Sub-tab state is silently reset on every expansion.**
|
||
`setMode('expanded')` forces `switchStab('actions')` (`:932`). User on
|
||
"Agents" collapses to peek (tab hidden but state kept), re-expands → yanked
|
||
back to "Actions". Collapse keeps state, expand discards it — inconsistent.
|
||
- **B4 (Medium) — Chips filter nothing.** The benefits chips only toggle
|
||
`.on` (`:905-911`). Featured/Health/Motor switch while the entire screen
|
||
(drive card, health tip) stays identical. It is a fake control.
|
||
- **B5 (Medium) — `user-scalable=no, maximum-scale=1` (`:5`).** WCAG 1.4.4 /
|
||
1.4.10 violation; low-vision users cannot zoom. This is disqualifying for an
|
||
*insurance* app that will be used by older demographics.
|
||
- **B6 (Medium) — Fake QR code.** The ID card "QR" (`<div class="qr">`) is a
|
||
static rect pattern encoding nothing. A user scanning *their own insurance
|
||
ID* and getting "no data" is a trust incident. At minimum an insurance ID
|
||
card must encode policy deep-link or signed token; as-is it's a real-looking
|
||
credential that does nothing — worse than no QR.
|
||
- **B7 (Medium) — Toast is not announced.** No `role="status"`/`aria-live` on
|
||
`#toast`; screen-reader users never hear "Policy renewal started ✓". Also
|
||
hardcoded `white-space:nowrap` will overflow the pill on 320 px devices or
|
||
longer localized strings.
|
||
- **B8 (Low) — Sheet keyboard/escape handling absent.** No Esc-to-close, no
|
||
focus trap, focus stays behind the backdrop; keyboard users tab into a
|
||
blurred background. `.backdrop` is a `<div>` with a click listener — not
|
||
keyboard-activatable.
|
||
- **B9 (Low) — Drag threshold tuned for mouse, not thumb.** 26–36 px on a
|
||
~0.35 mm/px phone screen is ~9 mm — double the comfort threshold for flick
|
||
gestures; combined with B2 users get missed or accidental expansions.
|
||
- **B10 (Low) — `Hero renew` pill says "Renew Now" and the toast confirms a
|
||
renewal with no confirmation step, no amount, no terms.** A one-tap
|
||
financial commitment with zero friction text. Fine for a mock; a lawsuit in
|
||
production.
|
||
- **B11 (Low) — Screens preserve scroll position across navigation.** `go()`
|
||
never resets `scrollTop`, so returning to a long screen restores a scrolled
|
||
view with no status indication. Minor but sloppy.
|
||
- **B12 (Low) — Time is hardcoded "9:41"** and the bell red dot has no
|
||
unread-state backing. Cosmetic, but it signals the app's data plumbing is
|
||
imaginary.
|
||
|
||
## 4. Performance
|
||
|
||
- **P1 — backdrop-filter stacking.** Five separate `backdrop-filter` regions
|
||
(hero-ic blur 4, batt-tag blur 6, dc-glass blur 6, tabbar blur **14**,
|
||
sheet backdrop blur 4) sit over scrollable content. On mid/low Android this
|
||
is the single largest GPU cost; scroll jank is guaranteed when the sheet is
|
||
open over blurred content.
|
||
- **P2 — Animating `filter: blur(3px)`** on `.tabbar.ghost` (`:283`) — filter
|
||
animation is paint-every-frame, not composited; on a full-width bar this is
|
||
a visible stutter. The same effect is free as pre-rendered opacity+translate.
|
||
- **P3 — Massive shadows.** `0 34px 90px`, `0 22px 46px`, multi-layer card
|
||
shadows repaint on any transform of their elements. Real risk during the
|
||
sheet's transform animation on low-end devices (verified pattern on mobile
|
||
WebKit).
|
||
- **P4 — Redundant gradients.** Hero, drive card, ID card, FAB each stack 2–3
|
||
radial + linear gradients as backgrounds. Beautiful; also 4 large
|
||
`background-size` rasterizations. Cacheable but adds first-paint cost on
|
||
weak GPUs.
|
||
- **P5 — No `will-change`/containment hints** on `.sheet` or `.screens`; the
|
||
`.screen` switch depends on `display` toggling which forces full
|
||
layout+paint of 1000-node pages on every tab switch. For 4 pages it's
|
||
tolerable; at 8 it won't be.
|
||
- **P6 — Positives:** zero external requests, no fonts, no images, ~10 KB
|
||
gzipped. First paint is fast. This is why it *feels* snappy in a demo.
|
||
- **P7 — Pointermove handler** does `performance.now()` + style writes per
|
||
event — fine — but no `requestAnimationFrame` throttle; at 120 Hz screens
|
||
style writes run 2× frame rate. Trivial fix.
|
||
|
||
## 5. Security & privacy
|
||
|
||
- **S1 — Realistic PII in public markup.** Full names (Rahul Sharma, Alex
|
||
Mandes, Sarah Kimani, David Otieno, Grace Wanjiru), DOB (24 Feb 2001),
|
||
ID number 326547624, policy CA326547624, plate MP04CY9999, residence. If any
|
||
of these map to real humans, this file is a data-protection incident (Kenya
|
||
DPA 2019 / GDPR-class). Even if synthetic, nothing in the file says so.
|
||
- **S2 — No auth/session concept.** An app that displays ID cards, policy
|
||
numbers, and takes "renewal" actions has zero authentication surface. As a
|
||
mock: understandable. As a product blueprint: where is it?
|
||
- **S3 — No CSP/SRI/referrer-meta.** Currently self-hosted so risk is latent,
|
||
but the day someone hot-links a script or style, there's no defense in
|
||
depth. `textContent` for toasts is XSS-safe today (`:897`) — the classic
|
||
regression is someone "improving" it to `innerHTML` for bold text.
|
||
- **S4 — Sensitive actions lack confirmation & audit trail.** Renewal, claim
|
||
registration, ID download: one tap, toast, done. No step-up auth, no
|
||
idempotency, no server request at all (obviously) — meaning the file
|
||
currently *teaches the wrong interaction contract* to anyone building the
|
||
backend from it.
|
||
- **S5 — Secrets/privacy in URLs to be designed later** — but note the QR
|
||
(B6) will likely tempt someone to encode raw policy IDs client-side. Needs a
|
||
signed, short-lived token design from the start.
|
||
- **S6 — Input handling is unstructured.** The search input has no
|
||
validation/sanitization conventions; when wired to an API this becomes the
|
||
injection vector of choice. Establish encoding-at-boundary now.
|
||
|
||
## 6. Design / UX (and a11y)
|
||
|
||
- **D1 — Visual craft is real.** Consistent radius scale (13→30), one strong
|
||
brand color, disciplined soft-shadow language, coherent purple/navy/light
|
||
hierarchy. It looks expensive. (This is what's fooling stakeholders into
|
||
thinking it's nearly-done.)
|
||
- **D2 — Identity confusion.** Hero says *Car* Insurance; sections push
|
||
*Health* & Wellness, Quick Actions reward "+$234.00" for *claims*, Benefits
|
||
pushes driving score + heart rate. The app doesn't know what it is: car
|
||
insurance, health insurance, or a wellness-rewards program. Pick one north
|
||
star per surface.
|
||
- **D3 — FAB mega-menu buries support two gestures deep** (FAB → expand →
|
||
Support stab) while "Message Now" exists on Policies — two competing
|
||
support entries with different states.
|
||
- **D4 — Emoji-as-iconography.** 🧔🏻👩🏼📄🔔💬🛡 render inconsistently across
|
||
platforms (tofu on some Androids, inconsistent skin tones). For a financial
|
||
app this reads as unprofessional; replace with a single icon font/SVG set.
|
||
- **D5 — Contrast failures.** `.lbl` rgba(255,255,255,.62) on purple
|
||
(~2.9:1), `#a6a7b3` placeholder, `#9a9ba8` 10.5 px tab labels, `#cfcfd9`
|
||
dots — multiple sub-4.5:1 ratios at 10–12 px sizes. Insurance users skew
|
||
older; this is an exclusion shipped as a style.
|
||
- **D6 — Home indicator / island are fake device chrome painted by the app**
|
||
(`.island`, `.home-ind`). Inside a real device in-app WebView these double
|
||
up with the OS chrome. Kill them outside the demo frame.
|
||
- **D7 — No loading/empty/error states anywhere.** The file only knows the
|
||
happy path. First product question that will be asked: "what does this
|
||
screen show while policies load?" — there's no answer in this code.
|
||
- **D8 — Motion is un-governed**: no `prefers-reduced-motion` query; the
|
||
fadeUp + sheet + blur + toast stack all animate regardless of vestibular
|
||
settings.
|
||
|
||
---
|
||
|
||
## 7. Execution plan — "finally improve InsurePay"
|
||
|
||
Rough effort for **1 senior FE + 0.5 QA**, assuming it must become a real
|
||
maintainable web app. If any phase's goal shifts (e.g., the product stays a
|
||
prototype), that's a scope decision — write it down explicitly instead of
|
||
letting it happen by accident.
|
||
|
||
### Phase 0 — Foundations & hygiene (≈3–4 days) — *do not skip*
|
||
|
||
| # | Task | Acceptance criteria |
|
||
|---|---|---|
|
||
| 0.1 | Split the monolith: `index.html` + `styles.css` (+ `tokens.css`) + `app.js` as ES module; add `package.json` (Vite or no-build ESM), lint (ESLint+stylelint), formatting (Prettier), git baseline | `npm run dev/build/lint` green; zero globals (verify via `window` enumeration in CI) |
|
||
| 0.2 | Extract design tokens → CSS custom properties for *all* spacing/radii/font sizes; px→rem at 16 px base; z-index scale `z.base/z.overlay/z.sheet/z.toast` | No magic numbers outside tokens; rem audit passes |
|
||
| 0.3 | SVG sprite: one `<symbol>` per icon, `<use>` everywhere (SVG currently duplicated 14×); delete emoji icons | Single bell definition; zero emoji in chrome |
|
||
| 0.4 | Kill dead code: `.hide`, dots pager, fake `.island`/`.home-ind`, inert controls → wire or delete each; **decision table** for all 22 `data-toast` and 14 `data-go` sites | Interactive-inventory doc: every tappable element has a named behavior |
|
||
| 0.5 | Fix viewport meta (drop `user-scalable=no`/`maximum-scale`); add `prefers-reduced-motion` gate for fadeUp/sheet/toast animations | axe-core: 0 critical |
|
||
| 0.6 | Toast: `role="status" aria-live="polite"`, max-width wrap, queue (2s) instead of replace | SR announces; no clipping at 320 px |
|
||
|
||
### Phase 1 — Behavior correctness (≈4–5 days)
|
||
|
||
| # | Task | Acceptance criteria |
|
||
|---|---|---|
|
||
| 1.1 | **State store**: one module owning `{screen, sheetMode, sheetTab, chip, toast}`; DOM becomes pure render. Keep the exact gesture constants as named, unit-tested exports — the Makepad port's `model.rs` is the reference implementation; port it back to JS/TS 1:1 | Store drives all class flips; `go('typo')` throws |
|
||
| 1.2 | Rewrite the gesture engine: trailing 100 ms velocity window, rAF-throttled moves, guarded `setPointerCapture`, touch-comfort thresholds (~48 px / velocity-weighted), tap tolerance on *both* axes | 95% of scripted gesture traces resolve as intended (test corpus) |
|
||
| 1.3 | Fix B1: move FAB out of the ghosted tabbar (or un-ghost it); restore real toggle; add Esc-to-close, backdrop focus trap, focus return | Keyboard-only walkthrough passes |
|
||
| 1.4 | Fix B3: preserve sub-tab across collapse/expand, or make the reset an explicit product decision (note in spec) | Behavior documented + tested |
|
||
| 1.5 | Fix B4: chips actually filter a benefits dataset (extract benefits rows from markup into `data/benefits.json`); search bar filters catalog | Typing "health" narrows visible rows |
|
||
| 1.6 | Routing: `#/policies`-style hash router with browser back support + scroll restore reset (B11) | Refresh on #/buy renders Buy; back works |
|
||
| 1.7 | One canonical Claims journey (merge the 3 divergent entries, IA decision per D2/D3) | One "File a Claim" path with one outcome |
|
||
|
||
### Phase 2 — Tests & quality gates (≈3–4 days, overlaps Phase 1)
|
||
|
||
| # | Task | Acceptance criteria |
|
||
|---|---|---|
|
||
| 2.1 | Unit tests (Vitest) for: router, store transitions, gesture math corpus (reuse the 28 test cases from `makepad-ports/insurance/src/model.rs` — they encode the same truth), chip filtering, toast queue | ≥80% branch coverage on logic modules |
|
||
| 2.2 | E2E (Playwright): boot smoke, tab circuit, FAB open/expand/close (mouse drag + touch emulation), chip select, toast TTL | All green in CI, trace/video on failure |
|
||
| 2.3 | a11y gate: axe-core in CI on all 4 screens; contrast sweep (fixes D5): raise `.lbl`/placeholder/tab-label to ≥4.5:1 or document exception | axe 0 critical/serious |
|
||
| 2.4 | Visual regression: screenshot 4 screens + sheet states (peek/expanded × 3 tabs) against golden PNGs | PR-blocker on diffs |
|
||
| 2.5 | Performance budget in CI: Lighthouse mobile ≥90; forbid new `backdrop-filter` & `filter:` animations without waiver (replaces P1–P3 with stacked alpha/opacity pre-composites) | Budgets enforced by CI |
|
||
|
||
### Phase 3 — Product hardening (≈2–6 weeks, domain-dependent)
|
||
|
||
| # | Task | Notes |
|
||
|---|---|---|
|
||
| 3.1 | AuthN/Z gate (OIDC), step-up for renewals/claims/ID download (B10, S2, S4) | Idempotent POSTs + confirmation sheets with amounts |
|
||
| 3.2 | Real QR: signed short-lived token → verification endpoint (B6, S5) | Never raw policy IDs in the QR |
|
||
| 3.3 | PII audit & synthetic-data policy (S1): replace all names/IDs with clearly-marked fixtures (`EXAMPLE`-prefixed); provenance review for agent names | Sign-off from whoever owns data compliance |
|
||
| 3.4 | i18n extraction (react-intl/paraglide), logical CSS properties for RTL, locale-safe date/currency | en + sw (Swahili) as first locales given user base |
|
||
| 3.5 | Security headers: CSP (script-src 'self'), referrer-policy, permissions-policy; SRI for any future external asset | Observed via scanner |
|
||
| 3.6 | API layer: typed contracts for policies/claims/agents/support; loading/empty/error states for every screen (D7) | MSW-mocked contract tests |
|
||
| 3.7 | Decide the native story: the **Makepad 2.0 port already exists and is fully tested** (`makepad-ports/insurance/` — 28 unit + 14 integration + 8 UI tests green). Either (a) keep web-only and treat `model.rs` as the executable spec to port back, or (b) adopt the port as the native shell and drive both from one JSON content contract | The port's `model.rs` is currently the *most correct description of this app's behavior that exists anywhere* — use it |
|
||
|
||
### Phase 4 — Observability (post-launch guardrails, ≈2 days)
|
||
|
||
Analytics events on nav/toast/CTA (privacy-reviewed), RUM for gesture jank and
|
||
tab-switch paint time, error reporting wired to CI releases, feature flags for
|
||
the staged rollout.
|
||
|
||
### Definition of done (product-level)
|
||
|
||
1. Every interactive element either works or is gone (D2 inventory signs off).
|
||
2. `npm run build && npm test && axe && lhci` green on every PR.
|
||
3. No PII-looking strings in the repo outside `tests/fixtures/`.
|
||
4. Gesture behavior documented + identical in web tests *and* the Makepad
|
||
port's model tests.
|
||
5. A real flow (renew or claim) completes against a mock API with loading,
|
||
error, and confirmation states.
|
||
|
||
---
|
||
|
||
### Bottom line
|
||
|
||
InsurePay's HTML is a **beautifully-executed demo of ~20% of an app**. It has
|
||
no logic worth salvaging as code — its value is (a) the visual system and
|
||
(b) the interaction vocabulary (sheet physics, tab model, toast idiom). Both
|
||
are already captured in a testable form in the Makepad port. Treat the HTML
|
||
as the *spec*, not the *product*: rebuild on Phase 0–1 foundations, gate with
|
||
Phase 2, and only then let it call itself an insurance app.
|