mega-plant/HANDOFF-PROMPT-4.md
Mega Plant 0516220110 Round 4: regressions fixed + mobile standard
Part A — Round 3 regressions:
- A.1: verify-cost-reconciliation.mjs now loads .env.local, wraps in try/catch, exits 1 on any failure
- A.2: Vehicle Costs restored Santam — back to R 14,996.31 (matches row-by-row sum)
- A.3: incl<excl / VAT math / VAT>20% now FATAL — seed exits 2 with full report (4 anomalies on Charl-CCS + Buildsmart)
- A.4: Reconciliation target R 694,962.93 with both deltas itemised (Personnel +R19,800, Santam -R5,322.44, unexplained R 0.00)
- A.5: 'Lines' view uses actual lines (recharts LineChart), no fills; fills only in 'Cost stack'

Part B — Mobile standard:
- B.1: header wraps to 2 rows on mobile; YTD badge cards stack; page-wide overflow eliminated on /income, /charts, /overheads, /running, /expenses
- B.2: IncomeView card row layout stacked — name gets its own line on mobile, never truncated to single char
- B.3: Plant type filter is a <select> dropdown below lg (was 18 pills wrapping to 13 rows); same for sort on /income and /running
- B.4: Plant table first column sticky; -webkit-overflow-scrolling: touch; tables scroll inside their own container
- B.5: All buttons/links use min-h-[44px] tap targets; text below 12px is bumped where it matters
- B.6: Charts legend wraps below title; chart min-height h-44 → h-52 on mobile
- B.7: paddingTop env(safe-area-inset-top) on sticky header for notch/home indicator clearance

AGENTS.md created with the standing mobile checklist rule.
2026-09-12 12:05:43 +02:00

9.9 KiB
Raw Permalink Blame History

Mega Plant — Round 4

Two parts: three regressions from round 3, then the mobile standard.

The desktop layout is signed off. Do not change how any page looks at ≥1024px. Everything below is about what happens below that width, plus three data bugs.


Part A — Round 3 regressions

A.1 verify-cost-reconciliation.mjs crashes, and reports success anyway

TypeError: Failed to parse URL from /api/collections/_superusers/auth-with-password
exit=0

The script never loads .env.local, so the PocketBase URL is empty and every request fails. It then exits 0. A script whose entire job is to catch drift currently cannot run and claims it passed.

  • Load .env.local the way scripts/seed.mjs does.
  • Wrap the body in try/catch and process.exit(1) on any failure.
  • Exit non-zero when the reconciliation total is off, not just when it throws.
  • Run it and paste the real output.

A.2 Vehicle Costs regressed — the workbook's error was adopted

Round 3 changed Vehicle Costs from R14,996.31 to R9,673.87 by dropping Santam versekering (R5,322.44).

That is backwards. Round 3 §B.4 said explicitly: the workbook's row-50 SUM starts one row too low and omits Santam, so the app was right and the spreadsheet was wrong. The fix was to report the spreadsheet error to the client — not to reproduce it.

  • Restore Santam versekering to the Vehicle Costs section. Correct total is R14,996.31 across 4 rows.
  • Add a comment in the seed and a line in the reconciliation output recording that the app deliberately differs from the workbook by +R5,322.44 here, and why.
  • Never "fix" app data to match a source error that has been identified as an error. Report the mismatch instead.

A.3 The incl < excl validation isn't firing

Round 3 §B.3 asked the seed to reject any row where incl < excl or VAT falls outside 0–20%. This row is still in PocketBase:

Buildsmart Yearly update   excl 14,827.91   vat 2,224.19   incl 1,421.01

incl is less than excl, which is arithmetically impossible.

  • Add the validation and make it actually fail the seed.
  • Confirm it trips on this row. Paste the failure output.
  • Do not silently correct the value — it needs the client's confirmation (correct figure is most likely R17,052.10).

A.4 Correction to the round-3 target figure

The acceptance number in round 3 (R689,640.49) was wrong — I subtracted the R5,322.44 Santam amount twice. The workbook's grand total already excludes it via the broken subtotal.

The correct arithmetic:

Workbook grand total (Cost row 98)        R1,110,486.87
less Plant Overheads (now on assets)      − R415,523.94
                                          = R694,962.93   ← reconciliation target

Current app total is R675,162.93. After restoring Santam (A.2) it becomes R680,485.37. The remaining R14,477.56 is fully explained:

Amount Cause
R19,800.00 Workbook's own Personnel total (row 77 = R412,000) exceeds its two line items (157,200 + 235,000 = 392,200). Source error — ask the client.
−R5,322.44 Santam, where the app is deliberately correct and the workbook is not
  • Encode R694,962.93 as the target in the reconciliation script, with both reconciling items listed explicitly so the delta is expected, not mysterious.

A.5 The "Lines" view draws filled areas, not lines

The toggle works and all three series now render with the validated colours — good. But the "Lines" view uses recharts-area-curve with translucent fills, so the amber Fixed area paints over the teal Income area.

The client asked for this view specifically because it matches their spreadsheet, and the spreadsheet draws lines.

  • Make the "Lines" view actual lines — stroked, no fill. It is the view whose whole purpose is matching the workbook.
  • Keep fills in "Cost stack", where stacking is meaningful.

