refactor(mana): rename inventar → inventory across the codebase

The workbench-registry app id 'inventar' did not match its
@mana/shared-branding MANA_APPS counterpart 'inventory', so the tier-
gating join in apps/web/src/lib/app-registry/registry.ts silently
failed for the inventory module — it fell into the "no MANA_APPS
entry, default visible" fallback and was effectively un-gated. The
codebase had also voted overwhelmingly for 'inventar' (53 files) vs
'inventory' (3 files in shared-branding), so the long-standing
mismatch was just bookkeeping debt waiting to bite.

Pre-release, no live data, so the cleanest fix is to align everything
on the English 'inventory':

- Workbench-registry id, module.config.ts appId, module folder, route
  folder and i18n locale folder all renamed via git mv
- Standalone apps/inventar/ workspace package renamed
- All imports, store identifiers (InventarEvents → InventoryEvents,
  INVENTAR_GUEST_SEED, inventarModuleConfig), i18n keys and href/goto
  paths follow the rename
- The German display label "Inventar" is preserved everywhere it is a
  user-visible string (page titles, i18n values, toast labels)
- Dexie table prefixes (invCollections, invItems, …) are unchanged
- Drive-by fix: ListView.svelte was querying non-existent
  inventarCollections/inventarItems tables — corrected to the actual
  invCollections/invItems names from module.config
- The "inventar ↔ inventory id mismatch" workaround comment in
  registry.ts is removed since the mismatch no longer exists

module-registry.ts also picks up the user's parallel newsModuleConfig
addition because both edits land in the same import block — keeping
them split would have left the build in an inconsistent state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-09 15:50:24 +02:00
parent 51f408755c
commit 45790ffbb8
65 changed files with 173 additions and 147 deletions

59
apps/inventory/CLAUDE.md Normal file
View file

@ -0,0 +1,59 @@
# Inventory
Configurable inventory management app - track anything with custom schemas.
**Web App Port:** 5190
## Project Overview
Inventory is a schema-less inventory management system built with SvelteKit. Users can create collections with custom field definitions, organize items by location and category, and view them in list/grid/table views.
### Tech Stack
| Layer | Technology |
|-------|------------|
| Frontend | SvelteKit 2, Svelte 5 (runes), Tailwind CSS 4 |
| State | Svelte 5 runes ($state, $derived) with localStorage persistence |
| Icons | @mana/shared-icons (Phosphor) |
| PWA | @vite-pwa/sveltekit + Workbox |
| i18n | svelte-i18n (de, en) |
## Key Concepts
- **Collections**: Groups of items with a shared schema (custom field definitions)
- **Templates**: Predefined schemas for common item types (electronics, books, etc.)
- **Items**: Individual inventory entries with custom field values
- **Locations**: Hierarchical places (House > Room > Cabinet > Shelf)
- **Categories**: Flexible categorization with hierarchy
- **Views**: List, Grid, Table views with saved filters
## Development
```bash
# From monorepo root
pnpm dev:inventory:web # Start web app on port 5190
```
## Project Structure
```
apps/inventory/
├── apps/
│ └── web/ # SvelteKit web client
│ ├── src/
│ │ ├── routes/
│ │ │ ├── (auth)/ # Login flow
│ │ │ └── (app)/ # Authenticated app
│ │ │ ├── collections/
│ │ │ ├── items/
│ │ │ ├── locations/
│ │ │ └── categories/
│ │ └── lib/
│ │ ├── stores/ # Svelte 5 rune stores
│ │ ├── components/ # UI components
│ │ ├── i18n/ # Translations
│ │ └── data/ # Templates, defaults
│ └── static/
└── packages/
└── shared/ # Shared types & constants
```

View file

@ -0,0 +1,14 @@
{
"name": "inventory",
"version": "1.0.0",
"private": true,
"description": "Inventory - Configurable Inventory Management",
"scripts": {
"dev": "pnpm --filter @inventory/web dev",
"dev:web": "pnpm --filter @inventory/web dev"
},
"devDependencies": {
"typescript": "^5.9.3"
},
"packageManager": "pnpm@9.15.0"
}

View file

@ -0,0 +1,22 @@
{
"name": "@inventory/shared",
"version": "1.0.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts",
"./types": "./src/types/index.ts",
"./constants": "./src/constants/index.ts"
},
"scripts": {
"type-check": "tsc --noEmit"
},
"dependencies": {
"@mana/shared-types": "workspace:*"
},
"devDependencies": {
"typescript": "^5.9.3"
}
}

View file

