# 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**