Seven things this is actually for.
Everything below is specified in the repository — in fourteen architecture chapters, forty-four decision records and a numbered requirements catalogue. What is not yet built is marked on the roadmap.
Fields you define, not fields somebody chose for you
A tenant administrator creates item types and location categories with their own fields — no developer, no restart, no migration. Types inherit, they are versioned, and each version generates the JSON Schema that both the server and the offline clients validate against.
- ·Sixteen field types, each with a defined control and six states
- ·Value lists and type templates, so a category does not have to be invented twice
- ·Stored as JSONB plus an application-maintained index table — configurable and still queryable
- ·There is no fixed item form. There is a renderer and this table.
| Field type | Control | Why that one |
|---|---|---|
| text · multiline | TextInput | Multiline grows, single-line does not |
| integer · decimal | TextInput | Right-aligned, with tabular figures |
| url · email | TextInput | A leading glyph and a format check |
| money | MoneyInput | Amount plus an attached currency cell |
| quantity | QuantityInput | The same anatomy as money — on purpose |
| boolean | Checkbox | Not a switch: a switch claims it is already saved |
| date · datetime | DateInput | The native picker: right on every platform, and offline |
| enum | Select / ComboBox | Above twelve options it becomes a search |
| multi-enum | TagInput | Chips wrap downward, never sideways |
| reference | ReferenceInput | Leaf name plus path; the picker follows the width |
| secret | SecretInput | “Wert verborgen” — never a row of dots |
| file | FileInput | A drop zone that is also a 44 px target |
A box inside a box inside a cellar
Storage locations nest to any depth, each with its own category and its own configurable fields. The path is what identifies a thing — so the breadcrumb collapses from the middle and keeps the last two segments, because “Shelf B” without “Workshop” is not an answer.
- ·Counts on every node, so you can see where the mass sits
- ·Mobile locations that can be sealed — a moving box is a location that travels
- ·Moving mode: scan the box, scan the room, everything inside follows
- ·At tablet width the tree collapses above the contents; on a phone it is its own route
Codes you can print, and read out loud
The public code is ten characters of Crockford Base32 plus a Damm check symbol — the alphabet has no I, L, O or U, so nothing can be misread as something else. It is not the database key: internally everything is UUIDv7, and the printed code can be pre-issued, bound later and reassigned.
- ·Print a sheet of blanks first, stick them on, bind them when you scan
- ·A label media catalogue with verification flags, start offset and a calibration sheet
- ·Templates with a bounded expression language; the print job is an aggregate with a state machine
- ·Resolution at
/c/{code}answers the same way for an unknown code and a forbidden one
The preview is dark-on-light in both appearances. An inverted code does not scan reliably, and a print preview is a picture of paper.
Scanning is a mode, not a screen
The same overlay serves five jobs; you pick one before you start, and the scanner keeps running. In the browser it uses the native barcode detector with a ZXing fallback; a HID handheld scanner types into the same session.
- Look up. Scan a code, land on the item.
- Bind. A pre-issued blank becomes this item's code.
- Move. Scan the thing, then scan where it now lives.
- Stocktake. Count against an expected list, report the difference.
- Capture. Create the item from the code you just stuck on.
- ·Foreign codes are recognised and can be bound: EAN, UPC, ITF-14, ISBN
- ·Stocktake mode counts against an expected list and ends in a discrepancy report
- ·Four distinct tones and haptics, because in a cellar you are not looking at the screen
- ·On a phone every control sits in the bottom 140 px — the band a thumb reaches
A week offline, then an honest reconciliation
The apps and the web client keep working without a network. When the connection returns, each change is pushed and comes back with one of five outcomes; only a real disagreement becomes a decision for you, field by field, with a three-way compare against the last common state.
- ·Merge rules per field kind: a counter adds up, a set unions, a text field asks
- ·The server version stays valid until you decide — nothing is silently overwritten
- ·Photos queue separately and upload with a determinate progress, not a spinner
- ·Devices are listed, can be deregistered, and can be wiped remotely
Both sides count the same — merged automatically.
Pick a side. On a phone these three options become full-width cards, one field at a time, with an “x of y decided” counter.
Every outbound connection is a plugin
The core talks to nothing outside the deployment. Mail, object storage, push, federated login and metadata lookups are plugins — each with a signed manifest, its own network segment, its own allowlist on the egress proxy, and capabilities granted per tenant. A plugin that misbehaves trips a circuit breaker, not the application.
The contract is home_inv.plugin.v1 over gRPC with mTLS, and the plugin API is Apache-2.0 so you can license your plugin however you like. It is proved by first-party plugins before it is published.
| Port | What attaches there |
|---|---|
| BlobStore | Filesystem in the core · S3/MinIO and Nextcloud as plugins |
| CodeFormat | QR shipped, further symbologies pluggable |
| PrintTarget | PDF shipped, label printers as plugins |
| ScanSource | Camera and HID in the core, Bluetooth scanners as plugins |
| MetadataResolver | ISBN and EAN lookup with a proposal model |
| ValuationProvider | Replacement values from external sources |
| Notification | plugin-smtp, plugin-webhook, Firebase, APNs, Web Push |
| Identity | plugin-oidc for federated login |
Security that was not retrofitted
Row-level security, the authorisation scaffolding, the content security policy and upload hardening are in the first stage, before the features that would have to be rewritten around them. The malware scan is mandatory and fail-closed in every profile — if the scanner is unreachable, the upload is refused rather than waved through.
HTTP/1.1 404 Not Found Content-Type: application/problem+json { "type": "https://home-inv.example/problems/not-found", "title": "Not found", "status": 404, "traceId": "0199c2f1-8d4e-7b1a-9f30-6c2f1b4ae7d2" }
traceId.REST · /api/v1
OpenAPI 3.1, written before the code. Cursor pagination, ETag and If-Match, idempotency keys, server-sent events.GraphQL · read-only
For views that would otherwise be six round trips. Writing stays with REST, where the rules live.gRPC · plugins
Internal only, mTLS, the versionedhome_inv.plugin.v1 contract with a sunset policy.None of this is installable yet. The order of work, and what “done” means for each stage, is on the roadmap.