@ -0,0 +1,199 @@
import type { Template, ItemStatus } from '../types/index.js';
export const ITEM_STATUSES: {
value: ItemStatus;
labelDe: string;
labelEn: string;
color: string;
}[] = [
{ value: 'owned', labelDe: 'Besitzt', labelEn: 'Owned', color: '#22c55e' },
{ value: 'lent', labelDe: 'Verliehen', labelEn: 'Lent', color: '#f59e0b' },
{ value: 'stored', labelDe: 'Eingelagert', labelEn: 'Stored', color: '#3b82f6' },
{ value: 'for_sale', labelDe: 'Zu verkaufen', labelEn: 'For Sale', color: '#a855f7' },
{ value: 'disposed', labelDe: 'Entsorgt', labelEn: 'Disposed', color: '#6b7280' },
];
export const DEFAULT_TEMPLATES: Template[] = [
{
id: 'electronics',
name: 'Elektronik',
description: 'Computer, Smartphones, Gadgets',
icon: '💻',
category: 'tech',
schema: {
fields: [
{ id: 'brand', name: 'Marke', type: 'text', order: 0 },
{ id: 'model', name: 'Modell', type: 'text', order: 1 },
{ id: 'serial_number', name: 'Seriennummer', type: 'text', order: 2 },
{ id: 'purchase_date', name: 'Kaufdatum', type: 'date', order: 3 },
{ id: 'warranty_until', name: 'Garantie bis', type: 'date', order: 4 },
{ id: 'price', name: 'Preis', type: 'currency', currencyCode: 'EUR', order: 5 },
{
id: 'condition',
name: 'Zustand',
type: 'select',
options: ['Neu', 'Sehr gut', 'Gut', 'Gebraucht', 'Defekt'],
order: 6,
},
],
},
},
{
id: 'books',
name: 'Bücher',
description: 'Bücher, E-Books, Hörbücher',
icon: '📚',
category: 'media',
schema: {
fields: [
{ id: 'author', name: 'Autor', type: 'text', order: 0 },
{ id: 'isbn', name: 'ISBN', type: 'text', order: 1 },
{ id: 'publisher', name: 'Verlag', type: 'text', order: 2 },
{ id: 'genre', name: 'Genre', type: 'text', order: 3 },
{ id: 'pages', name: 'Seiten', type: 'number', order: 4 },
{ id: 'read', name: 'Gelesen', type: 'checkbox', order: 5 },
{
id: 'rating',
name: 'Bewertung',
type: 'select',
options: ['1', '2', '3', '4', '5'],
order: 6,
},
],
},
},
{
id: 'furniture',
name: 'Möbel',
description: 'Tische, Stühle, Regale',
icon: '🪑',
category: 'home',
schema: {
fields: [
{ id: 'material', name: 'Material', type: 'text', order: 0 },
{ id: 'dimensions', name: 'Maße', type: 'text', placeholder: 'B x H x T in cm', order: 1 },
{ id: 'color', name: 'Farbe', type: 'text', order: 2 },
{ id: 'room', name: 'Raum', type: 'text', order: 3 },
{
id: 'condition',
name: 'Zustand',
type: 'select',
options: ['Neu', 'Sehr gut', 'Gut', 'Gebraucht', 'Reparaturbedürftig'],
order: 4,
},
{ id: 'price', name: 'Preis', type: 'currency', currencyCode: 'EUR', order: 5 },
],
},
},
{
id: 'clothing',
name: 'Kleidung',
description: 'Kleidung, Schuhe, Accessoires',
icon: '👕',
category: 'fashion',
schema: {
fields: [
{ id: 'brand', name: 'Marke', type: 'text', order: 0 },
{ id: 'size', name: 'Größe', type: 'text', order: 1 },
{ id: 'color', name: 'Farbe', type: 'text', order: 2 },
{ id: 'material', name: 'Material', type: 'text', order: 3 },
{
id: 'season',
name: 'Saison',
type: 'select',
options: ['Frühling', 'Sommer', 'Herbst', 'Winter', 'Ganzjährig'],
order: 4,
},
{ id: 'price', name: 'Preis', type: 'currency', currencyCode: 'EUR', order: 5 },
],
},
},
{
id: 'tools',
name: 'Werkzeug',
description: 'Handwerkzeug, Elektrowerkzeug',
icon: '🔧',
category: 'home',
schema: {
fields: [
{ id: 'brand', name: 'Marke', type: 'text', order: 0 },
{ id: 'model', name: 'Modell', type: 'text', order: 1 },
{
id: 'type',
name: 'Typ',
type: 'select',
options: ['Handwerkzeug', 'Elektrowerkzeug', 'Messwerkzeug', 'Sonstiges'],
order: 2,
},
{
id: 'condition',
name: 'Zustand',
type: 'select',
options: ['Neu', 'Gut', 'Gebraucht', 'Defekt'],
order: 3,
},
{ id: 'price', name: 'Preis', type: 'currency', currencyCode: 'EUR', order: 4 },
],
},
},
{
id: 'kitchen',
name: 'Küche',
description: 'Küchengeräte, Geschirr, Besteck',
icon: '🍳',
category: 'home',
schema: {
fields: [
{ id: 'brand', name: 'Marke', type: 'text', order: 0 },
{ id: 'material', name: 'Material', type: 'text', order: 1 },
{
id: 'category',
name: 'Kategorie',
type: 'select',
options: ['Gerät', 'Geschirr', 'Besteck', 'Topf/Pfanne', 'Sonstiges'],
order: 2,
},
{ id: 'dishwasher_safe', name: 'Spülmaschinenfest', type: 'checkbox', order: 3 },
{ id: 'price', name: 'Preis', type: 'currency', currencyCode: 'EUR', order: 4 },
],
},
},
{
id: 'media',
name: 'Medien',
description: 'Filme, Musik, Spiele',
icon: '🎬',
category: 'media',
schema: {
fields: [
{
id: 'format',
name: 'Format',
type: 'select',
options: ['DVD', 'Blu-ray', 'CD', 'Vinyl', 'Digital', 'Kassette'],
order: 0,
},
{ id: 'artist', name: 'Künstler/Regisseur', type: 'text', order: 1 },
{ id: 'genre', name: 'Genre', type: 'text', order: 2 },
{ id: 'year', name: 'Erscheinungsjahr', type: 'number', order: 3 },
{
id: 'rating',
name: 'Bewertung',
type: 'select',
options: ['1', '2', '3', '4', '5'],
order: 4,
},
],
},
},
{
id: 'custom',
name: 'Benutzerdefiniert',
description: 'Leere Sammlung, eigene Felder definieren',
icon: '✨',
category: 'other',
schema: {
fields: [],
},
},
];

