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.

Vanilla JS · ExcelJS · File System Access API Status: v5, in production use

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

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

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

The decision

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

~82kallocations handled in-browser
0servers to run or approve
v5in real production use

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

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