- 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
10 KiB
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— addeddisable: process.env.NODE_ENV === "development".gitignore— addedpublic/sw.js,public/workbox-*.js,public/swe-worker-*.jsgit rm --cachedon 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_costfield from themonthly_incomecollection in PocketBase itself, not just from the seed payload. - Derive running cost on read by joining
monthly_running_costson(asset, year, month). - Update
buildIncomeRowsinlib/rows.tsto take running costs as a separate argument rather than readingr.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_linescollection:asset (relation), year, month, kind, amount, wherekindis one ofinstalment | insurance | tracking | operator | running. - Delete the
Finance Costs,Insurance, andTrackingcategories fromassetsentirely. - After this,
assetsshould 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
typefield onassets, 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
Typefilter 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
1300on 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 typecheckexits cleannpm run buildsucceeds- Dev server stays idle when no files change (no recompile loop)
running_costno longer exists onmonthly_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
assetscontains only physical plant and vehicles — noFinance Costs,Insurance, orTrackingcategories- 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