View file

@ -0,0 +1,2 @@
export * from './types/index.js';
export * from './constants/index.js';

View file

@ -0,0 +1,171 @@
// Field types for configurable schemas
export type FieldType =
| 'text'
| 'number'
| 'date'
| 'select'
| 'tags'
| 'checkbox'
| 'url'
| 'currency';
// Custom field definition (stored in collection schema)
export interface FieldDefinition {
id: string;
name: string;
type: FieldType;
required?: boolean;
defaultValue?: unknown;
options?: string[]; // for select fields
currencyCode?: string; // for currency fields
placeholder?: string;
order: number;
}
// Collection schema
export interface CollectionSchema {
fields: FieldDefinition[];
}
// Item status
export type ItemStatus = 'owned' | 'lent' | 'stored' | 'for_sale' | 'disposed';
// Purchase data
export interface PurchaseData {
price?: number;
currency?: string;
date?: string;
retailer?: string;
warrantyExpiry?: string;
receiptUrl?: string;
}
// Item note
export interface ItemNote {
id: string;
content: string;
createdAt: string;
updatedAt: string;
}
// Photo
export interface ItemPhoto {
id: string;
url: string;
thumbnailUrl?: string;
caption?: string;
order: number;
}
// Document attachment
export interface ItemDocument {
id: string;
name: string;
url: string;
mimeType: string;
size: number;
uploadedAt: string;
}
// Collection
export interface Collection {
id: string;
name: string;
description?: string;
icon?: string;
color?: string;
schema: CollectionSchema;
templateId?: string;
order: number;
itemCount?: number;
createdAt: string;
updatedAt: string;
}
// Location (hierarchical)
export interface Location {
id: string;
parentId?: string;
name: string;
description?: string;
icon?: string;
path: string;
depth: number;
order: number;
children?: Location[];
createdAt: string;
updatedAt: string;
}
// Category
export interface Category {
id: string;
parentId?: string;
name: string;
icon?: string;
color?: string;
order: number;
children?: Category[];
createdAt: string;
updatedAt: string;
}
// Item
export interface Item {
id: string;
collectionId: string;
locationId?: string;
categoryId?: string;
name: string;
description?: string;
status: ItemStatus;
quantity: number;
fieldValues: Record<string, unknown>;
purchaseData?: PurchaseData;
photos: ItemPhoto[];
notes: ItemNote[];
documents: ItemDocument[];
tags: string[];
order: number;
createdAt: string;
updatedAt: string;
}
// Template definition
export interface Template {
id: string;
name: string;
description: string;
icon: string;
schema: CollectionSchema;
category: string;
}
// Saved filter
export interface SavedFilter {
id: string;
name: string;
criteria: FilterCriteria;
createdAt: string;
}
export interface FilterCriteria {
search?: string;
status?: ItemStatus[];
locationId?: string;
categoryId?: string;
tagIds?: string[];
collectionId?: string;
}
// View mode
export type ViewMode = 'list' | 'grid' | 'table';
// Sort options
export type SortField = 'name' | 'createdAt' | 'updatedAt' | 'status' | 'quantity';
export type SortDirection = 'asc' | 'desc';
export interface SortOption {
field: SortField;
direction: SortDirection;
}

View file

@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"]
}