kammer/PROJECT_SPECIFICATION.md
Christopher Mühl 3d246c00ec 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

616 lines
22 KiB
Markdown

# 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)
```turtle
@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)
```turtle
<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)
```turtle
<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
```typescript
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:
```typescript
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
```turtle
<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
```typescript
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
```typescript
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
```typescript
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)
```typescript
import { BarcodeScanner, BarcodeFormat } from '@capacitor-mlkit/barcode-scanning';
const { barcodes } = await BarcodeScanner.scan({
formats: [BarcodeFormat.QrCode, BarcodeFormat.Code128, BarcodeFormat.DataMatrix],
});
```
### 7.2 Web Fallback
```typescript
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
```json
{
"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
```turtle
<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