Case study · Flagship · Business app
Shipyard IMS: an ERP for a shipyard that lives in a browser tab
FIFO lot tracking, milestone billing and Excel round-tripping for a real marine business — around 82,000 allocations, no backend, no install, no IT ticket.
01The problem
A working shipyard tracked materials and hull project costs the way most SMEs do: a pile of Excel workbooks, one per supplier and per project, reconciled by hand. The moment you want a real answer — what did this hull actually cost, which lot did that steel come from, how much is still un-billed — the spreadsheets fight you. FIFO costing across dozens of supplier invoices is not something a human wants to do by hand every week, and getting it wrong shows up directly in the margin.
They needed the rigour of an ERP: proper lot tracking, markup-configurable billing tied to payment milestones, and audited numbers. What they did not need was an ERP rollout — cloud subscriptions, a login server, an IT approval cycle, and staff retraining onto software that felt nothing like the sheets they already trusted.
02Constraints
- No IT approval, no server. Whatever I shipped had to run on the machines they already had, without anyone provisioning infrastructure.
- Excel stays the source of truth. The business lives in spreadsheets. The tool had to read their workbooks and hand them back clean Excel, not lock data inside a proprietary format.
- Real scale. A live dataset runs to roughly 82,000 allocation rows. It had to stay responsive on ordinary office laptops.
- Correctness is non-negotiable. This is money. FIFO consumption, markup and billing milestones have to reconcile to the cent, and every change has to be reversible.
03Approach
I built it as a single self-contained web app — HTML, CSS and vanilla JavaScript with ExcelJS bundled in — that the user opens like any local file. There is no framework and no build server in the runtime path; build scripts only inline and merge the assets into one distributable.
The design splits cleanly into three layers. A costing core holds the domain model — lots, supplier invoices, issuances to hulls and personnel, markup rules and billing milestones — as plain data structures with pure functions over them. A thin persistence layer uses the File System Access API to open and save the user's Excel workbook, backed by localStorage snapshots and rolling auto-backups. On top sits the UI: financial dashboards, project timelines, a command palette, undo/redo, an import wizard and an onboarding tour. This is the same personal design system I reuse across my other operations apps, so the whole thing feels like one product rather than a stitched-together tool.
04Architecture
Excel workbook
Source of truth on disk. Opened & saved by the user.
File System API
ExcelJS parses & writes. Heavy work off the main thread.
Costing core
FIFO lots, markup, milestones. Incremental indexes over ~82k rows.
UI + backups
Dashboards, undo/redo, localStorage snapshots, auto-backup.
The critical property: the costing core never talks to the network and never touches the DOM directly. It is pure data-in, numbers-out, which is what makes it testable and fast.
05One hard decision & the trade-off
Keep the entire working dataset — all ~82k allocations — resident in memory and recompute costing incrementally, rather than paging data through IndexedDB or a query layer.
An in-memory model made FIFO consumption and milestone billing dramatically simpler to reason about and to keep correct: no async boundaries in the middle of a costing pass, no cache-invalidation bugs. The trade-off is a heavier first load — parsing a large workbook and building the indexes costs a few seconds and a real chunk of RAM — and it commits the app to Chromium, where the File System Access API and the memory headroom actually exist. For a known set of office machines that was the right call; the correctness and the sub-frame edit latency were worth far more than portability to every browser.
06Outcome
The shipyard runs its inventory and hull project costing out of a browser tab. FIFO lots reconcile automatically, billing tracks payment milestones with configurable markup, and the numbers export straight back to Excel for anyone downstream. Because it is self-contained, deployment is copying a file — there was never an IT ticket, a migration, or a subscription. It also became the origin of the design system I now reuse across my scheduling and inventory tools.
Tech
- Vanilla JS
- ExcelJS
- File System Access API
- localStorage
- FIFO costing engine
- Zero build step
07FAQ
How does an inventory system run with no backend?
Everything lives in the browser tab. State is kept in memory and mirrored to localStorage, while the source of truth is an Excel workbook the user opens and saves through the File System Access API. There is no server to install, patch or get past IT — the whole app is HTML, CSS and JavaScript with ExcelJS bundled in.
Doesn't 82,000 allocations make a browser app slow?
It can, if you rebuild the DOM naively. The costing engine works on plain JavaScript arrays and maps, keeps derived totals in indexes that update incrementally, and the tables render only the visible rows. The heavy Excel parsing runs off the main thread so the UI stays responsive during import.
What happens to the data if the browser or laptop dies?
The canonical data is the Excel file on disk, so it survives independently of the browser. On top of that the app keeps rolling auto-backups and a localStorage snapshot, and every destructive action is undoable, so a crash mid-edit loses seconds of work, not a day of it.
Need an internal tool that runs without IT approval?
Let's talk. If your team is drowning in spreadsheets, there is usually a self-contained tool that fixes it.
Get in touch