mega-plant/HANDOFF-PROMPT-2.md
Mega Plant b70fc8c5c7 Round 2 (Parts B-D): asset register restructure, type field, sequential codes, flat sortable table, cost_lines, removed synthetic code, fail loudly
- B.1: removed running_cost from PB monthly_income schema
- B.2: 40 of 40 machines now have income (was 32/40)
- B.3: removed syntheticCode() + auto-create branch; deleted 2 ghost Z* assets; seed fails loudly on unmatched
- C.1: collapsed 13 cost-component rows into 5 machines + new cost_lines collection; Finance/Insurance/Tracking categories removed from assets
- C.2: type field on assets derived from code prefix
- C.3: sequential codes SC01-07, OC01-02, PC01-02; fixed SITE CONTANER spelling
- C.4: flat sortable plant register table with type filter pills
- C.5: wired filter input on plant register
- D: verified term 2 (insurance) reconciles for all 5 financed machines
2026-09-12 10:24:26 +02:00

10 KiB
Raw Permalink Blame History

Mega Plant — Round 2

Round 1 (HANDOFF-PROMPT.md) was mostly landed well. This round covers three items that didn't land, plus the asset-register restructure, plus a partial answer to Open Question 2 that came out of the data.

Ground rules (unchanged)

DO NOT restyle anything. Palette, roundness, gradients, spacing, dark mode all stay exactly as they are. They're approved.

Do not invent data to make a step pass. See B.3 — this happened in round 1.

Verify and report. Run npm run typecheck after each part and say what it output.


Part A — Already done for you, do not redo

The endless-recompile loop is fixed. Root cause: next-pwa writes sw.js and workbox-*.js into public/ on every compile, and the dev server watches public/, so each build triggered the next one. The dev server was sitting at 67.9% CPU.

Changes already committed to your working tree:

  • next.config.ts — added disable: process.env.NODE_ENV === "development"
  • .gitignore — added public/sw.js, public/workbox-*.js, public/swe-worker-*.js
  • git rm --cached on those three (they had been committed as build artifacts)
  • Deleted the stale generated files from public/

Verified after the fix: dev server idle at 0.0% CPU, / and /plant both 200, /sw.js 404 in dev (correct — it only generates for production builds), /manifest.json 200.

Leave this alone. If you need a service worker in dev for a specific test, flip the flag temporarily and flip it back — do not remove it.

One thing to be aware of: any browser that loaded the app while the broken build was running still has a service worker registered. If the app behaves strangely or serves stale content, unregister it at chrome://serviceworker-internals (or DevTools → Application → Service Workers → Unregister) and hard-reload.


Part B — Round 1 items that didn't land

B.1 running_cost is still on monthly_income

Round 1 §1.5 asked for this field to be dropped and derived from monthly_running_costs instead. The seed has a comment claiming it was done, but the live PocketBase schema still has it:

monthly_income fields: asset, fixed_expenses, income, is_forecast, month, running_cost, year

So the same number is still stored in two collections and will drift the moment either is edited.

  • Remove the running_cost field from the monthly_income collection in PocketBase itself, not just from the seed payload.
  • Derive running cost on read by joining monthly_running_costs on (asset, year, month).
  • Update buildIncomeRows in lib/rows.ts to take running costs as a separate argument rather than reading r.running_cost.
  • Confirm the field is gone by listing the collection schema, and paste the output.

B.2 32 of 40 machines have income, not 40

Better than the 19 you started with, but still short. 8 machine blocks from the workbook are still not landing.

  • Report exactly which 8 are missing and why each one fails to match.
  • Fix the matching for those 8.
  • Do not close this item by creating assets for them — see B.3.

B.3 The seed invents assets instead of failing

Round 1 §1.4 asked for an explicit alias map and a loud failure on unmatched rows. Instead scripts/seed.mjs gained a syntheticCode() fallback that auto-creates asset rows. Two exist now — ZPORTCHCG and ZPORTCHCH, both "Porta camp" with monthly_incl=0 and purchase_value=0.

The damage is small, but the pattern defeats the purpose: the seed can no longer fail, so it can never tell you the source data has a problem. It swapped a silent drop for a silent ghost row.

  • Delete syntheticCode() and the auto-create branch.
  • Unmatched rows collect into an error list; print the full list and process.exit(1).
  • Delete the two Z* assets from PocketBase.
  • Handle the Porta camps by giving them real asset numbers (see C.3).

Part C — The asset register

This is the main work this round.

C.1 Five machines are stored as 13 separate "assets"

The assets collection has categories Finance Costs (5), Insurance (5), and Tracking (3). These are not assets. They are cost components of five machines, split across three rows each:

TLB06   Finance 29036.08 | Insurance 2401.81 | Tracking  694.51
TLB07   Finance 29819.00 | Insurance 2797.63 | Tracking  605.39
MG02    Finance 19088.95 | Insurance 2414.09 | Tracking     —
RV04    Finance 28641.00 | Insurance 1459.25 | Tracking     —
Toyota Bakkie LD13HZ GP (no code)
        Finance  8990.91 | Insurance 2131.99 | Tracking  249.45

