kammer/PROJECT_SPECIFICATION.md
Christopher Mühl d017987553 chore: scaffold SvelteKit project with Capacitor and Tailwind
- 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>
2026-02-26 14:50:10 +01:00

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:

  1. Native app installed (Capacitor): App Links / Universal Links → opens app
  2. PWA installed on Android: Chrome intercepts → opens PWA
  3. 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:

  1. Generate 50 new IDs → stored as "unassigned" in DB
  2. Download PDF (A4 sticker sheet) or print tape strip
  3. Apply labels to items
  4. 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

  1. Dashboard — Stats, recent sightings, overdue items, low-stock consumables
  2. Scanner — Camera + barcode detection
  3. Item Detail — Info, photos, history, quantity
  4. Item Create/Edit — Form with photo capture
  5. Location Browser — Hierarchical tree
  6. Search — Full-text search
  7. Label Sheets — Generate and manage batches
  8. 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