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>
This commit is contained in:
Christopher Mühl 2026-02-26 14:50:10 +01:00
commit d017987553
31 changed files with 3853 additions and 0 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
node_modules/
.svelte-kit/
build/
.env
.env.*
!.env.example
android/
ios/
*.local
.DS_Store

722
ADDENDUM_V2.md Normal file
View file

@ -0,0 +1,722 @@
# 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 |

800
CLAUDE.md Normal file
View file

@ -0,0 +1,800 @@
# SolidHaus — Claude Code Implementation Guide
This document provides step-by-step implementation instructions for Claude Code.
## Overview
- **Framework**: SvelteKit 2 (Svelte 5 with runes) + Capacitor
- **Goal**: Local-first, multi-user household inventory app with barcode scanning
- **Key deps**: idb, nanoid, bwip-js, @capacitor-mlkit/barcode-scanning, automerge, @inrupt/solid-client
## Priority: Phase 1 — Core MVP
Build the app incrementally. Each task builds on the previous.
---
## Task 1: Project Scaffold
Create a SvelteKit project with Capacitor and Tailwind CSS.
```bash
npx sv create solidhaus
cd solidhaus
# Static adapter for Capacitor
npm install -D @sveltejs/adapter-static
# Capacitor
npm install @capacitor/core @capacitor/cli
npx cap init solidhaus so.toph.solidhaus --web-dir build
npm install @capacitor/android @capacitor/ios
npx cap add android
npx cap add ios
# Barcode scanner
npm install @capacitor-mlkit/barcode-scanning
# Core deps
npm install idb nanoid date-fns bwip-js jspdf
# Solid (Phase 3, but install now)
npm install @inrupt/solid-client @inrupt/solid-client-authn-browser @inrupt/vocab-common-rdf
# Automerge (Phase 2, but install now)
npm install @automerge/automerge @automerge/automerge-repo @automerge/automerge-repo-network-websocket @automerge/automerge-repo-storage-indexeddb
# Dev
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;
export const prerender = false;
```
### vite.config.ts:
```typescript
import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
tailwindcss(),
sveltekit(),
],
});
```
### capacitor.config.ts:
```typescript
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'so.toph.solidhaus',
appName: 'SolidHaus',
webDir: 'build',
server: {
androidScheme: 'https'
}
};
export default config;
```
### File structure:
Follow the project structure defined in PROJECT_SPECIFICATION.md §12.
---
## Task 2: Data Layer (IndexedDB)
Create `src/lib/data/db.ts` using the `idb` library.
### Database Schema:
```typescript
import { openDB, DBSchema } from 'idb';
interface SolidHausDB extends DBSchema {
items: {
key: string; // shortId
value: {
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 (consumables/perishables)
currentQuantity: number | null;
originalQuantity: number | null;
quantityUnit: string | null;
lowThreshold: number | null;
expiryDate: string | null;
// Barcode
barcodeFormat: 'qr' | 'datamatrix' | 'code128';
barcodeUri: string;
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 tier
storageTier: 'hot' | 'warm' | 'cold';
storageContainerId: string | null;
storageContainerLabel: string | null;
// Label
labelPrinted: boolean;
labelPrintedAt: string | null;
labelBatchId: string | null;
// Metadata
createdAt: string;
updatedAt: string;
tags: string[];
createdBy: string | null;
};
indexes: {
'by-name': string;
'by-category': string;
'by-location': string;
'by-type': string;
'by-custody': string;
};
};
locations: {
key: string;
value: {
id: string;
name: string;
description: string;
parentId: string | null;
locationType: 'house' | 'floor' | 'room' | 'furniture' | 'shelf' | 'drawer' | 'box' | 'wall' | 'outdoor';
defaultStorageTier: 'hot' | 'warm' | 'cold' | null;
sortOrder: number;
createdAt: string;
updatedAt: string;
};
indexes: {
'by-parent': string;
'by-name': string;
};
};
sightings: {
key: string;
value: {
id: string;
itemId: string;
locationId: string;
timestamp: string;
sightingType: 'scan' | 'manual' | 'camera-detect' | 'audit-verify';
confidence: 'confirmed' | 'inferred' | 'assumed';
notes: string;
createdBy: string | null;
};
indexes: {
'by-item': string;
'by-location': string;
'by-timestamp': string;
};
};
photos: {
key: string;
value: {
id: string;
itemId: string;
blob: Blob;
thumbnail: Blob | null;
createdAt: string;
};
indexes: {
'by-item': string;
};
};
preGeneratedIds: {
key: string;
value: {
id: string;
generatedAt: string;
assignedTo: string | null;
batchId: string;
};
indexes: {
'by-batch': string;
'by-assigned': string;
};
};
settings: {
key: string;
value: {
key: string;
value: unknown;
};
};
}
const DB_NAME = 'solidhaus';
const DB_VERSION = 1;
export async function getDB() {
return openDB<SolidHausDB>(DB_NAME, DB_VERSION, {
upgrade(db) {
// Items
const itemStore = db.createObjectStore('items', { keyPath: 'shortId' });
itemStore.createIndex('by-name', 'name');
itemStore.createIndex('by-category', 'category');
itemStore.createIndex('by-location', 'lastSeenAt');
itemStore.createIndex('by-type', 'itemType');
itemStore.createIndex('by-custody', 'custodyState');
// Locations
const locStore = db.createObjectStore('locations', { keyPath: 'id' });
locStore.createIndex('by-parent', 'parentId');
locStore.createIndex('by-name', 'name');
// Sightings
const sightStore = db.createObjectStore('sightings', { keyPath: 'id' });
sightStore.createIndex('by-item', 'itemId');
sightStore.createIndex('by-location', 'locationId');
sightStore.createIndex('by-timestamp', 'timestamp');
// Photos
const photoStore = db.createObjectStore('photos', { keyPath: 'id' });
photoStore.createIndex('by-item', 'itemId');
// Pre-generated IDs
const pregenStore = db.createObjectStore('preGeneratedIds', { keyPath: 'id' });
pregenStore.createIndex('by-batch', 'batchId');
pregenStore.createIndex('by-assigned', 'assignedTo');
// Settings
db.createObjectStore('settings', { keyPath: 'key' });
},
});
}
```
### CRUD Functions:
Create `src/lib/data/items.ts`, `locations.ts`, `sightings.ts`, `labels.ts` with standard CRUD:
- `getAll()`, `getById()`, `create()`, `update()`, `remove()`
- Each function gets the DB instance via `getDB()` and performs the operation.
### ID Generator:
```typescript
// src/lib/utils/id.ts
import { customAlphabet } from 'nanoid';
const ALPHABET = '23456789abcdefghjkmnpqrstuvwxyz';
export const generateItemId = customAlphabet(ALPHABET, 7);
export const generateSightingId = () => `s_${generateItemId()}`;
```
---
## Task 3: Barcode Scanner Component
Create `src/lib/components/Scanner.svelte`.
### Requirements:
- On native (Capacitor): use @capacitor-mlkit/barcode-scanning
- On web: use Barcode Detection API with ZXing fallback
- Detect QR Code, Code 128, DataMatrix, EAN-13
- Parse detected value through `parseItemId()`
- Haptic feedback on successful scan (Capacitor Haptics)
### Implementation approach:
```typescript
// src/lib/scanning/detector.ts
import { Capacitor } from '@capacitor/core';
import { BarcodeScanner, BarcodeFormat } from '@capacitor-mlkit/barcode-scanning';
import { parseItemId } from './parser';
export async function scanBarcode(): Promise<string | null> {
if (Capacitor.isNativePlatform()) {
// Native: use ML Kit
const { barcodes } = await BarcodeScanner.scan({
formats: [BarcodeFormat.QrCode, BarcodeFormat.Code128, BarcodeFormat.DataMatrix],
});
for (const barcode of barcodes) {
const id = parseItemId(barcode.rawValue);
if (id) return id;
}
return null;
} else {
// Web: use Barcode Detection API
return scanWithWebAPI();
}
}
async function scanWithWebAPI(): Promise<string | null> {
if (!('BarcodeDetector' in window)) {
// Load ZXing fallback
// Use html5-qrcode library
return null;
}
const detector = new BarcodeDetector({
formats: ['qr_code', 'code_128', 'data_matrix'],
});
// Get camera stream
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' }
});
// Create video element, detect from frames
// ... (continuous detection loop at ~10fps)
return null;
}
```
### ID Parsing:
```typescript
// src/lib/scanning/parser.ts
const VALID_CHARS = /^[23456789a-hjkmnp-z]{7}$/;
export function parseItemId(rawValue: string): string | null {
// HTTPS URI
const httpsMatch = rawValue.match(/haus\.toph\.so\/([23456789a-hjkmnp-z]{7})$/);
if (httpsMatch) return httpsMatch[1];
// Custom scheme
const schemeMatch = rawValue.match(/^haus:\/\/([23456789a-hjkmnp-z]{7})$/);
if (schemeMatch) return schemeMatch[1];
// Raw ID
if (VALID_CHARS.test(rawValue)) return rawValue;
return null;
}
```
---
## Task 4: Item Management
### ItemForm Component (`src/lib/components/ItemForm.svelte`)
Fields: name, description, category, brand, serialNumber, color, purchaseDate, itemType, location, storageTier.
For consumable/perishable types, show additional fields: currentQuantity, originalQuantity, quantityUnit, lowThreshold, expiryDate.
Auto-generate shortId on create (or pre-fill if associating a pre-printed label).
### ItemDetail Component (`src/lib/components/ItemCard.svelte`)
Show: name, category, custody badge, location, confidence badge, storage tier, sighting history. For consumables: quantity bar with percentage.
Actions: Check In/Out (contextual), Edit, Print Label, View History.
### ItemList Component (`src/routes/items/+page.svelte`)
Filterable list: search, category filter, custody state filter, type filter. Sort by name, last seen, custody state.
---
## Task 5: Location Management
### LocationTree Component (`src/lib/components/LocationTree.svelte`)
Collapsible tree view using `parentId` relationships. Show item count per location. Tap to browse items in that location.
### LocationPicker Component (`src/lib/components/LocationPicker.svelte`)
Used in forms and scan flows. Shows tree with radio selection. Quick-access "recent locations" at top.
### Default Locations:
Pre-populate on first run:
```typescript
const DEFAULT_LOCATIONS = [
{ id: 'home', name: 'Home', parentId: null, type: 'house' },
{ id: 'eg', name: 'Erdgeschoss', parentId: 'home', type: 'floor' },
{ id: 'og', name: 'Obergeschoss', parentId: 'home', type: 'floor' },
{ id: 'keller', name: 'Keller', parentId: 'home', type: 'floor' },
{ id: 'kueche', name: 'Küche', parentId: 'eg', type: 'room' },
{ id: 'wohnzimmer', name: 'Wohnzimmer', parentId: 'eg', type: 'room' },
{ id: 'flur', name: 'Flur', parentId: 'eg', type: 'room' },
{ id: 'schlafzimmer', name: 'Schlafzimmer', parentId: 'og', type: 'room' },
{ id: 'buero', name: 'Büro', parentId: 'og', type: 'room' },
{ id: 'bad', name: 'Bad', parentId: 'og', type: 'room' },
{ id: 'werkstatt', name: 'Werkstatt', parentId: 'keller', type: 'room' },
];
```
---
## Task 6: Label Sheet Generation
### Label Sheet PDF (`src/lib/printing/labelSheet.ts`)
Generate A4 PDF of QR code stickers:
```typescript
import bwipjs from 'bwip-js';
import jsPDF from 'jspdf';
export async function generateLabelSheetPDF(ids: string[]): Promise<Blob> {
const doc = new jsPDF('p', 'mm', 'a4');
const COLS = 5;
const ROWS = 10;
const CELL_W = 38; // mm
const CELL_H = 27; // mm
const MARGIN_X = 8;
const MARGIN_Y = 10;
const QR_SIZE = 18; // mm
for (let i = 0; i < ids.length; i++) {
const col = i % COLS;
const row = Math.floor(i / COLS) % ROWS;
const page = Math.floor(i / (COLS * ROWS));
if (i > 0 && i % (COLS * ROWS) === 0) {
doc.addPage();
}
const x = MARGIN_X + col * CELL_W;
const y = MARGIN_Y + row * CELL_H;
// Generate QR code as PNG
const canvas = document.createElement('canvas');
bwipjs.toCanvas(canvas, {
bcid: 'qrcode',
text: `https://haus.toph.so/${ids[i]}`,
scale: 3,
includetext: false,
});
const qrDataUrl = canvas.toDataURL('image/png');
doc.addImage(qrDataUrl, 'PNG', x + 1, y + 1, QR_SIZE, QR_SIZE);
// ID text
doc.setFontSize(8);
doc.setFont('courier', 'normal');
doc.text(ids[i], x + QR_SIZE + 2, y + QR_SIZE / 2 + 1);
}
return doc.output('blob');
}
```
### Print Server Client (`src/lib/printing/printServer.ts`)
```typescript
export interface PrintServerConfig {
baseUrl: string; // e.g., "http://printer.local:3030"
}
export async function printLabels(
config: PrintServerConfig,
labels: { id: string; uri: string }[],
format: 'sheet' | 'strip' = 'sheet',
): Promise<void> {
const response = await fetch(`${config.baseUrl}/api/print`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ labels, format }),
});
if (!response.ok) {
throw new Error(`Print failed: ${response.statusText}`);
}
}
```
---
## Task 7: App Shell & Navigation
### Layout (`src/routes/+layout.svelte`)
```svelte
<script lang="ts">
import BottomNav from '$lib/components/BottomNav.svelte';
import { page } from '$app/stores';
let { children } = $props();
</script>
<div class="flex flex-col h-screen bg-slate-900 text-white">
<main class="flex-1 overflow-y-auto pb-16">
{@render children()}
</main>
<BottomNav currentPath={$page.url.pathname} />
</div>
```
### BottomNav (`src/lib/components/BottomNav.svelte`)
```svelte
<script lang="ts">
let { currentPath = '/' } = $props();
const tabs = [
{ path: '/scan', icon: '📷', label: 'Scan' },
{ path: '/items', icon: '📦', label: 'Items' },
{ path: '/locations', icon: '📍', label: 'Places' },
{ path: '/labels', icon: '🏷️', label: 'Labels' },
{ path: '/settings', icon: '⋯', label: 'More' },
];
</script>
<nav class="fixed bottom-0 left-0 right-0 bg-slate-800 border-t border-slate-700 flex justify-around py-2 z-50">
{#each tabs as tab}
<a
href={tab.path}
class="flex flex-col items-center px-3 py-1 text-xs
{currentPath.startsWith(tab.path) ? 'text-blue-400' : 'text-slate-400'}"
>
<span class="text-xl">{tab.icon}</span>
<span>{tab.label}</span>
</a>
{/each}
</nav>
```
---
## Task 8: State Management (Svelte 5 Runes)
Use Svelte 5 runes for reactive state. Create stores in `src/lib/stores/`:
```typescript
// src/lib/stores/inventory.svelte.ts
import { getDB } from '$lib/data/db';
class InventoryStore {
items = $state<Item[]>([]);
locations = $state<Location[]>([]);
loading = $state(true);
checkedOutItems = $derived(
this.items.filter(i => i.custodyState === 'checked-out')
);
overdueItems = $derived(
this.items.filter(i =>
i.custodyState === 'checked-out' &&
i.checkedOutSince &&
Date.now() - new Date(i.checkedOutSince).getTime() > 7 * 86400000
)
);
lowStockItems = $derived(
this.items.filter(i =>
i.currentQuantity != null &&
i.lowThreshold != null &&
i.currentQuantity <= i.lowThreshold
)
);
async loadAll() {
const db = await getDB();
this.items = await db.getAll('items');
this.locations = await db.getAll('locations');
this.loading = false;
}
async createItem(item: Item) {
const db = await getDB();
await db.put('items', item);
this.items = [...this.items, item];
}
async updateItem(shortId: string, updates: Partial<Item>) {
const db = await getDB();
const existing = await db.get('items', shortId);
if (!existing) return;
const updated = { ...existing, ...updates, updatedAt: new Date().toISOString() };
await db.put('items', updated);
this.items = this.items.map(i => i.shortId === shortId ? updated : i);
}
}
export const inventory = new InventoryStore();
```
---
## Design Guidelines
### Color Palette (Dark theme, slate-based):
- `bg-slate-900` — main background
- `bg-slate-800` — cards, surfaces
- `bg-slate-700` — hover, borders
- `text-blue-400` — primary actions
- `text-emerald-400` — confirmed, checked in
- `text-amber-400` — likely, warning
- `text-red-400` — unknown, danger
### Confidence Badges:
- Confirmed → `bg-emerald-500/20 text-emerald-400` + ✓
- Likely → `bg-amber-500/20 text-amber-400` + ◷
- Assumed → `bg-slate-500/20 text-slate-400` + ?
- Unknown → `bg-red-500/20 text-red-400` + !
### Mobile Considerations:
- Touch targets: min 44×44px
- Bottom sheet modals for scan results
- Haptic feedback on scan
- Skeleton loading states
- Monospace font for IDs: `font-mono text-lg tracking-wider`
---
## Testing Approach
### Unit Tests:
- ID generation (uniqueness, format validation)
- ID parsing (HTTPS, haus://, raw)
- Confidence decay logic
- Quantity formatting
### Integration Tests:
- IndexedDB CRUD operations
- Check-in/check-out state transitions
- Pre-generated ID → item association
### E2E Tests (Playwright):
- Create item flow
- Scan → check out → check in cycle
- Generate label sheet
- Search and filter
---
## Non-Functional Requirements
- First paint < 1s on mobile
- Scanner frame processing ≥ 10fps
- IndexedDB operations < 50ms
- Full offline capability (all features work without network)
- Multi-device sync via Automerge (eventual consistency)
- Accessible (ARIA labels, screen reader support)
---
## Key Algorithms
### Confidence Decay
```typescript
export function getConfidence(lastSeenTimestamp: string | null): ConfidenceLevel {
if (!lastSeenTimestamp) return 'unknown';
const daysSince = differenceInDays(new Date(), new Date(lastSeenTimestamp));
if (daysSince <= 30) return 'confirmed';
if (daysSince <= 90) return 'likely';
if (daysSince <= 180) return 'assumed';
return 'unknown';
}
```
### Sighting Processing
```typescript
async function processSighting(itemId: string, locationId: string, type: SightingMethod) {
const now = new Date().toISOString();
await createSighting({ id: generateSightingId(), itemId, locationId, timestamp: now, sightingType: type, confidence: 'confirmed', notes: '' });
await updateItem(itemId, { lastSeenAt: locationId, lastSeenTimestamp: now, locationConfidence: 'confirmed', updatedAt: now });
}
```
### Check-Out Processing
```typescript
async function processCheckOut(itemId: string, reason: CheckOutReason, fromLocationId: string, toLocationId: string | null, note = '') {
const now = new Date().toISOString();
await updateItem(itemId, {
custodyState: 'checked-out',
checkedOutSince: now,
checkedOutReason: reason,
checkedOutFrom: fromLocationId,
checkedOutTo: toLocationId,
checkedOutNote: note,
lastSeenAt: toLocationId ?? fromLocationId,
lastSeenTimestamp: now,
locationConfidence: 'confirmed',
});
}
```
### Check-In Processing
```typescript
async function processCheckIn(itemId: string, returnToLocationId: string) {
const now = new Date().toISOString();
await updateItem(itemId, {
custodyState: 'checked-in',
checkedOutSince: null,
checkedOutReason: null,
checkedOutFrom: null,
checkedOutTo: null,
checkedOutNote: null,
lastSeenAt: returnToLocationId,
lastSeenTimestamp: now,
locationConfidence: 'confirmed',
supposedToBeAt: returnToLocationId,
});
}
```
### Dashboard Queries
```typescript
function getCheckedOutItems(): Item[] {
return allItems.filter(i => i.custodyState === 'checked-out');
}
function getOverdueItems(maxDays = 7): Item[] {
const cutoff = subDays(new Date(), maxDays);
return allItems.filter(i => i.custodyState === 'checked-out' && i.checkedOutSince && new Date(i.checkedOutSince) < cutoff);
}
function getLentItems(): Item[] {
return allItems.filter(i => i.custodyState === 'checked-out' && i.checkedOutReason === 'lent');
}
function getItemsInContainer(containerId: string): Item[] {
return allItems.filter(i => i.storageContainerId === containerId);
}
```

616
PROJECT_SPECIFICATION.md Normal file
View file

@ -0,0 +1,616 @@
# 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

221
RESEARCH_SUMMARY.md Normal file
View file

@ -0,0 +1,221 @@
# SolidHaus — Technology Research Summary
**Date:** 2026-02-26
---
## 1. Solid Ecosystem (2026 Status)
### Libraries
| Library | Description | Status |
|---------|-------------|--------|
| `@inrupt/solid-client` | CRUD operations on Solid Pods | Stable v2.x |
| `@inrupt/solid-client-authn-browser` | Browser-based Solid authentication | Stable v2.x |
| `@inrupt/vocab-common-rdf` | Common RDF vocabulary constants | Stable |
| `rdflib.js` | Full RDF library (parsing, serialization, stores) | Stable, heavier |
### Solid Servers
- **Community Solid Server (CSS)**: Recommended for self-hosting. Written in TypeScript/Node.js, highly modular, active development.
- NSS (Node Solid Server): Older, being deprecated in favor of CSS.
### Key Findings
- Inrupt SDK is well-documented and suitable for read/write operations
- Solid Notifications (live updates) is still maturing — not suitable as primary sync mechanism
- For multi-user, use CRDT layer (Automerge) for real-time sync, Solid for long-term RDF archive
---
## 2. CRDT Libraries Comparison
| Library | Language | Data Types | Status | Notes |
|---------|----------|-----------|--------|-------|
| **Automerge** | Rust+JS | JSON documents, counters, text | Production | Best for structured data like inventory items |
| **Yjs** | JS | Map, Array, Text, XML | Production | Best for rich text, very performant |
| **Loro** | Rust+JS | Rich text, List, Map, Tree, Counter | Newer | Very promising, integrated tree CRDT |
| **SyncedStore** | JS | Yjs wrapper, plain JS objects | Beta | Easy integration with Svelte/Vue |
### Recommendation: Automerge
Automerge is the best fit for SolidHaus because:
- JSON document model maps naturally to inventory items
- `automerge-repo` provides ready-made sync infrastructure (WebSocket, IndexedDB storage)
- Conflict resolution is automatic and deterministic
- Offline-first by design — changes merge when reconnected
- Well-maintained, production-ready
### automerge-repo
The `automerge-repo` package provides:
- Document management (create, find, change, delete)
- Network adapters: WebSocket, BroadcastChannel, MessageChannel
- Storage adapters: IndexedDB, filesystem
- Sync protocol handling
```typescript
import { Repo } from '@automerge/automerge-repo';
import { IndexedDBStorageAdapter } from '@automerge/automerge-repo-storage-indexeddb';
import { BrowserWebSocketClientAdapter } from '@automerge/automerge-repo-network-websocket';
const repo = new Repo({
storage: new IndexedDBStorageAdapter(),
network: [new BrowserWebSocketClientAdapter('wss://sync.toph.so')],
});
```
---
## 3. Barcode Scanning Options
### Native (Capacitor)
| Plugin | Backend | Formats | Notes |
|--------|---------|---------|-------|
| **@capacitor-mlkit/barcode-scanning** | Google ML Kit | All 1D/2D formats | Recommended. Fast, reliable. Android + iOS. |
| **@capacitor/barcode-scanner** | OutSystems libs | All 1D/2D formats | Official Capacitor plugin. Also uses ML Kit/Vision. |
Both support: QR Code, DataMatrix, Code 128, Code 39, EAN-13, UPC-A, Aztec, PDF417, and more.
### Web
| Library | Size | Speed | Formats | Notes |
|---------|------|-------|---------|-------|
| **Barcode Detection API** | 0 | Very fast | QR, Code128, DataMatrix, EAN, UPC | Chrome Android/Desktop only. No Safari. |
| **html5-qrcode** | ~150KB | Good | QR, Code128, others | Standalone, no framework dependency |
| **@nicbright/web-barcode-reader** | ~200KB | Good | Many | Another ZXing wrapper |
### Recommendation
**Primary**: `@capacitor-mlkit/barcode-scanning` — covers Android and iOS natively.
**Web fallback**: Barcode Detection API (Chrome) → html5-qrcode (ZXing WASM) for other browsers.
Platform matrix:
```
Capacitor (Android) → ML Kit native ✅ All formats
Capacitor (iOS) → ML Kit native ✅ All formats
Web (Chrome) → Barcode Det. API ✅ All formats
Web (Safari) → html5-qrcode ✅ QR + common formats
Web (Firefox) → html5-qrcode ✅ QR + common formats
```
---
## 4. SvelteKit + Capacitor (Mobile Strategy)
### Why SvelteKit + Capacitor
- **SvelteKit** provides file-based routing, SSR/SPA flexibility, excellent DX
- **Capacitor** wraps the SvelteKit SPA in native WebViews with full native API access
- Together: single codebase → web, Android, iOS
- Well-documented integration path (official SvelteKit docs, Capacitor docs)
### Setup Pattern
```
SvelteKit (adapter-static, SPA mode)
→ Vite build → /build/
→ Capacitor sync → Android/iOS native projects
→ Native APIs: Camera, Barcode Scanner, Haptics, Deep Links
```
Key config: `ssr = false`, `prerender = false`, `fallback: 'index.html'`
### Native Features Used
| Feature | Capacitor Plugin |
|---------|-----------------|
| Barcode scanning | @capacitor-mlkit/barcode-scanning |
| Camera (photos) | @capacitor/camera |
| Haptics | @capacitor/haptics |
| Deep links | @capacitor/app (App.addListener) |
| File system | @capacitor/filesystem |
| Status bar | @capacitor/status-bar |
### Alternatives Considered
| Option | Verdict |
|--------|---------|
| **NativeScript + Svelte** | Stuck on Svelte 4, not maintained for Svelte 5 |
| **Lynx (ByteDance)** | No Svelte support yet; only works with one framework currently |
| **Tauri 2 Mobile** | Supports SvelteKit, but more bleeding-edge than Capacitor for mobile |
| **PWA only** | Works on Android, limited on iOS (no custom URI schemes, limited deep links) |
### Desktop: Tauri 2 (Future)
Tauri 2 has official SvelteKit support and can build for Windows, Linux, macOS. Can share the same SvelteKit SPA codebase. Good option for a desktop companion app later.
---
## 5. URI Scheme & Deep Linking
### QR Code Content
Use HTTPS URLs as QR content: `https://haus.toph.so/{id}`
This works universally:
- Native app installed → App Links/Universal Links intercept → opens app
- PWA installed (Android) → Chrome intercepts → opens PWA
- Nothing installed → browser → landing page
### Custom Scheme (`haus://`)
Register `haus://` in Capacitor native app for direct launches. Works when native app is installed, but custom schemes have no fallback.
### Deep Link Handling in Capacitor
```typescript
import { App } from '@capacitor/app';
App.addListener('appUrlOpen', (event) => {
const itemId = parseItemId(event.url);
if (itemId) {
goto(`/items/${itemId}`);
}
});
```
### PWA Limitation
PWAs cannot register custom URI schemes. They can only handle HTTPS URLs within their scope on Android. iOS doesn't support PWA deep linking at all (as of early 2026). This is why Capacitor native is recommended.
---
## 6. NextGraph — CRDT-Native RDF (Future)
NextGraph is a research project that merges CRDT, RDF, and Solid concepts:
- **CRDT + RDF**: RDF graphs as CRDTs (OR-set / SU-set)
- **Integrated engines**: Embeds Automerge and Yjs
- **Triple store**: Uses Oxigraph internally
- **Solid-compatible**: Can expose data as Solid Pods
- **Features**: E2EE, P2P sync, SPARQL queries, offline-first
### Status (2026)
- Alpha stage, funded by NLnet/NGI (EU)
- Not production-ready
- Most promising option for a unified CRDT+RDF+Solid stack
### When to Adopt
If NextGraph reaches beta/stable:
- Could replace Automerge + Solid Pod with a single layer
- RDF would be natively CRDT-enabled (no separate sync + export)
- Monitor progress: https://nextgraph.org/
---
## 7. Additional Notes
1. **Svelte 5 runes** (`$state`, `$derived`, `$effect`) provide built-in reactive state management. No external state library needed — the reactive system is part of the language.
2. **Capacitor Haptics**: Use `Haptics.impact({ style: ImpactStyle.Medium })` on successful barcode scan for tactile feedback.
3. **IndexedDB via `idb`**: The `idb` library provides a clean Promise-based wrapper. Works well for offline storage. Automerge can use `automerge-repo-storage-indexeddb` alongside for CRDT doc persistence.
4. **Label PDF generation**: `jsPDF` + `bwip-js` (canvas-based QR rendering) → downloadable A4 PDF. Works entirely client-side, no server needed for label sheet generation.
5. **EAN/UPC product barcodes**: The scanner can recognize existing product barcodes. Future feature: look up product info from Open Food Facts or similar open databases to pre-fill item details.

683
bun.lock Normal file
View file

@ -0,0 +1,683 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "solidhaus-scaffold",
"dependencies": {
"@automerge/automerge": "^3.2.4",
"@automerge/automerge-repo": "^2.5.3",
"@automerge/automerge-repo-network-websocket": "^2.5.3",
"@automerge/automerge-repo-storage-indexeddb": "^2.5.3",
"@capacitor-mlkit/barcode-scanning": "^8.0.1",
"@capacitor/cli": "^8.1.0",
"@capacitor/core": "^8.1.0",
"@inrupt/solid-client": "^3.0.0",
"@inrupt/solid-client-authn-browser": "^3.1.1",
"@inrupt/vocab-common-rdf": "^1.0.5",
"bwip-js": "^4.8.0",
"date-fns": "^4.1.0",
"idb": "^8.0.3",
"jspdf": "^4.2.0",
"nanoid": "^5.1.6",
},
"devDependencies": {
"@sveltejs/adapter-auto": "^7.0.0",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.50.2",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tailwindcss/vite": "^4.2.1",
"svelte": "^5.51.0",
"svelte-check": "^4.3.6",
"tailwindcss": "^4.2.1",
"typescript": "^5.9.3",
"vite": "^7.3.1",
},
},
},
"packages": {
"@automerge/automerge": ["@automerge/automerge@3.2.4", "", {}, "sha512-/IAShHSxme5d4ZK0Vs4A0P+tGaR/bSz6KtIJSCIPfwilCxsIqfRHAoNjmsXip6TGouadmbuw3WfLop6cal8pPQ=="],
"@automerge/automerge-repo": ["@automerge/automerge-repo@2.5.3", "", { "dependencies": { "@automerge/automerge": "2.2.8 - 3", "bs58check": "^3.0.1", "cbor-x": "^1.3.0", "debug": "^4.3.4", "eventemitter3": "^5.0.1", "fast-sha256": "^1.3.0", "uuid": "^9.0.0", "xstate": "^5.9.1" } }, "sha512-5ppzsUlzCs6PY+GeFlquaYLnVnYPz/hWzedep37zjb15tqZju1ukPPkBzT7KGEhEAnA99l4vfhfqSVC+1GktJg=="],
"@automerge/automerge-repo-network-websocket": ["@automerge/automerge-repo-network-websocket@2.5.3", "", { "dependencies": { "@automerge/automerge-repo": "2.5.3", "cbor-x": "^1.3.0", "debug": "^4.3.4", "eventemitter3": "^5.0.1", "isomorphic-ws": "^5.0.0", "ws": "^8.7.0" } }, "sha512-p6K1YLo34cyGxJ6oMWqeBbWX9rGvEPGyPdX2eq8S/iWNDjYf8lbjKSOhOD8DX87uAMZN/y1XOY/RckwS8lPJbQ=="],
"@automerge/automerge-repo-storage-indexeddb": ["@automerge/automerge-repo-storage-indexeddb@2.5.3", "", { "dependencies": { "@automerge/automerge-repo": "2.5.3" } }, "sha512-i4tSzY7sC8ZqurHm4Sd9H1lFL2Don9v9yaSoq0FXi4T+4Wg8eus/mtMlUCmS5XwYQL8GX+epbplrlBaBe+fYiw=="],
"@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
"@bergos/jsonparse": ["@bergos/jsonparse@1.4.2", "", { "dependencies": { "buffer": "^6.0.3" } }, "sha512-qUt0QNJjvg4s1zk+AuLM6s/zcsQ8MvGn7+1f0vPuxvpCYa08YtTryuDInngbEyW5fNGGYe2znKt61RMGd5HnXg=="],
"@capacitor-mlkit/barcode-scanning": ["@capacitor-mlkit/barcode-scanning@8.0.1", "", { "peerDependencies": { "@capacitor/core": ">=8.0.0" } }, "sha512-94SyxzbcQ2HBd7yxS34dqc0PzwdOw8/98xBrjw1ioznE/Ksky/iwsip5Bm8P8Qg+dgnJYWz7NcIPjd+6bLjVoQ=="],
"@capacitor/cli": ["@capacitor/cli@8.1.0", "", { "dependencies": { "@ionic/cli-framework-output": "^2.2.8", "@ionic/utils-subprocess": "^3.0.1", "@ionic/utils-terminal": "^2.3.5", "commander": "^12.1.0", "debug": "^4.4.0", "env-paths": "^2.2.0", "fs-extra": "^11.2.0", "kleur": "^4.1.5", "native-run": "^2.0.3", "open": "^8.4.0", "plist": "^3.1.0", "prompts": "^2.4.2", "rimraf": "^6.0.1", "semver": "^7.6.3", "tar": "^7.5.3", "tslib": "^2.8.1", "xml2js": "^0.6.2" }, "bin": { "cap": "bin/capacitor", "capacitor": "bin/capacitor" } }, "sha512-JAzA/ckPgTCjZz6YumBLV2dNCFEVXAuR1oOKLD7AJ4LAI5pF5RtRZrf5FoaxvJVb0S4CouZT5cD+7NwsNJX/nw=="],
"@capacitor/core": ["@capacitor/core@8.1.0", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-UfMBMWc1v7J+14AhH03QmeNwV3HZx3qnOWhpwnHfzALEwAwlV/itQOQqcasMQYhOHWL0tiymc5ByaLTn7KKQxw=="],
"@cbor-extract/cbor-extract-darwin-arm64": ["@cbor-extract/cbor-extract-darwin-arm64@2.2.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w=="],
"@cbor-extract/cbor-extract-darwin-x64": ["@cbor-extract/cbor-extract-darwin-x64@2.2.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w=="],
"@cbor-extract/cbor-extract-linux-arm": ["@cbor-extract/cbor-extract-linux-arm@2.2.0", "", { "os": "linux", "cpu": "arm" }, "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q=="],
"@cbor-extract/cbor-extract-linux-arm64": ["@cbor-extract/cbor-extract-linux-arm64@2.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ=="],
"@cbor-extract/cbor-extract-linux-x64": ["@cbor-extract/cbor-extract-linux-x64@2.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw=="],
"@cbor-extract/cbor-extract-win32-x64": ["@cbor-extract/cbor-extract-win32-x64@2.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
"@inrupt/oidc-client": ["@inrupt/oidc-client@1.11.6", "", { "dependencies": { "acorn": "^7.4.1", "base64-js": "^1.5.1", "core-js": "^3.8.3", "crypto-js": "^4.0.0", "serialize-javascript": "^4.0.0" } }, "sha512-1rCTk1T6pdm/7gKozutZutk7jwmYBADlnkGGoI5ypke099NOCa5KFXjkQpbjsps0PRkKZ+0EaR70XN5+xqmViA=="],
"@inrupt/oidc-client-ext": ["@inrupt/oidc-client-ext@3.1.1", "", { "dependencies": { "@inrupt/oidc-client": "^1.11.6", "@inrupt/solid-client-authn-core": "^3.1.1", "jose": "^5.1.3", "uuid": "^11.1.0" } }, "sha512-vftKD2u5nufZTFkdUDMS3Uxj5xNQwArP11OFaALFkq6/3RwCAhe3lwOv8hNzL7Scv98T+KbAErBM0TwGGrS69g=="],
"@inrupt/solid-client": ["@inrupt/solid-client@3.0.0", "", { "dependencies": { "@inrupt/solid-client-errors": "^0.0.2", "@rdfjs/dataset": "^1.1.1", "buffer": "^6.0.3", "http-link-header": "^1.1.1", "jsonld-context-parser": "^3.0.0", "jsonld-streaming-parser": "^5.0.0", "n3": "^1.17.2", "uuid": "^11.0.1" }, "optionalDependencies": { "fsevents": "^2.3.3" } }, "sha512-QQCLakUeQldTAFewZZv7qlIotAlXnN1ZigRphr6SNKck2L9NAESWihba9i+9/AJLMnAPStx9XS+WuJswzlmhVA=="],
"@inrupt/solid-client-authn-browser": ["@inrupt/solid-client-authn-browser@3.1.1", "", { "dependencies": { "@inrupt/oidc-client-ext": "^3.1.1", "@inrupt/solid-client-authn-core": "^3.1.1", "events": "^3.3.0", "jose": "^5.1.3", "uuid": "^11.1.0" } }, "sha512-Wd7TREmvdhTp+Sk88ei3hlg54sG1fNqkkPkuS+2tDBkcsXaViRQAEugVyh5pWRkd1xSFKrEzftb7UYEG4mJ0CQ=="],
"@inrupt/solid-client-authn-core": ["@inrupt/solid-client-authn-core@3.1.1", "", { "dependencies": { "events": "^3.3.0", "jose": "^5.1.3", "uuid": "^11.1.0" } }, "sha512-1oDSQCh/pVtPlTyvLQ2uwHo+hpLJF7izg82tjB+Ge8jqGYwkQyId0BrfncpCk//uJXxgRIcfAQp2MhXYbZo80Q=="],
"@inrupt/solid-client-errors": ["@inrupt/solid-client-errors@0.0.2", "", {}, "sha512-Nhq39DJMKDMc35/VFT168v9JwuKzfzCHPN4fYYAE/Q0ECtM6PuBGT7nu0gZ06+S0pZQasHDyTkOGXRIx+zkvJA=="],
"@inrupt/vocab-common-rdf": ["@inrupt/vocab-common-rdf@1.0.5", "", {}, "sha512-onrehQte8m0XW83WwM6T4o5WgmYGzdYUCef6FDjMKfQCF64FnARFNn5fYhKodimBaAOFKhuJ3vw1NBZV/EYqJw=="],
"@ionic/cli-framework-output": ["@ionic/cli-framework-output@2.2.8", "", { "dependencies": { "@ionic/utils-terminal": "2.3.5", "debug": "^4.0.0", "tslib": "^2.0.1" } }, "sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g=="],
"@ionic/utils-array": ["@ionic/utils-array@2.1.6", "", { "dependencies": { "debug": "^4.0.0", "tslib": "^2.0.1" } }, "sha512-0JZ1Zkp3wURnv8oq6Qt7fMPo5MpjbLoUoa9Bu2Q4PJuSDWM8H8gwF3dQO7VTeUj3/0o1IB1wGkFWZZYgUXZMUg=="],
"@ionic/utils-fs": ["@ionic/utils-fs@3.1.7", "", { "dependencies": { "@types/fs-extra": "^8.0.0", "debug": "^4.0.0", "fs-extra": "^9.0.0", "tslib": "^2.0.1" } }, "sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA=="],
"@ionic/utils-object": ["@ionic/utils-object@2.1.6", "", { "dependencies": { "debug": "^4.0.0", "tslib": "^2.0.1" } }, "sha512-vCl7sl6JjBHFw99CuAqHljYJpcE88YaH2ZW4ELiC/Zwxl5tiwn4kbdP/gxi2OT3MQb1vOtgAmSNRtusvgxI8ww=="],
"@ionic/utils-process": ["@ionic/utils-process@2.1.12", "", { "dependencies": { "@ionic/utils-object": "2.1.6", "@ionic/utils-terminal": "2.3.5", "debug": "^4.0.0", "signal-exit": "^3.0.3", "tree-kill": "^1.2.2", "tslib": "^2.0.1" } }, "sha512-Jqkgyq7zBs/v/J3YvKtQQiIcxfJyplPgECMWgdO0E1fKrrH8EF0QGHNJ9mJCn6PYe2UtHNS8JJf5G21e09DfYg=="],
"@ionic/utils-stream": ["@ionic/utils-stream@3.1.7", "", { "dependencies": { "debug": "^4.0.0", "tslib": "^2.0.1" } }, "sha512-eSELBE7NWNFIHTbTC2jiMvh1ABKGIpGdUIvARsNPMNQhxJB3wpwdiVnoBoTYp+5a6UUIww4Kpg7v6S7iTctH1w=="],
"@ionic/utils-subprocess": ["@ionic/utils-subprocess@3.0.1", "", { "dependencies": { "@ionic/utils-array": "2.1.6", "@ionic/utils-fs": "3.1.7", "@ionic/utils-process": "2.1.12", "@ionic/utils-stream": "3.1.7", "@ionic/utils-terminal": "2.3.5", "cross-spawn": "^7.0.3", "debug": "^4.0.0", "tslib": "^2.0.1" } }, "sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A=="],
"@ionic/utils-terminal": ["@ionic/utils-terminal@2.3.5", "", { "dependencies": { "@types/slice-ansi": "^4.0.0", "debug": "^4.0.0", "signal-exit": "^3.0.3", "slice-ansi": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0", "tslib": "^2.0.1", "untildify": "^4.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A=="],
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
"@rdfjs/data-model": ["@rdfjs/data-model@1.3.4", "", { "dependencies": { "@rdfjs/types": ">=1.0.1" }, "bin": { "rdfjs-data-model-test": "bin/test.js" } }, "sha512-iKzNcKvJotgbFDdti7GTQDCYmL7GsGldkYStiP0K8EYtN7deJu5t7U11rKTz+nR7RtesUggT+lriZ7BakFv8QQ=="],
"@rdfjs/dataset": ["@rdfjs/dataset@1.1.1", "", { "dependencies": { "@rdfjs/data-model": "^1.2.0" }, "bin": { "rdfjs-dataset-test": "bin/test.js" } }, "sha512-BNwCSvG0cz0srsG5esq6CQKJc1m8g/M0DZpLuiEp0MMpfwguXX7VeS8TCg4UUG3DV/DqEvhy83ZKSEjdsYseeA=="],
"@rdfjs/types": ["@rdfjs/types@2.0.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-uyAzpugX7KekAXAHq26m3JlUIZJOC0uSBhpnefGV5i15bevDyyejoB7I+9MKeUrzXD8OOUI3+4FeV1wwQr5ihA=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.9", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA=="],
"@sveltejs/adapter-auto": ["@sveltejs/adapter-auto@7.0.1", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-dvuPm1E7M9NI/+canIQ6KKQDU2AkEefEZ2Dp7cY6uKoPq9Z/PhOXABe526UdW2mN986gjVkuSLkOYIBnS/M2LQ=="],
"@sveltejs/adapter-static": ["@sveltejs/adapter-static@3.0.10", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew=="],
"@sveltejs/kit": ["@sveltejs/kit@2.53.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.6.3", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "set-cookie-parser": "^3.0.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": "^5.3.3", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" }, "optionalPeers": ["@opentelemetry/api", "typescript"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-M+MqAvFve12T1HWws/2npP/s3hFtyjw3GB/OXW/8a1jZBk48qnvPJrtgE+VOMc3RnjUMxc4mv/vQ73nvj2uNMg=="],
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.4", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", "magic-string": "^0.30.21", "obug": "^2.1.0", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA=="],
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.2", "", { "dependencies": { "obug": "^2.1.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig=="],
"@tailwindcss/node": ["@tailwindcss/node@4.2.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.1" } }, "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.1", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.1", "@tailwindcss/oxide-darwin-arm64": "4.2.1", "@tailwindcss/oxide-darwin-x64": "4.2.1", "@tailwindcss/oxide-freebsd-x64": "4.2.1", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", "@tailwindcss/oxide-linux-x64-musl": "4.2.1", "@tailwindcss/oxide-wasm32-wasi": "4.2.1", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" } }, "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.1", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ=="],
"@tailwindcss/vite": ["@tailwindcss/vite@4.2.1", "", { "dependencies": { "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "tailwindcss": "4.2.1" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w=="],
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/fs-extra": ["@types/fs-extra@8.1.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ=="],
"@types/http-link-header": ["@types/http-link-header@1.0.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-snm5oLckop0K3cTDAiBnZDy6ncx9DJ3mCRDvs42C884MbVYPP74Tiq2hFsSDRTyjK6RyDYDIulPiW23ge+g5Lw=="],
"@types/node": ["@types/node@25.3.1", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-hj9YIJimBCipHVfHKRMnvmHg+wfhKc0o4mTtXh9pKBjC8TLJzz0nzGmLi5UJsYAUgSvXFHgb0V2oY10DUFtImw=="],
"@types/pako": ["@types/pako@2.0.4", "", {}, "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw=="],
"@types/raf": ["@types/raf@3.4.3", "", {}, "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw=="],
"@types/readable-stream": ["@types/readable-stream@4.0.23", "", { "dependencies": { "@types/node": "*" } }, "sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig=="],
"@types/slice-ansi": ["@types/slice-ansi@4.0.0", "", {}, "sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ=="],
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
"@xmldom/xmldom": ["@xmldom/xmldom@0.8.11", "", {}, "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw=="],
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"aria-query": ["aria-query@5.3.1", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="],
"astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="],
"at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"base-x": ["base-x@4.0.1", "", {}, "sha512-uAZ8x6r6S3aUM9rbHGVOIsR15U/ZSc82b3ymnCPsT45Gk1DDvhDPdIgB5MrhirZWt+5K0EEPQH985kNqZgNPFw=="],
"base64-arraybuffer": ["base64-arraybuffer@1.0.2", "", {}, "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="],
"bplist-parser": ["bplist-parser@0.3.2", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ=="],
"brace-expansion": ["brace-expansion@5.0.3", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA=="],
"bs58": ["bs58@5.0.0", "", { "dependencies": { "base-x": "^4.0.0" } }, "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ=="],
"bs58check": ["bs58check@3.0.1", "", { "dependencies": { "@noble/hashes": "^1.2.0", "bs58": "^5.0.0" } }, "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ=="],
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
"bwip-js": ["bwip-js@4.8.0", "", { "bin": { "bwip-js": "bin/bwip-js.js" } }, "sha512-gUDkDHSTv8/DJhomSIbO0fX/Dx0MO/sgllLxJyJfu4WixCQe9nfGJzmHm64ZCbxo+gUYQEsQcRmqcwcwPRwUkg=="],
"canonicalize": ["canonicalize@1.0.8", "", {}, "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A=="],
"canvg": ["canvg@3.0.11", "", { "dependencies": { "@babel/runtime": "^7.12.5", "@types/raf": "^3.4.0", "core-js": "^3.8.3", "raf": "^3.4.1", "regenerator-runtime": "^0.13.7", "rgbcolor": "^1.0.1", "stackblur-canvas": "^2.0.0", "svg-pathdata": "^6.0.3" } }, "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA=="],
"cbor-extract": ["cbor-extract@2.2.0", "", { "dependencies": { "node-gyp-build-optional-packages": "5.1.1" }, "optionalDependencies": { "@cbor-extract/cbor-extract-darwin-arm64": "2.2.0", "@cbor-extract/cbor-extract-darwin-x64": "2.2.0", "@cbor-extract/cbor-extract-linux-arm": "2.2.0", "@cbor-extract/cbor-extract-linux-arm64": "2.2.0", "@cbor-extract/cbor-extract-linux-x64": "2.2.0", "@cbor-extract/cbor-extract-win32-x64": "2.2.0" }, "bin": { "download-cbor-prebuilds": "bin/download-prebuilds.js" } }, "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA=="],
"cbor-x": ["cbor-x@1.6.0", "", { "optionalDependencies": { "cbor-extract": "^2.2.0" } }, "sha512-0kareyRwHSkL6ws5VXHEf8uY1liitysCVJjlmhaLG+IXLqhSaOO+t63coaso7yjwEzWZzLy8fJo06gZDVQM9Qg=="],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
"core-js": ["core-js@3.48.0", "", {}, "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"crypto-js": ["crypto-js@4.2.0", "", {}, "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="],
"css-line-break": ["css-line-break@2.1.0", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w=="],
"date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"devalue": ["devalue@5.6.3", "", {}, "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg=="],
"dompurify": ["dompurify@3.3.1", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q=="],
"elementtree": ["elementtree@0.1.7", "", { "dependencies": { "sax": "1.1.4" } }, "sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg=="],
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="],
"env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
"esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
"esrap": ["esrap@2.2.3", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ=="],
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
"events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
"fast-png": ["fast-png@6.4.0", "", { "dependencies": { "@types/pako": "^2.0.3", "iobuffer": "^5.3.2", "pako": "^2.1.0" } }, "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q=="],
"fast-sha256": ["fast-sha256@1.3.0", "", {}, "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="],
"fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
"fs-extra": ["fs-extra@11.3.3", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"html2canvas": ["html2canvas@1.4.1", "", { "dependencies": { "css-line-break": "^2.1.0", "text-segmentation": "^1.0.3" } }, "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA=="],
"http-link-header": ["http-link-header@1.1.3", "", {}, "sha512-3cZ0SRL8fb9MUlU3mKM61FcQvPfXx2dBrZW3Vbg5CXa8jFlK8OaEpePenLe1oEXQduhz8b0QjsqfS59QP4AJDQ=="],
"idb": ["idb@8.0.3", "", {}, "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg=="],
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ini": ["ini@4.1.3", "", {}, "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg=="],
"iobuffer": ["iobuffer@5.4.0", "", {}, "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA=="],
"is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
"is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
"jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="],
"jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
"jsonld-context-parser": ["jsonld-context-parser@3.1.0", "", { "dependencies": { "@types/http-link-header": "^1.0.1", "@types/node": "^18.0.0", "http-link-header": "^1.0.2", "relative-to-absolute-iri": "^1.0.5" }, "bin": { "jsonld-context-parse": "bin/jsonld-context-parse.js" } }, "sha512-BfgNJ/t9jjK7Lun9XRCJM6YeNqDk8B6/lg+KS8rzhXAOtS0FvoClSmtLvF24V1M2CDYRy2LcEBt0ilxqSX93WA=="],
"jsonld-streaming-parser": ["jsonld-streaming-parser@5.0.1", "", { "dependencies": { "@bergos/jsonparse": "^1.4.0", "@types/http-link-header": "^1.0.1", "@types/readable-stream": "^4.0.0", "buffer": "^6.0.3", "canonicalize": "^1.0.1", "http-link-header": "^1.0.2", "jsonld-context-parser": "^3.1.0", "rdf-data-factory": "^2.0.0", "readable-stream": "^4.0.0" } }, "sha512-Rf230DRAWe5p1H4e7phk1vo4FHEMOmC5xVcIywKJBBcwy6zaJWFcAvPcwngufNTdJs7dpTMbKQDjp4TYDpMKUQ=="],
"jspdf": ["jspdf@4.2.0", "", { "dependencies": { "@babel/runtime": "^7.28.6", "fast-png": "^6.2.0", "fflate": "^0.8.1" }, "optionalDependencies": { "canvg": "^3.0.11", "core-js": "^3.6.0", "dompurify": "^3.3.1", "html2canvas": "^1.0.0-rc.5" } }, "sha512-hR/hnRevAXXlrjeqU5oahOE+Ln9ORJUB5brLHHqH67A+RBQZuFr5GkbI9XQI8OUFSEezKegsi45QRpc4bGj75Q=="],
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
"lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="],
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="],
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
"lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
"minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
"minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="],
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"n3": ["n3@1.26.0", "", { "dependencies": { "buffer": "^6.0.3", "readable-stream": "^4.0.0" } }, "sha512-SQknS0ua90rN+3RHuk8BeIqeYyqIH/+ecViZxX08jR4j6MugqWRjtONl3uANG/crWXnOM2WIqBJtjIhVYFha+w=="],
"nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="],
"native-run": ["native-run@2.0.3", "", { "dependencies": { "@ionic/utils-fs": "^3.1.7", "@ionic/utils-terminal": "^2.3.4", "bplist-parser": "^0.3.2", "debug": "^4.3.4", "elementtree": "^0.1.7", "ini": "^4.1.1", "plist": "^3.1.0", "split2": "^4.2.0", "through2": "^4.0.2", "tslib": "^2.6.2", "yauzl": "^2.10.0" }, "bin": { "native-run": "bin/native-run" } }, "sha512-U1PllBuzW5d1gfan+88L+Hky2eZx+9gv3Pf6rNBxKbORxi7boHzqiA6QFGSnqMem4j0A9tZ08NMIs5+0m/VS1Q=="],
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.1.1", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-test": "build-test.js", "node-gyp-build-optional-packages-optional": "optional.js" } }, "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw=="],
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
"open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="],
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
"pako": ["pako@2.1.0", "", {}, "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
"performance-now": ["performance-now@2.1.0", "", {}, "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
"raf": ["raf@3.4.1", "", { "dependencies": { "performance-now": "^2.1.0" } }, "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA=="],
"randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="],
"rdf-data-factory": ["rdf-data-factory@2.0.2", "", { "dependencies": { "@rdfjs/types": "^2.0.0" } }, "sha512-WzPoYHwQYWvIP9k+7IBLY1b4nIDitzAK4mA37WumAF/Cjvu/KOtYJH9IPZnUTWNSd5K2+pq4vrcE9WZC4sRHhg=="],
"readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"regenerator-runtime": ["regenerator-runtime@0.13.11", "", {}, "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="],
"relative-to-absolute-iri": ["relative-to-absolute-iri@1.0.7", "", {}, "sha512-Xjyl4HmIzg2jzK/Un2gELqbcE8Fxy85A/aLSHE6PE/3+OGsFwmKVA1vRyGaz6vLWSqLDMHA+5rjD/xbibSQN1Q=="],
"rgbcolor": ["rgbcolor@1.0.1", "", {}, "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw=="],
"rimraf": ["rimraf@6.1.3", "", { "dependencies": { "glob": "^13.0.3", "package-json-from-dist": "^1.0.1" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA=="],
"rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="],
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"serialize-javascript": ["serialize-javascript@4.0.0", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw=="],
"set-cookie-parser": ["set-cookie-parser@3.0.1", "", {}, "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
"slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
"stackblur-canvas": ["stackblur-canvas@2.7.0", "", {}, "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ=="],
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"svelte": ["svelte@5.53.5", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.3", "esm-env": "^1.2.1", "esrap": "^2.2.2", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-YkqERnF05g8KLdDZwZrF8/i1eSbj6Eoat8Jjr2IfruZz9StLuBqo8sfCSzjosNKd+ZrQ8DkKZDjpO5y3ht1Pow=="],
"svelte-check": ["svelte-check@4.4.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-4HtdEv2hOoLCEsSXI+RDELk9okP/4sImWa7X02OjMFFOWeSdFF3NFy3vqpw0z+eH9C88J9vxZfUXz/Uv2A1ANw=="],
"svg-pathdata": ["svg-pathdata@6.0.3", "", {}, "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw=="],
"tailwindcss": ["tailwindcss@4.2.1", "", {}, "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw=="],
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
"tar": ["tar@7.5.9", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg=="],
"text-segmentation": ["text-segmentation@1.0.3", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw=="],
"through2": ["through2@4.0.2", "", { "dependencies": { "readable-stream": "3" } }, "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
"tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
"untildify": ["untildify@4.0.0", "", {}, "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"utrie": ["utrie@1.0.2", "", { "dependencies": { "base64-arraybuffer": "^1.0.2" } }, "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw=="],
"uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
"vitefu": ["vitefu@1.1.2", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
"xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="],
"xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
"xstate": ["xstate@5.28.0", "", {}, "sha512-Iaqq6ZrUzqeUtA3hC5LQKZfR8ZLzEFTImMHJM3jWEdVvXWdKvvVLXZEiNQWm3SCA9ZbEou/n5rcsna1wb9t28A=="],
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
"yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
"zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
"@inrupt/oidc-client/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="],
"@inrupt/oidc-client-ext/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
"@inrupt/solid-client/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
"@inrupt/solid-client-authn-browser/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
"@inrupt/solid-client-authn-core/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
"@ionic/utils-fs/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"elementtree/sax": ["sax@1.1.4", "", {}, "sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg=="],
"jsonld-context-parser/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
"postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
"through2/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
"jsonld-context-parser/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
}
}

12
capacitor.config.ts Normal file
View file

@ -0,0 +1,12 @@
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'so.toph.solidhaus',
appName: 'SolidHaus',
webDir: 'build',
server: {
androidScheme: 'https'
}
};
export default config;

43
package.json Normal file
View file

@ -0,0 +1,43 @@
{
"name": "solidhaus",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^7.0.0",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.50.2",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tailwindcss/vite": "^4.2.1",
"svelte": "^5.51.0",
"svelte-check": "^4.3.6",
"tailwindcss": "^4.2.1",
"typescript": "^5.9.3",
"vite": "^7.3.1"
},
"dependencies": {
"@automerge/automerge": "^3.2.4",
"@automerge/automerge-repo": "^2.5.3",
"@automerge/automerge-repo-network-websocket": "^2.5.3",
"@automerge/automerge-repo-storage-indexeddb": "^2.5.3",
"@capacitor-mlkit/barcode-scanning": "^8.0.1",
"@capacitor/cli": "^8.1.0",
"@capacitor/core": "^8.1.0",
"@inrupt/solid-client": "^3.0.0",
"@inrupt/solid-client-authn-browser": "^3.1.1",
"@inrupt/vocab-common-rdf": "^1.0.5",
"bwip-js": "^4.8.0",
"date-fns": "^4.1.0",
"idb": "^8.0.3",
"jspdf": "^4.2.0",
"nanoid": "^5.1.6"
}
}

454
solidhaus.ttl Normal file
View file

@ -0,0 +1,454 @@
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix schema: <https://schema.org/> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix solidhaus: <https://vocab.toph.so/solidhaus#> .
# =============================================================================
# SolidHaus Ontology — Household Inventory Tracking
# =============================================================================
#
# Namespace: https://vocab.toph.so/solidhaus#
# Version: 0.1.0
# Author: Christopher (toph)
# Date: 2026-02-26
#
# This ontology extends schema.org types for household inventory tracking.
# It is designed to be used with Solid Pods for personal data storage.
#
# Key design decisions:
# - Reuses schema.org wherever possible
# - Adds tracking-specific properties not covered by schema.org
# - Location hierarchy uses schema:containedInPlace
# - Sighting events extend schema:FindAction
# - Actions follow schema.org Action vocabulary
# =============================================================================
solidhaus: a owl:Ontology ;
dcterms:title "SolidHaus Household Inventory Ontology"@en ;
dcterms:description "An ontology for tracking physical items in a household, designed for use with Solid Pods and schema.org."@en ;
dcterms:creator "Christopher (toph)" ;
dcterms:created "2026-02-26"^^xsd:date ;
owl:versionInfo "0.2.0" ;
rdfs:seeAlso <https://schema.org/IndividualProduct> ;
rdfs:seeAlso <https://schema.org/Place> ;
rdfs:seeAlso <https://schema.org/FindAction> ;
rdfs:seeAlso <https://schema.org/CheckInAction> ;
rdfs:seeAlso <https://schema.org/CheckOutAction> .
# =============================================================================
# Classes
# =============================================================================
solidhaus:SightingEvent a rdfs:Class ;
rdfs:subClassOf schema:FindAction ;
rdfs:label "Sighting Event"@en ;
rdfs:comment "An observation of an item at a specific location and time. Created when an item's barcode is scanned or its location is manually confirmed."@en .
solidhaus:AuditSession a rdfs:Class ;
rdfs:subClassOf schema:Action ;
rdfs:label "Audit Session"@en ;
rdfs:comment "A systematic walk-through of a location to verify which items are present."@en .
solidhaus:InventoryContainer a rdfs:Class ;
rdfs:subClassOf schema:Place ;
rdfs:label "Inventory Container"@en ;
rdfs:comment "A sub-location within a room that can contain items, such as a shelf, drawer, box, or cabinet."@en .
# =============================================================================
# Classes — Check-In / Check-Out Model
# =============================================================================
#
# Items have a custody state: they are either "checked in" (stored at their
# designated location) or "checked out" (removed for use, lending, transport).
#
# This maps to schema:CheckInAction and schema:CheckOutAction, which exist
# in schema.org. We extend them with inventory-specific properties.
#
# The check-out reason captures *why* the item left storage:
# - in-use: actively being used (drill on workbench)
# - transit: being moved between locations
# - lent: lent to another person
# - repair: sent for repair/maintenance
# - temporary: temporarily relocated (e.g., for a project)
#
# Storage tiers capture *how accessible* the storage location is:
# - hot: immediately accessible (pegboard, desk, kitchen counter)
# - warm: in the room but requires effort (cabinet, toolbox, closed shelf)
# - cold: deep storage (attic box, basement bin, labeled container)
#
# The combination tells a rich story:
# "Drill is CHECKED OUT (in-use) from pegboard (hot) — at workbench"
# "Holiday lights are CHECKED IN at attic box #3 (cold)"
# "Borrowed book is CHECKED OUT (lent) from bookshelf (warm) — at neighbor's"
# =============================================================================
solidhaus:CheckOutEvent a rdfs:Class ;
rdfs:subClassOf schema:CheckOutAction ;
rdfs:label "Check-Out Event"@en ;
rdfs:comment "Records an item being taken from its storage location. The item transitions from 'checked in' to 'checked out'. Captures who took it, from where, why, and when."@en .
solidhaus:CheckInEvent a rdfs:Class ;
rdfs:subClassOf schema:CheckInAction ;
rdfs:label "Check-In Event"@en ;
rdfs:comment "Records an item being returned to a storage location. The item transitions from 'checked out' to 'checked in'. May update the designated storage location if the item is being reorganized."@en .
# =============================================================================
# Properties — Item Identification
# =============================================================================
solidhaus:shortId a rdf:Property ;
rdfs:label "Short ID"@en ;
rdfs:comment "A short, human-readable identifier (nanoid) used for barcodes and labels. Example: 'A3K7M2'."@en ;
rdfs:domain schema:Thing ;
rdfs:range xsd:string .
solidhaus:barcodeFormat a rdf:Property ;
rdfs:label "Barcode Format"@en ;
rdfs:comment "The format of the barcode printed for this item. Values: 'qr', 'code128', 'datamatrix'."@en ;
rdfs:domain schema:Thing ;
rdfs:range xsd:string .
solidhaus:barcodeUri a rdf:Property ;
rdfs:label "Barcode URI"@en ;
rdfs:comment "The full URI encoded in the barcode, e.g. 'https://inv.toph.so/A3K7M2'."@en ;
rdfs:domain schema:Thing ;
rdfs:range xsd:anyURI .
solidhaus:labelPrinted a rdf:Property ;
rdfs:label "Label Printed"@en ;
rdfs:comment "Whether a physical label has been printed for this item."@en ;
rdfs:domain schema:IndividualProduct ;
rdfs:range xsd:boolean .
solidhaus:labelPrintedAt a rdf:Property ;
rdfs:label "Label Printed At"@en ;
rdfs:comment "When the label was last printed."@en ;
rdfs:domain schema:IndividualProduct ;
rdfs:range xsd:dateTime .
# =============================================================================
# Properties — Custody State (Check-In / Check-Out)
# =============================================================================
solidhaus:custodyState a rdf:Property ;
rdfs:label "Custody State"@en ;
rdfs:comment "Whether the item is currently checked in (stored) or checked out (in use, lent, etc.)."@en ;
rdfs:domain schema:IndividualProduct ;
rdfs:range solidhaus:CustodyState .
solidhaus:checkedOutSince a rdf:Property ;
rdfs:label "Checked Out Since"@en ;
rdfs:comment "Timestamp when the item was last checked out of storage."@en ;
rdfs:domain schema:IndividualProduct ;
rdfs:range xsd:dateTime .
solidhaus:checkedOutReason a rdf:Property ;
rdfs:label "Checked Out Reason"@en ;
rdfs:comment "Why the item was checked out of storage."@en ;
rdfs:domain schema:IndividualProduct ;
rdfs:range solidhaus:CheckOutReason .
solidhaus:checkedOutFrom a rdf:Property ;
rdfs:label "Checked Out From"@en ;
rdfs:comment "The storage location the item was checked out from (so it can be returned there)."@en ;
rdfs:domain schema:IndividualProduct ;
rdfs:range schema:Place .
solidhaus:checkedOutTo a rdf:Property ;
rdfs:label "Checked Out To"@en ;
rdfs:comment "Where or to whom the item was checked out. Can be a Place (workbench) or a Person (neighbor)."@en ;
rdfs:domain schema:IndividualProduct ;
rdfs:range schema:Thing .
solidhaus:checkedOutNote a rdf:Property ;
rdfs:label "Checked Out Note"@en ;
rdfs:comment "Free-text note about the check-out, e.g. 'Lent to Max for weekend project'."@en ;
rdfs:domain schema:IndividualProduct ;
rdfs:range xsd:string .
# =============================================================================
# Properties — Storage Tier
# =============================================================================
solidhaus:storageTier a rdf:Property ;
rdfs:label "Storage Tier"@en ;
rdfs:comment "How accessible the storage location is. Applies to the item's designated storage or to a Place."@en ;
rdfs:domain schema:Thing ; # Can apply to Item (its home) or Place (inherent tier)
rdfs:range solidhaus:StorageTier .
solidhaus:storageContainer a rdf:Property ;
rdfs:label "Storage Container"@en ;
rdfs:comment "For cold-storage items: which specific container (box, bin, crate) the item is in. Important because you need to know which box to dig out."@en ;
rdfs:domain schema:IndividualProduct ;
rdfs:range schema:Thing .
solidhaus:storageContainerLabel a rdf:Property ;
rdfs:label "Storage Container Label"@en ;
rdfs:comment "Human-readable label on the storage container, e.g. 'Box #3 — Electronics' or 'Blue IKEA bin — Holiday'."@en ;
rdfs:domain schema:Thing ;
rdfs:range xsd:string .
# =============================================================================
# Properties — Location Tracking
# =============================================================================
solidhaus:lastSeenAt a rdf:Property ;
rdfs:label "Last Seen At"@en ;
rdfs:comment "The location where this item was most recently observed (scanned or manually confirmed)."@en ;
rdfs:domain schema:IndividualProduct ;
rdfs:range schema:Place .
solidhaus:lastSeenTimestamp a rdf:Property ;
rdfs:label "Last Seen Timestamp"@en ;
rdfs:comment "When this item was most recently observed."@en ;
rdfs:domain schema:IndividualProduct ;
rdfs:range xsd:dateTime .
solidhaus:lastUsedAt a rdf:Property ;
rdfs:label "Last Used At"@en ;
rdfs:comment "When this item was last used (logged via UseAction)."@en ;
rdfs:domain schema:IndividualProduct ;
rdfs:range xsd:dateTime .
solidhaus:lastKnownToBeAt a rdf:Property ;
rdfs:label "Last Known To Be At"@en ;
rdfs:comment "The most recent location where this item is known or believed to be, regardless of confidence level."@en ;
rdfs:domain schema:IndividualProduct ;
rdfs:range schema:Place .
solidhaus:supposedToBeAt a rdf:Property ;
rdfs:label "Supposed To Be At"@en ;
rdfs:comment "The designated storage location for this item — where it 'belongs' or should be returned to."@en ;
rdfs:domain schema:IndividualProduct ;
rdfs:range schema:Place .
solidhaus:locationConfidence a rdf:Property ;
rdfs:label "Location Confidence"@en ;
rdfs:comment "How confident we are about the item's current location. Values from solidhaus:ConfidenceLevel."@en ;
rdfs:domain schema:IndividualProduct ;
rdfs:range solidhaus:ConfidenceLevel .
# =============================================================================
# Properties — Location Classification
# =============================================================================
solidhaus:locationType a rdf:Property ;
rdfs:label "Location Type"@en ;
rdfs:comment "The type of location in the hierarchy. Values from solidhaus:LocationType."@en ;
rdfs:domain schema:Place ;
rdfs:range solidhaus:LocationType .
# =============================================================================
# Properties — Sighting Events
# =============================================================================
solidhaus:sightingType a rdf:Property ;
rdfs:label "Sighting Type"@en ;
rdfs:comment "How the sighting was registered."@en ;
rdfs:domain solidhaus:SightingEvent ;
rdfs:range solidhaus:SightingMethod .
solidhaus:confidence a rdf:Property ;
rdfs:label "Confidence"@en ;
rdfs:comment "The confidence level of this specific sighting observation."@en ;
rdfs:domain solidhaus:SightingEvent ;
rdfs:range solidhaus:ConfidenceLevel .
# =============================================================================
# Properties — Audit
# =============================================================================
solidhaus:auditLocation a rdf:Property ;
rdfs:label "Audit Location"@en ;
rdfs:comment "The location being audited in this session."@en ;
rdfs:domain solidhaus:AuditSession ;
rdfs:range schema:Place .
solidhaus:itemsExpected a rdf:Property ;
rdfs:label "Items Expected"@en ;
rdfs:comment "Number of items expected to be found at the audit location."@en ;
rdfs:domain solidhaus:AuditSession ;
rdfs:range xsd:integer .
solidhaus:itemsFound a rdf:Property ;
rdfs:label "Items Found"@en ;
rdfs:comment "Number of items confirmed present during the audit."@en ;
rdfs:domain solidhaus:AuditSession ;
rdfs:range xsd:integer .
# =============================================================================
# Enumerations
# =============================================================================
solidhaus:ConfidenceLevel a rdfs:Class ;
rdfs:label "Confidence Level"@en ;
rdfs:comment "Enumeration of confidence levels for item location."@en .
solidhaus:Confirmed a solidhaus:ConfidenceLevel ;
rdfs:label "Confirmed"@en ;
rdfs:comment "Item was directly scanned or visually confirmed at this location."@en .
solidhaus:Likely a solidhaus:ConfidenceLevel ;
rdfs:label "Likely"@en ;
rdfs:comment "Item was seen here recently but not confirmed recently (e.g., >30 days since last scan)."@en .
solidhaus:Assumed a solidhaus:ConfidenceLevel ;
rdfs:label "Assumed"@en ;
rdfs:comment "Item is assumed to be at its designated location but has not been verified."@en .
solidhaus:Unknown a solidhaus:ConfidenceLevel ;
rdfs:label "Unknown"@en ;
rdfs:comment "Item's current location is not known (e.g., >90 days since last scan)."@en .
solidhaus:SightingMethod a rdfs:Class ;
rdfs:label "Sighting Method"@en ;
rdfs:comment "How an item sighting was registered."@en .
solidhaus:BarcodeScan a solidhaus:SightingMethod ;
rdfs:label "Barcode Scan"@en ;
rdfs:comment "Item was identified by scanning its barcode/QR code."@en .
solidhaus:ManualInput a solidhaus:SightingMethod ;
rdfs:label "Manual Input"@en ;
rdfs:comment "Item location was manually entered by the user."@en .
solidhaus:CameraDetect a solidhaus:SightingMethod ;
rdfs:label "Camera Detect"@en ;
rdfs:comment "Item was detected via continuous camera scanning (future feature)."@en .
solidhaus:AuditVerify a solidhaus:SightingMethod ;
rdfs:label "Audit Verify"@en ;
rdfs:comment "Item was verified during an audit session."@en .
solidhaus:LocationType a rdfs:Class ;
rdfs:label "Location Type"@en ;
rdfs:comment "Classification of locations in the inventory hierarchy."@en .
solidhaus:House a solidhaus:LocationType ;
rdfs:label "House"@en .
solidhaus:Floor a solidhaus:LocationType ;
rdfs:label "Floor"@en .
solidhaus:Room a solidhaus:LocationType ;
rdfs:label "Room"@en .
solidhaus:Furniture a solidhaus:LocationType ;
rdfs:label "Furniture"@en ;
rdfs:comment "A piece of furniture that contains items (desk, wardrobe, shelf unit)."@en .
solidhaus:Shelf a solidhaus:LocationType ;
rdfs:label "Shelf"@en .
solidhaus:Drawer a solidhaus:LocationType ;
rdfs:label "Drawer"@en .
solidhaus:Box a solidhaus:LocationType ;
rdfs:label "Box"@en ;
rdfs:comment "A container/box for storage."@en .
solidhaus:Wall a solidhaus:LocationType ;
rdfs:label "Wall"@en ;
rdfs:comment "Wall-mounted storage (pegboard, hooks, etc.)."@en .
solidhaus:Outdoor a solidhaus:LocationType ;
rdfs:label "Outdoor"@en ;
rdfs:comment "Outdoor areas (garden, garage, shed)."@en .
# =============================================================================
# Enumerations — Custody State
# =============================================================================
solidhaus:CustodyState a rdfs:Class ;
rdfs:label "Custody State"@en ;
rdfs:comment "Whether an item is checked in (at its storage location) or checked out (removed for some purpose)."@en .
solidhaus:CheckedIn a solidhaus:CustodyState ;
rdfs:label "Checked In"@en ;
rdfs:comment "Item is at its designated storage location. Put away, accounted for."@en .
solidhaus:CheckedOut a solidhaus:CustodyState ;
rdfs:label "Checked Out"@en ;
rdfs:comment "Item has been removed from storage for use, lending, repair, or other purpose."@en .
# =============================================================================
# Enumerations — Check-Out Reason
# =============================================================================
solidhaus:CheckOutReason a rdfs:Class ;
rdfs:label "Check-Out Reason"@en ;
rdfs:comment "Why an item was taken from its storage location."@en .
solidhaus:InUse a solidhaus:CheckOutReason ;
rdfs:label "In Use"@en ;
rdfs:comment "Item is actively being used (drill on workbench, book being read, pan on stove)."@en .
solidhaus:InTransit a solidhaus:CheckOutReason ;
rdfs:label "In Transit"@en ;
rdfs:comment "Item is being moved between locations (e.g., carrying tools to another room)."@en .
solidhaus:Lent a solidhaus:CheckOutReason ;
rdfs:label "Lent"@en ;
rdfs:comment "Item has been lent to another person. Use checkedOutTo to record who."@en .
solidhaus:InRepair a solidhaus:CheckOutReason ;
rdfs:label "In Repair"@en ;
rdfs:comment "Item has been sent for repair or maintenance."@en .
solidhaus:Temporary a solidhaus:CheckOutReason ;
rdfs:label "Temporary Relocation"@en ;
rdfs:comment "Item has been temporarily moved for a project or event and should return to storage when done."@en .
solidhaus:Consumed a solidhaus:CheckOutReason ;
rdfs:label "Consumed / Disposed"@en ;
rdfs:comment "Item has been consumed, used up, or disposed of. Terminal state."@en .
# =============================================================================
# Enumerations — Storage Tier
# =============================================================================
#
# Storage tiers describe how easily accessible a storage location is.
# This is crucial for the UX: when you're looking for something, it matters
# whether it's on the pegboard in front of you (hot) or buried in a box
# in the attic (cold). The tier affects both where you put things and
# how you find them.
#
# Tiers can be applied to:
# 1. A Place (the pegboard is inherently "hot" storage)
# 2. An Item's designated home (this drill's home tier is "hot")
#
# =============================================================================
solidhaus:StorageTier a rdfs:Class ;
rdfs:label "Storage Tier"@en ;
rdfs:comment "How quickly and easily an item can be accessed at its storage location."@en .
solidhaus:HotStorage a solidhaus:StorageTier ;
rdfs:label "Hot / Active"@en ;
rdfs:comment "Immediately accessible, grab-and-go. Examples: pegboard, desk surface, kitchen counter, tool belt hook, frequently-used drawer. Items here are used daily or near-daily."@en .
solidhaus:WarmStorage a solidhaus:StorageTier ;
rdfs:label "Warm / Nearby"@en ;
rdfs:comment "In the room but requires some effort to access. Examples: closed cabinet, toolbox, high shelf, under-desk drawer, closet. Items here are used weekly or occasionally."@en .
solidhaus:ColdStorage a solidhaus:StorageTier ;
rdfs:label "Cold / Deep"@en ;
rdfs:comment "Deep storage requiring deliberate retrieval. Examples: attic boxes, basement bins, labeled storage containers, garage overhead racks. Items here are seasonal, archival, or rarely used. For cold storage, the storageContainer and storageContainerLabel properties become especially important — you need to know which box to dig out."@en .

1
src/app.css Normal file
View file

@ -0,0 +1 @@
@import 'tailwindcss';

13
src/app.d.ts vendored Normal file
View file

@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

11
src/app.html Normal file
View file

@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,23 @@
<script lang="ts">
let { currentPath = '/' }: { currentPath: string } = $props();
const tabs = [
{ path: '/scan', icon: 'scan', label: 'Scan' },
{ path: '/items', icon: 'package', label: 'Items' },
{ path: '/locations', icon: 'map-pin', label: 'Places' },
{ path: '/labels', icon: 'tag', label: 'Labels' },
{ path: '/settings', icon: 'more', label: 'More' },
];
</script>
<nav class="fixed bottom-0 left-0 right-0 bg-slate-800 border-t border-slate-700 flex justify-around py-2 z-50">
{#each tabs as tab}
<a
href={tab.path}
class="flex flex-col items-center px-3 py-1 text-xs min-w-[44px] min-h-[44px] justify-center
{currentPath.startsWith(tab.path) ? 'text-blue-400' : 'text-slate-400'}"
>
<span class="text-lg font-medium">{tab.label}</span>
</a>
{/each}
</nav>

1
src/lib/index.ts Normal file
View file

@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

120
src/lib/types/index.ts Normal file
View file

@ -0,0 +1,120 @@
export type ItemType =
| 'durable'
| 'consumable'
| 'disposable'
| 'perishable'
| 'media'
| 'clothing'
| 'document'
| 'container';
export type BarcodeFormat = 'qr' | 'datamatrix' | 'code128';
export type LocationConfidence = 'confirmed' | 'likely' | 'assumed' | 'unknown';
export type CustodyState = 'checked-in' | 'checked-out';
export type CheckOutReason =
| 'in-use'
| 'in-transit'
| 'lent'
| 'in-repair'
| 'temporary'
| 'consumed'
| null;
export type StorageTier = 'hot' | 'warm' | 'cold';
export type LocationType =
| 'house'
| 'floor'
| 'room'
| 'furniture'
| 'shelf'
| 'drawer'
| 'box'
| 'wall'
| 'outdoor';
export type SightingType = 'scan' | 'manual' | 'camera-detect' | 'audit-verify';
export type SightingConfidence = 'confirmed' | 'inferred' | 'assumed';
export interface Item {
shortId: string;
name: string;
description: string;
category: string;
brand: string;
serialNumber: string;
color: string;
purchaseDate: string | null;
itemType: ItemType;
currentQuantity: number | null;
originalQuantity: number | null;
quantityUnit: string | null;
lowThreshold: number | null;
expiryDate: string | null;
barcodeFormat: BarcodeFormat;
barcodeUri: string;
photoIds: string[];
lastSeenAt: string | null;
lastSeenTimestamp: string | null;
lastUsedAt: string | null;
supposedToBeAt: string | null;
locationConfidence: LocationConfidence;
custodyState: CustodyState;
checkedOutSince: string | null;
checkedOutReason: CheckOutReason;
checkedOutFrom: string | null;
checkedOutTo: string | null;
checkedOutNote: string | null;
storageTier: StorageTier;
storageContainerId: string | null;
storageContainerLabel: string | null;
labelPrinted: boolean;
labelPrintedAt: string | null;
labelBatchId: string | null;
createdAt: string;
updatedAt: string;
tags: string[];
createdBy: string | null;
}
export interface Location {
id: string;
name: string;
description: string;
parentId: string | null;
locationType: LocationType;
defaultStorageTier: StorageTier | null;
sortOrder: number;
createdAt: string;
updatedAt: string;
}
export interface Sighting {
id: string;
itemId: string;
locationId: string;
timestamp: string;
sightingType: SightingType;
confidence: SightingConfidence;
notes: string;
createdBy: string | null;
}
export interface Photo {
id: string;
itemId: string;
blob: Blob;
thumbnail: Blob | null;
createdAt: string;
}
export interface PreGeneratedId {
id: string;
generatedAt: string;
assignedTo: string | null;
batchId: string;
}

5
src/lib/utils/id.ts Normal file
View file

@ -0,0 +1,5 @@
import { customAlphabet } from 'nanoid';
const ALPHABET = '23456789abcdefghjkmnpqrstuvwxyz';
export const generateItemId = customAlphabet(ALPHABET, 7);
export const generateSightingId = () => `s_${generateItemId()}`;

14
src/routes/+layout.svelte Normal file
View file

@ -0,0 +1,14 @@
<script lang="ts">
import '../app.css';
import BottomNav from '$lib/components/BottomNav.svelte';
import { page } from '$app/stores';
let { children } = $props();
</script>
<div class="flex flex-col h-screen bg-slate-900 text-white">
<main class="flex-1 overflow-y-auto pb-16">
{@render children()}
</main>
<BottomNav currentPath={$page.url.pathname} />
</div>

2
src/routes/+layout.ts Normal file
View file

@ -0,0 +1,2 @@
export const ssr = false;
export const prerender = false;

7
src/routes/+page.svelte Normal file
View file

@ -0,0 +1,7 @@
<script lang="ts">
</script>
<div class="p-4">
<h1 class="text-2xl font-bold mb-4">SolidHaus</h1>
<p class="text-slate-400">Household inventory at a glance.</p>
</div>

View file

@ -0,0 +1,7 @@
<script lang="ts">
</script>
<div class="p-4">
<h1 class="text-2xl font-bold mb-4">Items</h1>
<p class="text-slate-400">Browse and manage your inventory.</p>
</div>

View file

@ -0,0 +1,8 @@
<script lang="ts">
import { page } from '$app/stores';
</script>
<div class="p-4">
<h1 class="text-2xl font-bold mb-4">Item Detail</h1>
<p class="text-slate-400 font-mono">{$page.params.id}</p>
</div>

View file

@ -0,0 +1,7 @@
<script lang="ts">
</script>
<div class="p-4">
<h1 class="text-2xl font-bold mb-4">New Item</h1>
<p class="text-slate-400">Add a new item to your inventory.</p>
</div>

View file

@ -0,0 +1,7 @@
<script lang="ts">
</script>
<div class="p-4">
<h1 class="text-2xl font-bold mb-4">Labels</h1>
<p class="text-slate-400">Generate and print barcode label sheets.</p>
</div>

View file

@ -0,0 +1,7 @@
<script lang="ts">
</script>
<div class="p-4">
<h1 class="text-2xl font-bold mb-4">Places</h1>
<p class="text-slate-400">Manage your locations and rooms.</p>
</div>

View file

@ -0,0 +1,7 @@
<script lang="ts">
</script>
<div class="p-4">
<h1 class="text-2xl font-bold mb-4">Scan</h1>
<p class="text-slate-400">Scan a barcode to look up or check in/out an item.</p>
</div>

View file

@ -0,0 +1,7 @@
<script lang="ts">
</script>
<div class="p-4">
<h1 class="text-2xl font-bold mb-4">Settings</h1>
<p class="text-slate-400">App configuration and sync settings.</p>
</div>

3
static/robots.txt Normal file
View file

@ -0,0 +1,3 @@
# allow crawling everything by default
User-agent: *
Disallow:

11
svelte.config.js Normal file
View file

@ -0,0 +1,11 @@
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
})
}
};

20
tsconfig.json Normal file
View file

@ -0,0 +1,20 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"rewriteRelativeImportExtensions": true,
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// To make changes to top-level options such as include and exclude, we recommend extending
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
}

10
vite.config.ts Normal file
View file

@ -0,0 +1,10 @@
import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
tailwindcss(),
sveltekit(),
],
});