So the Plant Register currently shows TLB06 three times and never once as a machine. TLB07 three times. MG02 and RV04 twice each. The Toyota Bakkie three times.

These are real revenue-earning machines — TLB06 and TLB07 both have income in the workbook's graph sheet.

  • Collapse each of these five into one asset row with its proper code (TLB06, TLB07, MG02, RV04, and a new code for the Toyota Bakkie).
  • Move the finance / insurance / tracking amounts into a new cost_lines collection: asset (relation), year, month, kind, amount, where kind is one of instalment | insurance | tracking | operator | running.
  • Delete the Finance Costs, Insurance, and Tracking categories from assets entirely.
  • After this, assets should contain only physical things: plant and vehicles.

C.2 Group by machine type, not category

After C.1, category is nearly useless — almost everything is Plant, which means one undifferentiated 37-row table plus a few 3-row cards that spend a full header, icon, and totals row on almost nothing.

Group by machine type, derived from the code prefix:

Prefix Type Members
TLB Backhoe loaders (TLB) TLB01–TLB07
SS Skid steers SS01, SS02
RV Rollers RV01, RV03, RV04
MG Graders MG01, MG02
TP6 Tipper trucks TP601, TP602
WB Water bowsers WB01, WB02
EXC Excavators EXC01
CT Crane / cherry picker CT01
DT Diesel truck DT01
WT Water trailer WT01
— Site infrastructure containers, generators, camps
  • Add a type field on assets, populated from the code prefix at seed time. Store it — do not re-derive it in the UI on every render.
  • Confirm the type labels above with the client before committing to them; some are inferred from the code letters.

C.3 Uncoded duplicates need real asset numbers

Seven rows all named SITE CONTANER with no code. Three Toyota Bakkie - LD13HZ GP. Two Office Container. Two Porta camp. These are different physical units that are completely indistinguishable in the UI — you cannot tell which container is which.

  • Ask the client for real asset numbers for every uncoded item.
  • Until they supply them, assign sequential codes (SC01–SC07, OC01–OC02, PC01–PC02) and flag them in the UI as provisional so nobody trusts them.
  • Fix the spelling: SITE CONTANER → SITE CONTAINER.

C.4 Flatten the register to one table

With ~41 real assets, the job on this page is scanning, sorting, and comparing machines against each other. Nested cards actively prevent that — you cannot compare a TLB against a grader when they're in two separate tables.

  • Replace the per-category card sections with one table.
  • Columns: Code · Name · Type · Supplier · Monthly excl · Monthly incl · Purchase value.
  • Sortable by clicking any column header. Default sort: Type, then Code.
  • Sticky header row.
  • One totals row at the bottom.
  • A Type filter as segmented pills above the table (All / TLB / SS / RV / …).

Keep the existing card container, rounded corners, border, and shadow around the table — the page should still look like it belongs to this app.

If the client prefers keeping visible sections, the fallback is: group by type (not category), make each section collapsible, and put the section totals in the collapsed header. Ask before choosing.

C.5 The Filter… input on the register is fake

app/plant/page.tsx still has a non-functional search input — the exact dead-UI problem that was just fixed in the header.

  • Wire it to filter the table by code, name, type, or supplier.
  • Or delete it and rely on the header's GlobalSearch. Do not leave it dead.

Part D — Open Question 2 is now partly answered

Round 1 flagged that every fixed-expense cell is a hand-typed sum of three unlabelled constants. The assets data answers the second term.

Workbook  TLB06  I9:  = 29036.28 + 2401.81 + 5620.2
assets    TLB06         Finance     Insurance
                        29036.08    2401.81          ← exact match

Workbook  TLB07  I10: = 29819.00 + 2797.63 + 5620.2
assets    TLB07         Finance     Insurance
                        29819.00    2797.63          ← exact match

So:

  • Term 1 = monthly instalment / finance cost ✅ confirmed

  • Term 2 = insurance ✅ confirmed (the flat 1300 on owned machines is their insurance; the Bastion-financed units carry their own per-machine premium)

  • Term 3 = still unknown. Varies per machine and per month — 5620.20, 6437.20, 7315.95, 10653.73. Most likely operator wages, possibly with tracking folded in. Still needs client confirmation.

  • Once C.1 is done, verify the reconstruction holds for all five financed machines across all 12 months, and report any that don't add up.

  • Ask the client what term 3 is. That is the last piece needed before fixed expenses can be computed from inputs rather than copied as a stored number.


Acceptance criteria

  • npm run typecheck exits clean
  • npm run build succeeds
  • Dev server stays idle when no files change (no recompile loop)
  • running_cost no longer exists on monthly_income; schema output pasted as proof
  • 40 of 40 machines have income, with zero auto-created assets
  • Seed exits non-zero and lists every failure when any row cannot be matched
  • assets contains only physical plant and vehicles — no Finance Costs, Insurance, or Tracking categories
  • TLB06, TLB07, MG02, RV04, and the Toyota Bakkie each appear exactly once
  • Every asset has a code; provisional codes are visibly marked as such
  • Plant Register is one sortable table with a working filter
  • Term 2 of the fixed-expense formula reconciles to insurance for all five financed machines
  • Palette, roundness, gradients, and dark mode visually unchanged