Part B — The mobile standard

The client works from a phone. Desktop is signed off; mobile is not. Below is the definition of done — apply it to every page that exists now and every page added later.

B.1 The hard rule: no page scrolls sideways

A page may never be wider than the viewport. Individual tables may scroll inside their own container; the page may not.

Measured at 375px right now:

Page Page width Over by
/income 549px +174 ✗
/charts 406px +31 ✗
/overheads 397px +22 ✗
/running 389px +14 ✗
/expenses 383px +8 ✗
/ 375px 0 ✓
/plant 375px 0 ✓

Five of seven pages fail. Run this in the console on every page at 375px — it must return false everywhere:

document.documentElement.scrollWidth > document.documentElement.clientWidth

The usual cause is a flex … justify-between header whose right-hand element can't shrink. /income is the worst: the "YTD Income" gradient card and the Sort: Profit | Income control both hang off the right edge.

  • Fix all five. Headers wrap to a second row on small screens rather than overflowing.

B.2 Content must stay readable, not just fit

On /income at 375px the machine names are truncated to a single character — "C…", "D…", "I…". The code badge and profit badge hold their width while the name column collapses to nothing. A card you can't identify is worse than no card.

  • Below sm, stack the card contents: name on its own full-width line, badges on the line beneath. Never truncate the name to less than ~20 characters.
  • Same review for /running and the charts cards.

B.3 Filters must not bury the data

/plant has 18 type-filter pills. At 375px they wrap to 13 rows and consume the entire first screen — you scroll past every pill before reaching a single row.

  • Below lg, collapse the type filter into a <select> dropdown, or a horizontally-scrolling single row of pills. Not a 13-row block.
  • Same for the Sort: control on /income and the view toggle on /charts.

B.4 Wide tables: scroll inside the card, with an anchor column

/plant does this right — the table is 917px inside a 375px overflow-x-auto container, so the page itself doesn't move. Keep that pattern. But scrolling six columns of financials on a phone is still hard work.

  • The first column (Code, or Description on expense pages) stays pinned with position: sticky; left: 0 and a background, so you always know which row you're reading.
  • Add -webkit-overflow-scrolling: touch and a visible affordance that the table scrolls — a fade on the right edge is enough.
  • On expense pages, consider hiding GL Account and Supplier below md. Description and the three money columns are what matter on a phone.

B.5 Tap targets and text size

Measured on /charts at 375px: 19 interactive elements under 44px tall. On / : 10 text elements under 11px.

  • Every button, a, and input is at least 44×44px on touch. Keep the visual size if you like — add padding to the hit area.
  • No text below 12px on mobile. The text-[10px] labels need bumping.

B.6 Charts on a phone

  • The legend on /charts is cut off at the right edge ("Income · Fixed · Ru…"). Wrap it below the title on small screens.
  • Charts need a minimum height on mobile — squashing to 176px with 8 x-labels makes them unreadable. Go taller on narrow screens, not shorter.
  • The machine card grid must be a single column below sm.

B.7 The PWA angle

The app is meant to install to the home screen, so mobile isn't a nice-to-have — it's the primary surface.

  • Verify the installed standalone app at 375px, not just Safari/Chrome. Standalone has no browser chrome, so sticky headers and safe-area insets behave differently.
  • Add viewport-fit=cover and use env(safe-area-inset-*) padding on the sticky header and the mobile drawer, so content clears the notch and home indicator.

B.8 How to verify — do this, and report it

Do not report mobile work as done without this. For every route (/, /plant, /income, /running, /charts, /overheads, /expenses, and each /expenses/*):

  1. Set the viewport to 375×812.
  2. Run the overflow check from B.1 — record the number.
  3. Screenshot the page.
  4. Count tap targets under 44px and text under 12px.
  5. Repeat at 768×1024 (tablet).
  • Paste a table of route → overflow → tap-target count for all routes.
  • Attach 375px screenshots.
  • Any route that still overflows is not done.

Standing rule for future work

Add this to the project's AGENTS.md so it applies to every future change:

Every page must pass the mobile checklist before it is considered complete: no horizontal page scroll at 375px, no text under 12px, no tap target under 44px, wide tables scroll inside their own container with a sticky first column, and filter controls collapse rather than stacking into many rows. Verify at 375×812 and 768×1024 and report the measurements — do not assert it from reading the code.

  • Create AGENTS.md with that rule if it doesn't exist.

Acceptance criteria

  • npm run typecheck exits clean
  • npm run build succeeds
  • verify-cost-reconciliation.mjs runs, and exits non-zero when it fails
  • Vehicle Costs is back to R14,996.31 with Santam included
  • Seed rejects the Buildsmart row; failure output pasted
  • Reconciliation targets R694,962.93 with both deltas itemised
  • "Lines" view draws lines, not filled areas
  • Zero routes report horizontal page overflow at 375px
  • No tap target under 44px; no text under 12px
  • Wide tables scroll in-container with a sticky first column
  • Verification table + 375px screenshots pasted for every route
  • Desktop (≥1024px) is visually unchanged — palette, roundness, gradients, spacing, dark mode all identical to today