- SvelteKit 2 (Svelte 5) with TypeScript, static adapter (SPA mode) - Capacitor config for iOS/Android (so.toph.solidhaus) - Tailwind CSS v4 via Vite plugin - All dependencies: idb, nanoid, bwip-js, jspdf, Automerge, Solid - Route stubs: dashboard, scan, items, locations, labels, settings - Type definitions and ID generator utility - Project specification and ontology files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
22 KiB
SolidHaus — Household Inventory Management on Solid + RDF
Project Codename: SolidHaus Author: Christopher (toph) Date: 2026-02-26 Status: Design / Brainstorm Phase Version: 2.0
1. Vision & Goals
SolidHaus is a local-first, multi-user, Solid-compatible household inventory management system that lets you track every physical item in your home using semantic web technologies (RDF, schema.org, Linked Data). Items are identified by short 7-character auto-generated IDs (nanoid), which are pre-printed as QR codes on label sheets. The app scans these codes to log sightings, locations, and usage — building a living knowledge graph of your possessions.
Core Principles
- Data Sovereignty: All data lives in your Solid Pod (Community Solid Server / CSS). You own it.
- Local-First + Multi-User: Works offline, syncs between household members via CRDT (Automerge). No cloud dependency for core operations.
- Semantic Web Native: Uses schema.org vocabulary + custom ontology extensions. Data is RDF (JSON-LD / Turtle).
- Scan-First UX: Optimized for walking around with a phone, scanning barcodes, and quickly updating item locations.
- Pre-Printed Labels: Batch-generate QR code sheets with pre-allocated IDs. Peel, stick, scan to associate.
- Check-In / Check-Out: Items have a custody state — tracked from storage to active use and back.
- From Tools to Pantry: Supports durable goods, consumables, and perishables with quantity tracking.
2. Architecture Overview
┌─────────────────────────────────────────────────────┐
│ SolidHaus App │
│ (SvelteKit + Capacitor, native on Android/iOS) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────────────┐ │
│ │ Scanner │ │ Inventory│ │ Label Sheet │ │
│ │ (Camera) │ │ Browser │ │ Generator (PDF) │ │
│ └────┬─────┘ └────┬─────┘ └────────┬──────────┘ │
│ │ │ │ │
│ ┌────▼──────────────▼──────────────────▼──────────┐ │
│ │ Local Data Layer │ │
│ │ (IndexedDB + Automerge CRDT) │ │
│ └────────────────────┬────────────────────────────┘ │
│ │ │
│ ┌────────────────────▼────────────────────────────┐ │
│ │ Sync Layer │ │
│ │ Automerge-repo (WebSocket) + Solid RDF export │ │
│ └────────────────────┬────────────────────────────┘ │
└───────────────────────┼──────────────────────────────┘
│
┌─────────────┼──────────────┐
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ Automerge Sync │ │ Solid Pod │
│ Server (NixOS) │ │ (CSS / NSS) │
│ (WebSocket) │ │ /inventory/ │
└─────────────────┘ └──────────────────┘
Technology Stack
| Layer | Technology | Rationale |
|---|---|---|
| Framework | SvelteKit 2 (Svelte 5 with runes) | Compiler-first, minimal overhead, excellent DX |
| Native Wrapper | Capacitor | Android/iOS from single codebase, native API access |
| Build | Vite (built into SvelteKit) | Fast, modern bundler |
| Local Storage | IndexedDB (via idb) | Reliable offline storage for structured data |
| CRDT / Sync | Automerge + automerge-repo | Conflict-free multi-user sync between household members |
| RDF/Solid | @inrupt/solid-client, @inrupt/solid-client-authn-browser | Solid Pod interaction, RDF serialization |
| Barcode Scanning (native) | @capacitor-mlkit/barcode-scanning | ML Kit — fast, supports all formats on Android/iOS |
| Barcode Scanning (web) | Barcode Detection API + ZXing fallback | Native browser API on Chrome, polyfill for others |
| Barcode Rendering | bwip-js | Generate barcode/QR images for label sheets |
| ID Generation | nanoid (custom alphabet) | Short, URL-safe, collision-resistant IDs |
| Styling | Tailwind CSS 4 | Rapid mobile-first UI development |
| State Management | Svelte 5 runes ($state, $derived, $effect) | Built-in, no external library needed |
| Desktop (future) | Tauri 2 | Rust backend, officially supports SvelteKit |
3. Data Model — Schema.org + Custom Ontology
3.1 Core Types
We use schema:IndividualProduct as the base type for tracked items. This is the correct schema.org type for "a single, identifiable product instance (e.g., a laptop with a particular serial number)."
Item (schema:IndividualProduct)
@prefix schema: <https://schema.org/> .
@prefix solidhaus: <https://vocab.toph.so/solidhaus#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
<https://pod.toph.so/inventory/items/za3rbam>
a schema:IndividualProduct ;
schema:identifier "za3rbam" ;
schema:name "Bosch Drill PSB 1800" ;
schema:description "Cordless drill, green, bought at Bauhaus" ;
schema:category "Tools" ;
schema:brand [ a schema:Brand ; schema:name "Bosch" ] ;
schema:image <https://pod.toph.so/inventory/photos/za3rbam_01.jpg> ;
schema:serialNumber "SN-483920" ;
schema:purchaseDate "2024-03-15"^^xsd:date ;
schema:color "Green" ;
# Custom properties for inventory tracking
solidhaus:shortId "za3rbam" ;
solidhaus:itemType solidhaus:Durable ;
solidhaus:barcodeFormat "qr" ;
solidhaus:barcodeUri "https://haus.toph.so/za3rbam" ;
# Location tracking (latest state)
solidhaus:lastSeenAt <https://pod.toph.so/inventory/locations/werkstatt> ;
solidhaus:lastSeenTimestamp "2026-02-20T14:30:00Z"^^xsd:dateTime ;
solidhaus:supposedToBeAt <https://pod.toph.so/inventory/locations/werkstatt-pegboard> ;
solidhaus:locationConfidence "confirmed" ;
# Custody state
solidhaus:custodyState solidhaus:CheckedIn ;
solidhaus:storageTier solidhaus:HotStorage .
Location (schema:Place)
<https://pod.toph.so/inventory/locations/werkstatt>
a schema:Place ;
schema:name "Werkstatt" ;
schema:description "Basement workshop" ;
solidhaus:locationType "room" ;
solidhaus:defaultStorageTier solidhaus:HotStorage ;
schema:containedInPlace <https://pod.toph.so/inventory/locations/home> .
Sighting Event (schema:Action)
<https://pod.toph.so/inventory/sightings/s_x7k9m2>
a solidhaus:SightingEvent, schema:FindAction ;
schema:object <https://pod.toph.so/inventory/items/za3rbam> ;
schema:location <https://pod.toph.so/inventory/locations/werkstatt> ;
schema:startTime "2026-02-20T14:30:00Z"^^xsd:dateTime ;
schema:agent <https://pod.toph.so/profile/card#me> ;
solidhaus:sightingType "scan" ;
solidhaus:confidence "confirmed" .
3.2 Custom Ontology: solidhaus
Namespace: https://vocab.toph.so/solidhaus#
Full ontology defined in solidhaus.ttl. Key terms:
| Term | Type | Description |
|---|---|---|
shortId |
Property | 7-char nanoid identifier |
barcodeUri |
Property | URI encoded in barcode |
itemType |
Property | durable, consumable, perishable, etc. |
custodyState |
Property | checked-in or checked-out |
storageTier |
Property | hot, warm, or cold |
lastSeenAt |
Property | Where item was last observed |
supposedToBeAt |
Property | Designated storage location |
checkedOutReason |
Property | in-use, lent, in-repair, etc. |
currentQuantity |
Property | Amount remaining (consumables) |
quantityUnit |
Property | ml, g, pcs, %, etc. |
lowThreshold |
Property | Alert threshold |
CheckOutEvent |
Class | Subclass of schema:CheckOutAction |
CheckInEvent |
Class | Subclass of schema:CheckInAction |
3.3 Item Types & Lifecycle
type ItemType =
| 'durable' // Tools, furniture, electronics — long-lasting
| 'consumable' // Batteries, tape, screws — deplete with use
| 'disposable' // Paper plates, trash bags — single use
| 'perishable' // Food/drink with expiry dates
| 'media' // Books, DVDs, games
| 'clothing' // Wearable items
| 'document' // Papers, manuals, certificates
| 'container' // Boxes, bins — items that hold other items
;
Consumables and perishables have quantity tracking:
interface QuantityInfo {
currentQuantity: number | null; // e.g., 750
originalQuantity: number | null; // e.g., 1000
quantityUnit: string; // "ml", "g", "pcs", "sheets", "%"
lowThreshold: number | null; // alert when below (e.g., 100)
expiryDate: string | null; // ISO date, for perishables
}
3.4 Location Hierarchy
Locations form a tree using schema:containedInPlace:
Home (Residence)
├── Erdgeschoss (Floor)
│ ├── Küche (Room)
│ │ ├── Schublade Links (Drawer)
│ │ └── Regal über Spüle (Shelf)
│ ├── Wohnzimmer (Room)
│ │ └── TV-Schrank (Furniture)
│ └── Flur (Room)
├── Obergeschoss (Floor)
│ ├── Schlafzimmer (Room)
│ ├── Büro (Room)
│ │ ├── Schreibtisch (Furniture)
│ │ └── Regal (Shelf)
│ └── Bad (Room)
└── Keller (Floor)
└── Werkstatt (Room)
├── Werkbank (Furniture)
└── Werkzeugwand (Storage)
3.5 Custody Model — Check-In / Check-Out
Items have a custody state:
| State | Meaning | Icon |
|---|---|---|
| Checked In | At designated storage location, put away | ✅ 📥 |
| Checked Out | Removed from storage | 🔄 📤 |
Check-out reasons:
| Reason | Example |
|---|---|
| In Use | Drill on workbench, book being read |
| In Transit | Carrying tools to another room |
| Lent | Lent to neighbor Max (records who) |
| In Repair | Sent to repair shop |
| Temporary | Moved for a project |
| Consumed | Used up or disposed of (terminal) |
RDF Example
<items/za3rbam>
solidhaus:custodyState solidhaus:CheckedOut ;
solidhaus:checkedOutSince "2026-02-26T10:00:00Z"^^xsd:dateTime ;
solidhaus:checkedOutReason solidhaus:InUse ;
solidhaus:checkedOutFrom <locations/werkstatt-pegboard> ;
solidhaus:lastSeenAt <locations/werkstatt-workbench> ;
solidhaus:supposedToBeAt <locations/werkstatt-pegboard> ;
solidhaus:storageTier solidhaus:HotStorage .
3.6 Storage Tiers
| Tier | Name | Examples | Usage |
|---|---|---|---|
| 🔴 Hot | Active / Grab-and-go | Pegboard, desk, kitchen counter | Daily |
| 🟡 Warm | Nearby / Effort required | Cabinet, toolbox, high shelf | Weekly |
| 🔵 Cold | Deep / Archival | Attic boxes, basement bins | Seasonal |
For cold storage, storageContainer and storageContainerLabel track which box to dig out. Containers themselves can be tracked items.
3.7 Scan Actions (Context-Dependent)
If item is Checked In: "Check Out" | "Just Sighting" | "Move Storage" If item is Checked Out: "Check In" | "Still Using" | "Change Location"
Confidence decays: ≤30d → confirmed, ≤90d → likely, ≤180d → assumed, >180d → unknown. Overdue items (checked out >7d) surface on dashboard.
4. ID Generation & URI Strategy
4.1 Nanoid Configuration
import { customAlphabet } from 'nanoid';
const ALPHABET = '23456789abcdefghjkmnpqrstuvwxyz'; // no 0/o/1/i/l
const generateItemId = customAlphabet(ALPHABET, 7);
// Examples: "za3rbam", "k7xhw2n", "p4fvt8c"
30^7 ≈ 21.8 billion combinations. Lowercase for URI readability.
4.2 URI Scheme
Canonical QR content: https://haus.toph.so/{id}
Deep linking behavior:
- Native app installed (Capacitor): App Links / Universal Links → opens app
- PWA installed on Android: Chrome intercepts → opens PWA
- Nothing installed: Opens browser → landing page
Additionally register haus:// as custom scheme in Capacitor for direct launch.
4.3 ID Parsing
export function parseItemId(rawValue: string): string | null {
const httpsMatch = rawValue.match(/haus\.toph\.so\/([23456789a-hjkmnp-z]{7})$/);
if (httpsMatch) return httpsMatch[1];
const schemeMatch = rawValue.match(/^haus:\/\/([23456789a-hjkmnp-z]{7})$/);
if (schemeMatch) return schemeMatch[1];
const rawMatch = rawValue.match(/^[23456789a-hjkmnp-z]{7}$/);
if (rawMatch) return rawMatch[0];
return null;
}
4.4 Pre-Printed Label Sheets
Batch-generate QR code sheets with unassigned IDs. Apply labels physically, then scan to associate.
Workflow:
- Generate 50 new IDs → stored as "unassigned" in DB
- Download PDF (A4 sticker sheet) or print tape strip
- Apply labels to items
- Scan label → "Create new item with ID za3rbam?" → enter details
5. Multi-User Offline-First Sync
5.1 Architecture: Automerge CRDT
Device A ←→ Automerge Sync Server (NixOS) ←→ Device B
│
▼
Solid Pod (periodic RDF export)
Each device maintains a local Automerge document. automerge-repo handles sync via WebSocket relay. Offline changes accumulate locally and merge conflict-free when reconnected.
Solid Pod serves as long-term RDF archive, not real-time sync layer.
5.2 Automerge Document Structure
interface InventoryDoc {
items: { [shortId: string]: Item };
locations: { [id: string]: Location };
sightings: { [id: string]: Sighting };
preGeneratedIds: { [id: string]: PreGeneratedId };
}
5.3 Auth & Permissions
- Household members authenticate with Solid WebID
- Sync server: shared-secret or Solid-based auth
- Personal items can be
visibility: 'private'
5.4 NextGraph (Future)
CRDT-native RDF with E2EE, P2P sync, SPARQL. Could replace Automerge + Solid if it reaches production.
6. Solid Pod Structure
/inventory/
├── ontology.ttl
├── items/
│ ├── za3rbam.ttl
│ └── k7xhw2n.ttl
├── locations/
│ ├── home.ttl
│ └── werkstatt.ttl
├── sightings/
│ └── 2026-02/
│ └── s_x7k9m2.ttl
└── photos/
└── za3rbam_01.jpg
7. Barcode Scanning
7.1 Capacitor ML Kit (Primary)
import { BarcodeScanner, BarcodeFormat } from '@capacitor-mlkit/barcode-scanning';
const { barcodes } = await BarcodeScanner.scan({
formats: [BarcodeFormat.QrCode, BarcodeFormat.Code128, BarcodeFormat.DataMatrix],
});
7.2 Web Fallback
if ('BarcodeDetector' in window) {
const detector = new BarcodeDetector({ formats: ['qr_code', 'code_128', 'data_matrix'] });
const barcodes = await detector.detect(videoFrame);
}
// Fallback: html5-qrcode (ZXing WASM) for Safari/Firefox
7.3 Deep Linking (Capacitor)
AndroidManifest.xml registers haus:// scheme and https://haus.toph.so App Links. iOS Info.plist registers URL scheme + Associated Domains.
8. Label Generation & Printing
8.1 Pre-Generated ID Batches
IDs are allocated in batches (e.g., 50), stored as unassigned until scanned and linked to an item.
8.2 PDF Label Sheets
Generate A4 PDFs using Canvas API + jsPDF. Each cell: QR code + human-readable ID. Print on sticker paper.
8.3 Print Server (NixOS, Optional)
REST API for label tape printing. Accepts label data over HTTP, handles the printer protocol. Works from any device on LAN.
9. App Screens & User Flows
9.1 Main Screens
- Dashboard — Stats, recent sightings, overdue items, low-stock consumables
- Scanner — Camera + barcode detection
- Item Detail — Info, photos, history, quantity
- Item Create/Edit — Form with photo capture
- Location Browser — Hierarchical tree
- Search — Full-text search
- Label Sheets — Generate and manage batches
- Settings — Solid Pod, sync, household members
9.2 Key Flows
Scan & Check Out
Scan item → Card shows: ✅ Checked In at Pegboard (Hot)
→ Tap "Check Out" → reason: "In Use" → location: Workbench
→ 🔄 Checked Out (In Use) from Pegboard → at Workbench
Scan & Check In
Scan item → Card shows: 🔄 Checked Out since 2h ago
→ Tap "Check In" → confirm return to Pegboard
→ ✅ Checked In at Pegboard
Scan Pre-Printed Label → New Item
Scan unknown QR → ID recognized as pre-generated, unassigned
→ "Create new item?" → fill form → save → label associated
Quick Update Consumable
Scan olive oil → "400ml / 1000ml (40%)"
→ Quick: "Almost empty" | "Half left" | "Just opened"
→ Below threshold → "Add to shopping list?"
Walking Audit
Start Audit for "Büro" → shows expected items
→ Scan each → confirmed ✅
→ Summary: 12/15 confirmed, 3 missing
10. Implementation Phases
Phase 1: Core MVP (2-3 weeks)
- SvelteKit + Capacitor + Tailwind scaffold
- IndexedDB data layer (items, locations, sightings)
- Item create/edit with photo capture
- 7-char nanoid + QR generation (bwip-js)
- Scanner (Capacitor ML Kit + Barcode Detection API fallback)
- Location hierarchy, check-in/check-out, sighting logging
- Basic search, offline support
Phase 2: Multi-User Sync (1-2 weeks)
- Automerge doc structure + automerge-repo
- NixOS sync server, multi-device sync
Phase 3: Solid Integration (1-2 weeks)
- Solid auth, RDF serialization, periodic export
- Pod structure, ACLs, ontology at vocab.toph.so
Phase 4: Label Sheets (1 week)
- Pre-gen ID batches, A4 PDF generator
- Unassigned → item association flow
- Print server (NixOS, optional)
Phase 5: Consumables & Pantry (1 week)
- Item types, quantity tracking, low-stock alerts, expiry dates
Phase 6: Advanced (2+ weeks)
- Audit mode, confidence decay, stats, categories/tags, import/export
Phase 7: Future
- NextGraph, NFC tags, desktop via Tauri 2, product barcode lookup
11. Key Dependencies
{
"dependencies": {
"@inrupt/solid-client": "^2",
"@inrupt/solid-client-authn-browser": "^2",
"@automerge/automerge": "latest",
"@automerge/automerge-repo": "latest",
"@automerge/automerge-repo-network-websocket": "latest",
"@automerge/automerge-repo-storage-indexeddb": "latest",
"@capacitor/core": "^6",
"@capacitor-mlkit/barcode-scanning": "^6",
"nanoid": "^5",
"idb": "^8",
"bwip-js": "^4",
"date-fns": "^4",
"jspdf": "^2"
},
"devDependencies": {
"@sveltejs/adapter-static": "latest",
"tailwindcss": "^4",
"@tailwindcss/vite": "latest",
"typescript": "^5.7"
}
}
12. Project Structure
solidhaus/
├── src/
│ ├── routes/
│ │ ├── +layout.svelte # App shell, bottom nav
│ │ ├── +layout.ts # SSR disabled
│ │ ├── +page.svelte # Dashboard
│ │ ├── scan/+page.svelte
│ │ ├── items/
│ │ │ ├── +page.svelte
│ │ │ ├── new/+page.svelte
│ │ │ └── [id]/+page.svelte
│ │ ├── locations/+page.svelte
│ │ ├── labels/+page.svelte
│ │ └── settings/+page.svelte
│ ├── lib/
│ │ ├── components/ # .svelte files
│ │ ├── data/ # IndexedDB layer
│ │ ├── sync/ # Automerge + Solid export
│ │ ├── solid/ # Solid auth, RDF, pod
│ │ ├── scanning/ # Barcode detection + parsing
│ │ ├── printing/ # Label PDF gen, print server
│ │ ├── stores/ # Svelte 5 rune stores
│ │ ├── types/ # TypeScript types
│ │ ├── ontology/ # Namespace constants
│ │ └── utils/ # ID gen, confidence, formatting
│ └── app.html
├── static/
├── ontology/solidhaus.ttl
├── android/ # Capacitor
├── ios/ # Capacitor
├── svelte.config.js
├── capacitor.config.ts
├── vite.config.ts
└── package.json
13. Schema.org Actions
<sighting_xyz> a schema:UseAction ;
schema:object <item/za3rbam> ;
schema:agent <profile/card#me> ;
schema:startTime "2026-02-20T14:30:00Z" ;
schema:location <locations/werkstatt> .
<sighting_abc> a schema:MoveAction ;
schema:object <item/za3rbam> ;
schema:fromLocation <locations/werkstatt> ;
schema:toLocation <locations/buero> ;
schema:startTime "2026-02-21T09:00:00Z" .
14. Design System
Dark theme (slate-based): bg slate-900, surface slate-800, primary blue-500, confirmed emerald-500, warning amber-500, danger red-500.
Mobile: touch targets ≥44px, bottom sheet modals, haptic on scan (Capacitor Haptics), large monospace for IDs.
15. References
- Solid: solidproject.org, docs.inrupt.com, CommunitySolidServer
- Schema.org: IndividualProduct, Place, FindAction, CheckInAction, CheckOutAction
- SvelteKit + Capacitor: svelte.dev/docs/kit, capacitorjs.com, v2.tauri.app
- CRDT: automerge.org, nextgraph.org
- Barcode: MDN BarcodeDetector, bwip-js, html5-qrcode