- 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>
722 lines
26 KiB
Markdown
722 lines
26 KiB
Markdown
# SolidHaus — Addendum: Pre-printed Labels, Item Types, Consumables, Svelte, Multi-User
|
|
|
|
**Supersedes / amends:** PROJECT_SPECIFICATION.md, CLAUDE_CODE_GUIDE.md
|
|
**Date:** 2026-02-26 (v2)
|
|
|
|
---
|
|
|
|
## 1. Pre-Printed Label Sheets (Not On-Demand)
|
|
|
|
### The Concept
|
|
|
|
Instead of printing one label at a time when creating items, **pre-generate sheets of labels with unused IDs**. You peel off a label, stick it on an item, then scan it in the app to associate it with the item's data. This is similar to how asset tags work in enterprise IT.
|
|
|
|
### ID Format Change
|
|
|
|
**Old:** 6-character nanoid
|
|
**New:** 7-character nanoid from a 30-char alphabet (no ambiguous chars)
|
|
|
|
```typescript
|
|
import { customAlphabet } from 'nanoid';
|
|
const ALPHABET = '23456789abcdefghjkmnpqrstuvwxyz'; // lowercase, no 0/o/1/i/l
|
|
const generateId = customAlphabet(ALPHABET, 7);
|
|
// Examples: "za3rbam", "k7xhw2n", "p4fvt8c"
|
|
```
|
|
|
|
- 30^7 ≈ 21.8 billion combinations — effectively infinite for household use
|
|
- Lowercase is easier to type manually and reads better in URIs
|
|
- 7 chars gives more headroom for pre-generating batches without collision worry
|
|
|
|
### Label Sheet Generation
|
|
|
|
Generate a PDF of label sheets (A4 or letter), each cell containing:
|
|
|
|
```
|
|
┌────────────────┐
|
|
│ ▄▄▄▄▄ haus:// │
|
|
│ █ █ za3rbam │
|
|
│ █ █ │
|
|
│ ▀▀▀▀▀ │
|
|
└────────────────┘
|
|
```
|
|
|
|
For Brother P-touch tape, generate a continuous strip:
|
|
|
|
```
|
|
┌──────┬──────┬──────┬──────┬──────┬──────┐
|
|
│ QR │ QR │ QR │ QR │ QR │ QR │
|
|
│za3rb │k7xhw │p4fvt │m2bck │r9gnj │w5dtx │
|
|
└──────┴──────┴──────┴──────┴──────┴──────┘
|
|
```
|
|
|
|
### Pre-Generation Workflow
|
|
|
|
```
|
|
1. [App: Settings > Label Sheets]
|
|
2. [Generate 50 new IDs] → creates 50 "unassigned" ID records in DB
|
|
3. [Download PDF / Print to P-touch]
|
|
4. [Physically apply labels to items around the house]
|
|
5. [Scan label with app → "New item with ID za3rbam" → Enter name/details]
|
|
```
|
|
|
|
The IDs exist in the database as "unassigned" entries until scanned and linked to an item. This means scanning an unknown-but-valid ID (one that matches the format) prompts: "This looks like a SolidHaus label. Create a new item?"
|
|
|
|
### Implementation
|
|
|
|
```typescript
|
|
interface PreGeneratedId {
|
|
id: string; // the 7-char nanoid
|
|
generatedAt: string;
|
|
assignedTo: string | null; // null = unassigned, else item shortId
|
|
batchId: string; // which print batch this came from
|
|
}
|
|
|
|
// New IndexedDB store
|
|
preGeneratedIds: {
|
|
key: string;
|
|
value: PreGeneratedId;
|
|
indexes: {
|
|
'by-batch': string;
|
|
'by-assigned': string;
|
|
};
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 2. URI Scheme: `haus://`
|
|
|
|
### Format
|
|
|
|
```
|
|
haus://za3rbam
|
|
```
|
|
|
|
- **Protocol:** `haus://` — short, memorable, unique enough
|
|
- **Path:** the 7-character item ID
|
|
- **Full example in QR code:** `haus://za3rbam`
|
|
|
|
### How QR Scanning + Deep Linking Works
|
|
|
|
| Platform | Behavior when scanning `haus://za3rbam` |
|
|
|----------|----------------------------------------|
|
|
| **Android (Capacitor native app)** | Intent filter catches `haus://` scheme → opens app directly to item |
|
|
| **iOS (Capacitor native app)** | URL scheme registered in Info.plist → opens app directly to item |
|
|
| **Android (installed PWA via Chrome)** | PWA won't catch custom scheme. Needs workaround (see below). |
|
|
| **iOS (Safari PWA)** | Won't work. Custom schemes need native app. |
|
|
| **Any phone, no app installed** | Camera shows "haus://za3rbam" as text. User must manually open app. |
|
|
|
|
### The Deep Link Problem for PWAs
|
|
|
|
Custom URI schemes (`haus://`) **do not work with PWAs**. PWAs can only handle `https://` URLs on Android (via Web App Manifest scope + intent filters that Chrome creates when installing PWA).
|
|
|
|
**Dual-encoding solution**: Encode QR codes with an HTTPS URL that falls back gracefully:
|
|
|
|
```
|
|
https://haus.toph.so/za3rbam
|
|
```
|
|
|
|
This web address:
|
|
1. **If Capacitor native app is installed:** Android App Links / iOS Universal Links intercept → opens app
|
|
2. **If PWA is installed on Android:** Chrome's intent filter for the PWA scope intercepts → opens PWA
|
|
3. **If nothing installed:** Opens browser → landing page says "Install SolidHaus" with item preview
|
|
|
|
The QR code itself contains `https://haus.toph.so/za3rbam`. The app's ID parser extracts the 7-char ID from either format:
|
|
|
|
```typescript
|
|
export function parseItemId(rawValue: string): string | null {
|
|
// Pattern 1: HTTPS URI — https://haus.toph.so/za3rbam
|
|
const httpsMatch = rawValue.match(/haus\.toph\.so\/([23456789a-hjkmnp-z]{7})$/);
|
|
if (httpsMatch) return httpsMatch[1];
|
|
|
|
// Pattern 2: Custom scheme — haus://za3rbam
|
|
const schemeMatch = rawValue.match(/^haus:\/\/([23456789a-hjkmnp-z]{7})$/);
|
|
if (schemeMatch) return schemeMatch[1];
|
|
|
|
// Pattern 3: Raw ID
|
|
const rawMatch = rawValue.match(/^[23456789a-hjkmnp-z]{7}$/);
|
|
if (rawMatch) return rawMatch[0];
|
|
|
|
return null;
|
|
}
|
|
```
|
|
|
|
### Recommendation
|
|
|
|
**Use `https://haus.toph.so/{id}` as the canonical QR content.** Register `haus://` as a custom scheme in the Capacitor native app for bonus direct-launch capability. The HTTPS URL works everywhere and degrades gracefully.
|
|
|
|
---
|
|
|
|
## 3. Item Types & Lifecycle
|
|
|
|
### Type Taxonomy
|
|
|
|
```typescript
|
|
type ItemType =
|
|
| 'durable' // Long-lasting items (tools, furniture, electronics)
|
|
| 'consumable' // Items that get used up (batteries, tape, screws)
|
|
| 'disposable' // Single-use items (paper plates, trash bags)
|
|
| 'perishable' // Food/drink with expiry (pantry, fridge, freezer)
|
|
| 'media' // Books, DVDs, games
|
|
| 'clothing' // Wearable items
|
|
| 'document' // Important papers, manuals, certificates
|
|
| 'container' // Boxes, bins — items that hold other items
|
|
;
|
|
```
|
|
|
|
### RDF Mapping
|
|
|
|
```turtle
|
|
solidhaus:ItemType a rdfs:Class ;
|
|
rdfs:label "Item Type"@en .
|
|
|
|
solidhaus:Durable a solidhaus:ItemType ;
|
|
rdfs:label "Durable"@en ;
|
|
rdfs:comment "Long-lasting items: tools, furniture, electronics, appliances."@en .
|
|
|
|
solidhaus:Consumable a solidhaus:ItemType ;
|
|
rdfs:label "Consumable"@en ;
|
|
rdfs:comment "Items that deplete with use: batteries, tape, screws, ink, cleaning supplies."@en .
|
|
|
|
solidhaus:Disposable a solidhaus:ItemType ;
|
|
rdfs:label "Disposable"@en ;
|
|
rdfs:comment "Single-use items: paper plates, trash bags, wipes."@en .
|
|
|
|
solidhaus:Perishable a solidhaus:ItemType ;
|
|
rdfs:label "Perishable"@en ;
|
|
rdfs:comment "Food and drink with expiration dates."@en .
|
|
|
|
solidhaus:itemType a rdf:Property ;
|
|
rdfs:domain schema:IndividualProduct ;
|
|
rdfs:range solidhaus:ItemType .
|
|
```
|
|
|
|
### Quantitative Properties (for Consumables & Perishables)
|
|
|
|
Items that get used up need quantity tracking:
|
|
|
|
```typescript
|
|
interface QuantityInfo {
|
|
currentAmount: number | null; // e.g., 750
|
|
originalAmount: number | null; // e.g., 1000
|
|
unit: string; // e.g., "ml", "g", "pcs", "sheets", "%"
|
|
lowThreshold: number | null; // alert when below this (e.g., 100)
|
|
expiryDate: string | null; // ISO date, for perishables
|
|
}
|
|
```
|
|
|
|
RDF properties:
|
|
|
|
```turtle
|
|
solidhaus:currentQuantity a rdf:Property ;
|
|
rdfs:label "Current Quantity"@en ;
|
|
rdfs:comment "Current amount remaining. Pair with quantityUnit."@en ;
|
|
rdfs:domain schema:IndividualProduct ;
|
|
rdfs:range xsd:decimal .
|
|
|
|
solidhaus:originalQuantity a rdf:Property ;
|
|
rdfs:label "Original Quantity"@en ;
|
|
rdfs:comment "Amount when full/new."@en ;
|
|
rdfs:domain schema:IndividualProduct ;
|
|
rdfs:range xsd:decimal .
|
|
|
|
solidhaus:quantityUnit a rdf:Property ;
|
|
rdfs:label "Quantity Unit"@en ;
|
|
rdfs:comment "Unit of measurement: ml, l, g, kg, pcs, sheets, %."@en ;
|
|
rdfs:domain schema:IndividualProduct ;
|
|
rdfs:range xsd:string .
|
|
|
|
solidhaus:lowThreshold a rdf:Property ;
|
|
rdfs:label "Low Threshold"@en ;
|
|
rdfs:comment "Alert when currentQuantity drops below this value."@en ;
|
|
rdfs:domain schema:IndividualProduct ;
|
|
rdfs:range xsd:decimal .
|
|
```
|
|
|
|
schema.org already has `schema:weight` and `schema:size`, but for dynamic tracking of "how much is left" we need the custom properties above.
|
|
|
|
### Pantry / Kitchen Extension
|
|
|
|
With perishable items, this naturally extends to pantry management:
|
|
|
|
```turtle
|
|
<items/olive_oil_01>
|
|
a schema:IndividualProduct ;
|
|
schema:name "Olivenöl — Bertolli Extra Vergine" ;
|
|
solidhaus:itemType solidhaus:Consumable ;
|
|
solidhaus:currentQuantity 400 ;
|
|
solidhaus:originalQuantity 1000 ;
|
|
solidhaus:quantityUnit "ml" ;
|
|
solidhaus:lowThreshold 100 ;
|
|
solidhaus:custodyState solidhaus:CheckedIn ;
|
|
solidhaus:supposedToBeAt <locations/kueche-regal> ;
|
|
solidhaus:storageTier solidhaus:HotStorage .
|
|
|
|
<items/flour_01>
|
|
a schema:IndividualProduct ;
|
|
schema:name "Mehl Type 405" ;
|
|
solidhaus:itemType solidhaus:Perishable ;
|
|
solidhaus:currentQuantity 800 ;
|
|
solidhaus:originalQuantity 1000 ;
|
|
solidhaus:quantityUnit "g" ;
|
|
solidhaus:lowThreshold 200 ;
|
|
schema:expires "2026-09-15"^^xsd:date ;
|
|
solidhaus:supposedToBeAt <locations/kueche-schrank> .
|
|
```
|
|
|
|
### Quick-Update UX for Consumables
|
|
|
|
When scanning a consumable, offer a fast "update quantity" flow:
|
|
|
|
```
|
|
[Scan olive oil bottle]
|
|
→ [Card shows: "Olivenöl Bertolli — 400ml / 1000ml (40%)"]
|
|
→ [Quick actions: "Almost empty" | "Half left" | "Just opened" | "Custom"]
|
|
→ [Tap "Almost empty" → sets to ~100ml]
|
|
→ [If below threshold → "Add to shopping list?" prompt]
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Barcode Format Support
|
|
|
|
### What Formats Can Be Read?
|
|
|
|
| Format | Barcode Detection API (Chrome) | ZXing (fallback) | Capacitor ML Kit | Notes |
|
|
|--------|-------------------------------|-------------------|-----------------|-------|
|
|
| **QR Code** | ✅ | ✅ | ✅ | Our primary format |
|
|
| **DataMatrix** | ✅ | ✅ | ✅ | Very compact, great for small labels |
|
|
| **Code 128** | ✅ | ✅ | ✅ | Linear, good for tape labels |
|
|
| **EAN-13/UPC** | ✅ | ✅ | ✅ | Product barcodes (scan existing products) |
|
|
| **Aztec** | ✅ | ✅ | ✅ | |
|
|
| **PDF417** | ✅ | ✅ | ✅ | |
|
|
|
|
**All common formats are supported across all platforms.** QR is the best default because:
|
|
- Highest information density for small size
|
|
- Error correction built in
|
|
- Phone cameras auto-recognize and offer to open URLs
|
|
- Works well even when slightly damaged or at angle
|
|
|
|
DataMatrix is a great alternative for very small labels (it's more compact than QR at small sizes).
|
|
|
|
### Scanning Existing Product Barcodes
|
|
|
|
The app should also recognize standard EAN/UPC barcodes on commercial products. When scanning a product barcode (e.g., the EAN-13 on a bottle of olive oil):
|
|
1. Look up in local database first (maybe you've already cataloged it)
|
|
2. If not found, optionally query an open product database (Open Food Facts, etc.)
|
|
3. Offer to create a new item pre-filled with product info
|
|
|
|
This is a nice-to-have for Phase 2+.
|
|
|
|
---
|
|
|
|
## 5. Framework: SvelteKit + Capacitor (Not React)
|
|
|
|
### Why Svelte/SvelteKit Over React
|
|
|
|
Given the preference for Svelte, the recommended stack is:
|
|
|
|
| Layer | Technology |
|
|
|-------|-----------|
|
|
| **Framework** | SvelteKit 2 (Svelte 5 with runes) |
|
|
| **Build** | Vite (built into SvelteKit) |
|
|
| **Native wrapper** | Capacitor (Android + iOS) |
|
|
| **Desktop** | Tauri 2 (optional, later) |
|
|
| **Styling** | Tailwind CSS 4 |
|
|
| **State** | Svelte 5 runes ($state, $derived, $effect) — no external state library needed |
|
|
| **Routing** | SvelteKit file-based routing |
|
|
| **Barcode (native)** | @capacitor/barcode-scanner or @capacitor-mlkit/barcode-scanning |
|
|
| **Barcode (web)** | Barcode Detection API + ZXing fallback |
|
|
| **Local DB** | IndexedDB via idb (same as before) |
|
|
| **CRDT** | Automerge (for multi-user, see §6) |
|
|
|
|
### SvelteKit + Capacitor Setup
|
|
|
|
This is a well-documented, production-proven path:
|
|
|
|
```bash
|
|
# Create SvelteKit project
|
|
npx sv create solidhaus
|
|
cd solidhaus
|
|
|
|
# Add static adapter (required for Capacitor)
|
|
npm install -D @sveltejs/adapter-static
|
|
|
|
# Add Capacitor
|
|
npm install @capacitor/core @capacitor/cli
|
|
npx cap init solidhaus so.toph.solidhaus --web-dir build
|
|
|
|
# Add platforms
|
|
npm install @capacitor/android @capacitor/ios
|
|
npx cap add android
|
|
npx cap add ios
|
|
|
|
# Add barcode scanner
|
|
npm install @capacitor/barcode-scanner
|
|
# or for ML Kit (more powerful):
|
|
npm install @capacitor-mlkit/barcode-scanning
|
|
|
|
# Other deps
|
|
npm install nanoid idb bwip-js date-fns
|
|
npm install -D tailwindcss @tailwindcss/vite
|
|
```
|
|
|
|
**svelte.config.js:**
|
|
```javascript
|
|
import adapter from '@sveltejs/adapter-static';
|
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
|
|
|
export default {
|
|
preprocess: vitePreprocess(),
|
|
kit: {
|
|
adapter: adapter({
|
|
fallback: 'index.html' // SPA mode for Capacitor
|
|
})
|
|
}
|
|
};
|
|
```
|
|
|
|
**src/routes/+layout.ts:**
|
|
```typescript
|
|
export const ssr = false; // Capacitor needs client-side only
|
|
export const prerender = false;
|
|
```
|
|
|
|
### Deep Linking with Capacitor
|
|
|
|
Register the custom URI scheme and HTTPS App Links:
|
|
|
|
**capacitor.config.ts:**
|
|
```typescript
|
|
const config: CapacitorConfig = {
|
|
appId: 'so.toph.solidhaus',
|
|
appName: 'SolidHaus',
|
|
webDir: 'build',
|
|
server: {
|
|
// For deep linking via HTTPS
|
|
androidScheme: 'https'
|
|
},
|
|
plugins: {
|
|
// App Links / Universal Links configuration
|
|
}
|
|
};
|
|
```
|
|
|
|
**AndroidManifest.xml additions:**
|
|
```xml
|
|
<!-- Custom scheme: haus:// -->
|
|
<intent-filter>
|
|
<action android:name="android.intent.action.VIEW" />
|
|
<category android:name="android.intent.category.DEFAULT" />
|
|
<category android:name="android.intent.category.BROWSABLE" />
|
|
<data android:scheme="haus" />
|
|
</intent-filter>
|
|
|
|
<!-- HTTPS App Links: https://haus.toph.so/* -->
|
|
<intent-filter android:autoVerify="true">
|
|
<action android:name="android.intent.action.VIEW" />
|
|
<category android:name="android.intent.category.DEFAULT" />
|
|
<category android:name="android.intent.category.BROWSABLE" />
|
|
<data android:scheme="https" android:host="haus.toph.so" />
|
|
</intent-filter>
|
|
```
|
|
|
|
### Is There a "Svelte Native"?
|
|
|
|
**Short answer: not really, not in a usable way.**
|
|
|
|
- **NativeScript + Svelte** existed but is stuck on Svelte 4 and not actively maintained for Svelte 5.
|
|
- **Lynx** (ByteDance) has an agnostic core but only works with React currently. Svelte support requires a custom renderer API that's being worked on by the Mainmatter team but isn't ready.
|
|
- **Capacitor** is the pragmatic choice — it wraps your web app in a native WebView with full access to native APIs (camera, Bluetooth, file system, deep links). It's not "truly native" rendering, but for an inventory app the performance difference is negligible.
|
|
- **Tauri 2** supports Android and iOS and could be an alternative to Capacitor, with Rust backend instead of Java/Swift. More bleeding-edge but the SvelteKit integration is officially documented.
|
|
|
|
**Recommendation: SvelteKit + Capacitor.** It's the most mature path for Svelte on mobile with native features (barcode scanning, deep links, Bluetooth).
|
|
|
|
---
|
|
|
|
## 6. Multi-User & Offline-First (Hard Requirement)
|
|
|
|
### The Scenario
|
|
|
|
Multiple household members scan/update items independently:
|
|
- Person A scans the drill in the workshop (phone offline in basement)
|
|
- Person B logs the drill as lent to neighbor (phone online upstairs)
|
|
- Both changes eventually sync and resolve
|
|
|
|
### Architecture: CRDT + Solid Pod
|
|
|
|
With multi-user as a hard requirement, simple LWW is no longer sufficient. We need CRDTs.
|
|
|
|
**Recommended approach: Automerge as the sync layer, Solid Pod as the persistence/sharing layer.**
|
|
|
|
```
|
|
Device A (Phone) Device B (Tablet)
|
|
│ │
|
|
▼ ▼
|
|
┌─────────────┐ ┌─────────────┐
|
|
│ Local │ │ Local │
|
|
│ Automerge │ │ Automerge │
|
|
│ Document │ │ Document │
|
|
│ (IndexedDB) │ │ (IndexedDB) │
|
|
└──────┬──────┘ └──────┬──────┘
|
|
│ │
|
|
▼ ▼
|
|
┌──────────────────────────────┐
|
|
│ Sync Server │
|
|
│ (automerge-repo-server │
|
|
│ on NixOS infrastructure) │
|
|
└──────────────┬───────────────┘
|
|
│
|
|
▼
|
|
┌───────────┐
|
|
│ Solid Pod │
|
|
│ (RDF export│
|
|
│ periodic) │
|
|
└───────────┘
|
|
```
|
|
|
|
### How It Works
|
|
|
|
1. **Each device** maintains a local Automerge document with all inventory data
|
|
2. **Automerge-repo** handles sync between devices via a WebSocket relay server
|
|
3. **Offline**: Changes accumulate locally in the Automerge doc. When back online, automerge-repo syncs automatically — merges are conflict-free by design
|
|
4. **Solid Pod integration**: A periodic job serializes the Automerge state to RDF/Turtle and writes it to the Solid Pod. This is the "canonical" linked data representation. The Solid Pod is the long-term archive and interoperability layer, not the real-time sync layer.
|
|
|
|
### Why Not Use Solid Pod Directly for Sync?
|
|
|
|
Solid Pods use HTTP REST (LDP). They're great for storage and sharing but not for real-time multi-device sync:
|
|
- No built-in conflict resolution
|
|
- Polling-based (Solid Notifications is still maturing)
|
|
- High latency for offline→online sync bursts
|
|
|
|
CRDTs via Automerge solve the hard sync problem; Solid solves the data ownership/interoperability problem. They're complementary.
|
|
|
|
### Automerge Document Structure
|
|
|
|
```typescript
|
|
import { next as Automerge } from '@automerge/automerge';
|
|
|
|
interface InventoryDoc {
|
|
items: { [shortId: string]: Item };
|
|
locations: { [id: string]: Location };
|
|
sightings: { [id: string]: Sighting };
|
|
preGeneratedIds: { [id: string]: PreGeneratedId };
|
|
}
|
|
|
|
// Create and modify
|
|
let doc = Automerge.init<InventoryDoc>();
|
|
doc = Automerge.change(doc, 'Add new item', d => {
|
|
d.items['za3rbam'] = {
|
|
shortId: 'za3rbam',
|
|
name: 'Bosch Drill',
|
|
custodyState: 'checked-in',
|
|
// ...
|
|
};
|
|
});
|
|
```
|
|
|
|
### Auth & Permissions
|
|
|
|
- Each household member authenticates with their Solid WebID
|
|
- The Automerge sync server can use simple shared-secret or Solid-based auth
|
|
- All household members have read/write access to the shared inventory
|
|
- Personal items can be marked as `visibility: 'private'` (only visible to owner)
|
|
|
|
### Sync Server (NixOS)
|
|
|
|
The automerge-repo sync server is a small Node.js process:
|
|
|
|
```nix
|
|
# NixOS module sketch
|
|
services.solidhaus-sync = {
|
|
enable = true;
|
|
port = 3030;
|
|
package = pkgs.nodejs;
|
|
# automerge-repo-network-websocket server
|
|
};
|
|
```
|
|
|
|
This runs alongside your existing Solid pod (CSS) and other infrastructure.
|
|
|
|
### Future: NextGraph
|
|
|
|
If NextGraph reaches production readiness, it could replace the entire Automerge + Solid Pod stack with a single integrated solution: CRDT-native RDF, E2EE, P2P sync, SPARQL queries — all in one. Keep monitoring.
|
|
|
|
---
|
|
|
|
## 7. Updated Project Structure (SvelteKit)
|
|
|
|
```
|
|
solidhaus/
|
|
├── src/
|
|
│ ├── routes/
|
|
│ │ ├── +layout.svelte # App shell, bottom nav
|
|
│ │ ├── +layout.ts # SSR disabled
|
|
│ │ ├── +page.svelte # Dashboard
|
|
│ │ ├── scan/
|
|
│ │ │ └── +page.svelte # Scanner view
|
|
│ │ ├── items/
|
|
│ │ │ ├── +page.svelte # Item list
|
|
│ │ │ ├── new/+page.svelte # Create item
|
|
│ │ │ └── [id]/
|
|
│ │ │ ├── +page.svelte # Item detail
|
|
│ │ │ └── edit/+page.svelte
|
|
│ │ ├── locations/
|
|
│ │ │ └── +page.svelte # Location tree
|
|
│ │ ├── labels/
|
|
│ │ │ └── +page.svelte # Label sheet generator + print queue
|
|
│ │ └── settings/
|
|
│ │ └── +page.svelte # Settings, sync status, pod config
|
|
│ ├── lib/
|
|
│ │ ├── components/
|
|
│ │ │ ├── Scanner.svelte
|
|
│ │ │ ├── ItemCard.svelte
|
|
│ │ │ ├── ItemForm.svelte
|
|
│ │ │ ├── LocationPicker.svelte
|
|
│ │ │ ├── LocationTree.svelte
|
|
│ │ │ ├── QuantitySlider.svelte # For consumables
|
|
│ │ │ ├── ConfidenceBadge.svelte
|
|
│ │ │ ├── CustodyBadge.svelte
|
|
│ │ │ ├── LabelPreview.svelte
|
|
│ │ │ ├── BottomNav.svelte
|
|
│ │ │ └── PhotoCapture.svelte
|
|
│ │ ├── data/
|
|
│ │ │ ├── db.ts # IndexedDB
|
|
│ │ │ ├── items.ts
|
|
│ │ │ ├── locations.ts
|
|
│ │ │ ├── sightings.ts
|
|
│ │ │ └── labels.ts # Pre-generated ID management
|
|
│ │ ├── sync/
|
|
│ │ │ ├── automerge.ts # Automerge doc management
|
|
│ │ │ ├── repo.ts # automerge-repo setup
|
|
│ │ │ └── solid-export.ts # Periodic RDF export to Solid Pod
|
|
│ │ ├── solid/
|
|
│ │ │ ├── auth.ts
|
|
│ │ │ ├── rdf.ts
|
|
│ │ │ └── pod.ts
|
|
│ │ ├── scanning/
|
|
│ │ │ ├── detector.ts # Barcode detection (native + web)
|
|
│ │ │ └── parser.ts # ID extraction from barcodes
|
|
│ │ ├── printing/
|
|
│ │ │ ├── labelSheet.ts # PDF label sheet generation
|
|
│ │ │ ├── tapeStrip.ts # P-touch tape strip generation
|
|
│ │ │ └── printServer.ts # HTTP print server client
|
|
│ │ ├── stores/
|
|
│ │ │ ├── inventory.svelte.ts # Svelte 5 runes-based stores
|
|
│ │ │ ├── scan.svelte.ts
|
|
│ │ │ ├── sync.svelte.ts
|
|
│ │ │ └── settings.svelte.ts
|
|
│ │ ├── types/
|
|
│ │ │ ├── item.ts
|
|
│ │ │ ├── location.ts
|
|
│ │ │ ├── sighting.ts
|
|
│ │ │ └── quantity.ts
|
|
│ │ ├── ontology/
|
|
│ │ │ ├── solidhaus.ts # Namespace constants
|
|
│ │ │ └── namespaces.ts
|
|
│ │ └── utils/
|
|
│ │ ├── id.ts # nanoid generator
|
|
│ │ ├── confidence.ts
|
|
│ │ └── format.ts # Quantity formatting ("400ml / 1L")
|
|
│ └── app.html
|
|
├── static/
|
|
│ ├── manifest.json
|
|
│ └── icons/
|
|
├── ontology/
|
|
│ └── solidhaus.ttl
|
|
├── android/ # Capacitor Android project
|
|
├── ios/ # Capacitor iOS project
|
|
├── svelte.config.js
|
|
├── capacitor.config.ts
|
|
├── vite.config.ts
|
|
├── tailwind.config.ts
|
|
├── package.json
|
|
└── tsconfig.json
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Updated IndexedDB Schema (Items)
|
|
|
|
```typescript
|
|
interface Item {
|
|
shortId: string; // 7-char nanoid
|
|
name: string;
|
|
description: string;
|
|
category: string;
|
|
brand: string;
|
|
serialNumber: string;
|
|
color: string;
|
|
purchaseDate: string | null;
|
|
|
|
// Item type
|
|
itemType: 'durable' | 'consumable' | 'disposable' | 'perishable'
|
|
| 'media' | 'clothing' | 'document' | 'container';
|
|
|
|
// Quantity (for consumables/perishables)
|
|
currentQuantity: number | null;
|
|
originalQuantity: number | null;
|
|
quantityUnit: string | null; // "ml", "g", "pcs", "sheets", "%"
|
|
lowThreshold: number | null;
|
|
expiryDate: string | null; // ISO date
|
|
|
|
// Barcode
|
|
barcodeUri: string; // "https://haus.toph.so/za3rbam"
|
|
barcodeFormat: 'qr' | 'datamatrix' | 'code128';
|
|
photoIds: string[];
|
|
|
|
// Location tracking
|
|
lastSeenAt: string | null;
|
|
lastSeenTimestamp: string | null;
|
|
lastUsedAt: string | null;
|
|
supposedToBeAt: string | null;
|
|
locationConfidence: 'confirmed' | 'likely' | 'assumed' | 'unknown';
|
|
|
|
// Custody state
|
|
custodyState: 'checked-in' | 'checked-out';
|
|
checkedOutSince: string | null;
|
|
checkedOutReason: 'in-use' | 'in-transit' | 'lent' | 'in-repair' | 'temporary' | 'consumed' | null;
|
|
checkedOutFrom: string | null;
|
|
checkedOutTo: string | null;
|
|
checkedOutNote: string | null;
|
|
|
|
// Storage
|
|
storageTier: 'hot' | 'warm' | 'cold';
|
|
storageContainerId: string | null;
|
|
storageContainerLabel: string | null;
|
|
|
|
// Label
|
|
labelPrinted: boolean;
|
|
labelPrintedAt: string | null;
|
|
labelBatchId: string | null; // which pre-gen batch
|
|
|
|
// Metadata
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
tags: string[];
|
|
createdBy: string | null; // WebID of creator (multi-user)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Summary of All Decisions
|
|
|
|
| Decision | Choice | Rationale |
|
|
|----------|--------|-----------|
|
|
| ID length | 7 characters | More headroom for pre-generation batches |
|
|
| ID alphabet | Lowercase + digits, no ambiguous | Easier to type in URIs |
|
|
| QR content | `https://haus.toph.so/{id}` | Works everywhere, degrades gracefully |
|
|
| Custom scheme | `haus://` (bonus, native only) | Direct app launch when native app installed |
|
|
| Labels | Pre-printed sheets/strips | Faster than on-demand, batch workflow |
|
|
| Framework | SvelteKit 5 + Capacitor | Svelte preference, native features via Capacitor |
|
|
| Native mobile | Capacitor (not Svelte Native) | Svelte Native doesn't exist in usable form |
|
|
| Desktop | Tauri 2 (future) | Rust backend, SvelteKit frontend, officially supported |
|
|
| Multi-user sync | Automerge (CRDT) | Conflict-free offline-first sync between household members |
|
|
| Data ownership | Solid Pod (RDF export) | Long-term archive, interoperability, data sovereignty |
|
|
| Real-time sync | automerge-repo WebSocket server | Low-latency device-to-device sync |
|
|
| Barcode scanning (native) | @capacitor-mlkit/barcode-scanning | ML Kit is fast, supports all formats |
|
|
| Barcode scanning (web) | Barcode Detection API + ZXing | Progressive enhancement |
|
|
| Item types | durable/consumable/disposable/perishable/... | Naturally extends to pantry tracking |
|
|
| Quantity tracking | currentQuantity + unit + threshold | Consumables need "how much is left" |
|
|
| Printing | Pre-gen PDF sheets + P-touch strips via print server | Batch workflow, NixOS service |
|