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

View file

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

View file

@ -1,4 +1,4 @@
# Inventar # Inventory
Configurable inventory management app - track anything with custom schemas. Configurable inventory management app - track anything with custom schemas.
@ -6,7 +6,7 @@ Configurable inventory management app - track anything with custom schemas.
## Project Overview ## Project Overview
Inventar 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. 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 ### Tech Stack
@ -31,13 +31,13 @@ Inventar is a schema-less inventory management system built with SvelteKit. User
```bash ```bash
# From monorepo root # From monorepo root
pnpm dev:inventar:web # Start web app on port 5190 pnpm dev:inventory:web # Start web app on port 5190
``` ```
## Project Structure ## Project Structure
``` ```
apps/inventar/ apps/inventory/
├── apps/ ├── apps/
│ └── web/ # SvelteKit web client │ └── web/ # SvelteKit web client
│ ├── src/ │ ├── src/

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

@ -1,5 +1,5 @@
{ {
"name": "@inventar/shared", "name": "@inventory/shared",
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"type": "module", "type": "module",

View file

@ -34,7 +34,7 @@ const sections: Section[] = [
{ name: 'ManaCalendar', icon: 'ph:calendar-bold', tagline: 'Kalender', url: 'https://calendar.mana.how' }, { name: 'ManaCalendar', icon: 'ph:calendar-bold', tagline: 'Kalender', url: 'https://calendar.mana.how' },
{ name: 'Kontakte', icon: 'ph:address-book-bold', tagline: 'Kontakte', url: 'https://contacts.mana.how' }, { name: 'Kontakte', icon: 'ph:address-book-bold', tagline: 'Kontakte', url: 'https://contacts.mana.how' },
{ name: 'ManaStorage', icon: 'ph:cloud-bold', tagline: 'Cloud-Speicher', url: 'https://storage.mana.how' }, { name: 'ManaStorage', icon: 'ph:cloud-bold', tagline: 'Cloud-Speicher', url: 'https://storage.mana.how' },
{ name: 'Inventar', icon: 'ph:package-bold', tagline: 'Inventar', url: 'https://inventar.mana.how' }, { name: 'Inventar', icon: 'ph:package-bold', tagline: 'Inventar', url: 'https://inventory.mana.how' },
], ],
}, },
{ {

View file

@ -64,7 +64,7 @@ const APP_SUBDOMAINS = new Set([
'picture', 'picture',
'calc', 'calc',
'citycorners', 'citycorners',
'inventar', 'inventory',
'times', 'times',
'uload', 'uload',
'memoro', 'memoro',

View file

@ -40,6 +40,7 @@ import {
Calculator, Calculator,
Lightning, Lightning,
Sparkle, Sparkle,
Newspaper,
} from '@mana/shared-icons'; } from '@mana/shared-icons';
// ── Apps with entity capabilities ─────────────────────────── // ── Apps with entity capabilities ───────────────────────────
@ -565,13 +566,13 @@ registerApp({
}); });
registerApp({ registerApp({
id: 'inventar', id: 'inventory',
name: 'Inventar', name: 'Inventar',
color: '#78716C', color: '#78716C',
icon: Package, icon: Package,
views: { views: {
list: { load: () => import('$lib/modules/inventar/ListView.svelte') }, list: { load: () => import('$lib/modules/inventory/ListView.svelte') },
detail: { load: () => import('$lib/modules/inventar/views/DetailView.svelte') }, detail: { load: () => import('$lib/modules/inventory/views/DetailView.svelte') },
}, },
}); });
@ -669,3 +670,24 @@ registerApp({
list: { load: () => import('$lib/modules/playground/ListView.svelte') }, list: { load: () => import('$lib/modules/playground/ListView.svelte') },
}, },
}); });
registerApp({
id: 'news',
name: 'News',
color: '#10B981',
icon: Newspaper,
views: {
list: { load: () => import('$lib/modules/news/ListView.svelte') },
},
contextMenuActions: [
{
id: 'open-feed',
label: 'Feed öffnen',
icon: Plus,
action: () =>
window.dispatchEvent(
new CustomEvent('mana:quick-action', { detail: { app: 'news', action: 'open' } })
),
},
],
});

View file

@ -91,10 +91,9 @@ export function getAllApps(): AppDescriptor[] {
* *
* Returns null when there is no matching MANA_APPS entry. Internal-only * Returns null when there is no matching MANA_APPS entry. Internal-only
* tools that exist in the workbench but not in MANA_APPS (`automations`, * tools that exist in the workbench but not in MANA_APPS (`automations`,
* `playground`, the `inventar` `inventory` id mismatch) fall into this * `playground`) fall into this case `getAccessibleApps` then treats
* case `getAccessibleApps` then treats them as visible by default * them as visible by default rather than hiding them for everyone, since
* rather than hiding them for everyone, since the alternative is a * the alternative is a silent regression for founders/devs.
* silent regression for founders/devs.
*/ */
function getAppRequiredTier(appId: string): AccessTier | null { function getAppRequiredTier(appId: string): AccessTier | null {
const branding = MANA_APPS.find((a) => a.id === appId); const branding = MANA_APPS.find((a) => a.id === appId);

View file

@ -138,7 +138,7 @@ describe('module-registry — pre-refactor snapshot', () => {
music: ['songs', 'mukkePlaylists', 'playlistSongs', 'mukkeProjects', 'markers', 'songTags'], music: ['songs', 'mukkePlaylists', 'playlistSongs', 'mukkeProjects', 'markers', 'songTags'],
storage: ['files', 'storageFolders', 'fileTags'], storage: ['files', 'storageFolders', 'fileTags'],
presi: ['presiDecks', 'slides', 'presiDeckTags'], presi: ['presiDecks', 'slides', 'presiDeckTags'],
inventar: ['invCollections', 'invItems', 'invLocations', 'invCategories', 'invItemTags'], inventory: ['invCollections', 'invItems', 'invLocations', 'invCategories', 'invItemTags'],
photos: ['albums', 'albumItems', 'photoFavorites', 'photoMediaTags'], photos: ['albums', 'albumItems', 'photoFavorites', 'photoMediaTags'],
skilltree: ['skills', 'activities', 'achievements', 'skillTags'], skilltree: ['skills', 'activities', 'achievements', 'skillTags'],
citycorners: ['cities', 'ccLocations', 'ccFavorites', 'ccLocationTags'], citycorners: ['cities', 'ccLocations', 'ccFavorites', 'ccLocationTags'],

View file

@ -61,7 +61,7 @@ import { zitareModuleConfig } from '$lib/modules/zitare/module.config';
import { musicModuleConfig } from '$lib/modules/music/module.config'; import { musicModuleConfig } from '$lib/modules/music/module.config';
import { storageModuleConfig } from '$lib/modules/storage/module.config'; import { storageModuleConfig } from '$lib/modules/storage/module.config';
import { presiModuleConfig } from '$lib/modules/presi/module.config'; import { presiModuleConfig } from '$lib/modules/presi/module.config';
import { inventarModuleConfig } from '$lib/modules/inventar/module.config'; import { inventoryModuleConfig } from '$lib/modules/inventory/module.config';
import { photosModuleConfig } from '$lib/modules/photos/module.config'; import { photosModuleConfig } from '$lib/modules/photos/module.config';
import { skilltreeModuleConfig } from '$lib/modules/skilltree/module.config'; import { skilltreeModuleConfig } from '$lib/modules/skilltree/module.config';
import { citycornersModuleConfig } from '$lib/modules/citycorners/module.config'; import { citycornersModuleConfig } from '$lib/modules/citycorners/module.config';
@ -84,6 +84,7 @@ import { financeModuleConfig } from '$lib/modules/finance/module.config';
import { placesModuleConfig } from '$lib/modules/places/module.config'; import { placesModuleConfig } from '$lib/modules/places/module.config';
import { playgroundModuleConfig } from '$lib/modules/playground/module.config'; import { playgroundModuleConfig } from '$lib/modules/playground/module.config';
import { whoModuleConfig } from '$lib/modules/who/module.config'; import { whoModuleConfig } from '$lib/modules/who/module.config';
import { newsModuleConfig } from '$lib/modules/news/module.config';
export const MODULE_CONFIGS: readonly ModuleConfig[] = [ export const MODULE_CONFIGS: readonly ModuleConfig[] = [
manaCoreConfig, manaCoreConfig,
@ -100,7 +101,7 @@ export const MODULE_CONFIGS: readonly ModuleConfig[] = [
musicModuleConfig, musicModuleConfig,
storageModuleConfig, storageModuleConfig,
presiModuleConfig, presiModuleConfig,
inventarModuleConfig, inventoryModuleConfig,
photosModuleConfig, photosModuleConfig,
skilltreeModuleConfig, skilltreeModuleConfig,
citycornersModuleConfig, citycornersModuleConfig,
@ -123,6 +124,7 @@ export const MODULE_CONFIGS: readonly ModuleConfig[] = [
placesModuleConfig, placesModuleConfig,
playgroundModuleConfig, playgroundModuleConfig,
whoModuleConfig, whoModuleConfig,
newsModuleConfig,
]; ];
// ─── Derived Maps ────────────────────────────────────────── // ─── Derived Maps ──────────────────────────────────────────

View file

@ -38,7 +38,7 @@ function registerLocale(lang: SupportedLocale) {
presi, presi,
uload, uload,
times, times,
inventar, inventory,
photos, photos,
nutriphi, nutriphi,
planta, planta,
@ -49,6 +49,7 @@ function registerLocale(lang: SupportedLocale) {
guides, guides,
help, help,
cycles, cycles,
news,
] = await Promise.all([ ] = await Promise.all([
import(`./locales/apps/${lang}.json`), import(`./locales/apps/${lang}.json`),
import(`./locales/common/${lang}.json`), import(`./locales/common/${lang}.json`),
@ -72,7 +73,7 @@ function registerLocale(lang: SupportedLocale) {
import(`./locales/presi/${lang}.json`), import(`./locales/presi/${lang}.json`),
import(`./locales/uload/${lang}.json`), import(`./locales/uload/${lang}.json`),
import(`./locales/times/${lang}.json`), import(`./locales/times/${lang}.json`),
import(`./locales/inventar/${lang}.json`), import(`./locales/inventory/${lang}.json`),
import(`./locales/photos/${lang}.json`), import(`./locales/photos/${lang}.json`),
import(`./locales/nutriphi/${lang}.json`), import(`./locales/nutriphi/${lang}.json`),
import(`./locales/planta/${lang}.json`), import(`./locales/planta/${lang}.json`),
@ -83,6 +84,7 @@ function registerLocale(lang: SupportedLocale) {
import(`./locales/guides/${lang}.json`), import(`./locales/guides/${lang}.json`),
import(`./locales/help/${lang}.json`), import(`./locales/help/${lang}.json`),
import(`./locales/cycles/${lang}.json`), import(`./locales/cycles/${lang}.json`),
import(`./locales/news/${lang}.json`),
]); ]);
return { return {
@ -108,7 +110,7 @@ function registerLocale(lang: SupportedLocale) {
presi: presi.default, presi: presi.default,
uload: uload.default, uload: uload.default,
times: times.default, times: times.default,
inventar: inventar.default, inventory: inventory.default,
photos: photos.default, photos: photos.default,
nutriphi: nutriphi.default, nutriphi: nutriphi.default,
planta: planta.default, planta: planta.default,
@ -119,6 +121,7 @@ function registerLocale(lang: SupportedLocale) {
guides: guides.default, guides: guides.default,
help: help.default, help: help.default,
cycles: cycles.default, cycles: cycles.default,
news: news.default,
}; };
}); });
} }

View file

@ -18,7 +18,7 @@
"nutriphi": "NutriPhi", "nutriphi": "NutriPhi",
"planta": "Planta", "planta": "Planta",
"presi": "Presi", "presi": "Presi",
"inventar": "Inventar", "inventory": "Inventar",
"memoro": "Memoro", "memoro": "Memoro",
"questions": "Recherche", "questions": "Recherche",
"skilltree": "Skills", "skilltree": "Skills",

View file

@ -18,7 +18,7 @@
"nutriphi": "NutriPhi", "nutriphi": "NutriPhi",
"planta": "Planta", "planta": "Planta",
"presi": "Presi", "presi": "Presi",
"inventar": "Inventory", "inventory": "Inventory",
"memoro": "Memoro", "memoro": "Memoro",
"questions": "Research", "questions": "Research",
"skilltree": "Skills", "skilltree": "Skills",

View file

@ -18,7 +18,7 @@
"nutriphi": "NutriPhi", "nutriphi": "NutriPhi",
"planta": "Planta", "planta": "Planta",
"presi": "Presi", "presi": "Presi",
"inventar": "Inventario", "inventory": "Inventario",
"memoro": "Memoro", "memoro": "Memoro",
"questions": "Investigación", "questions": "Investigación",
"skilltree": "Skills", "skilltree": "Skills",

View file

@ -18,7 +18,7 @@
"nutriphi": "NutriPhi", "nutriphi": "NutriPhi",
"planta": "Planta", "planta": "Planta",
"presi": "Presi", "presi": "Presi",
"inventar": "Inventaire", "inventory": "Inventaire",
"memoro": "Memoro", "memoro": "Memoro",
"questions": "Recherche", "questions": "Recherche",
"skilltree": "Skills", "skilltree": "Skills",

View file

@ -18,7 +18,7 @@
"nutriphi": "NutriPhi", "nutriphi": "NutriPhi",
"planta": "Planta", "planta": "Planta",
"presi": "Presi", "presi": "Presi",
"inventar": "Inventario", "inventory": "Inventario",
"memoro": "Memoro", "memoro": "Memoro",
"questions": "Ricerca", "questions": "Ricerca",
"skilltree": "Skills", "skilltree": "Skills",

View file

@ -1,5 +1,5 @@
<!-- <!--
Inventar — Workbench ListView Inventory — Workbench ListView
Collections and items overview. Collections and items overview.
--> -->
<script lang="ts"> <script lang="ts">
@ -12,12 +12,12 @@
let { navigate }: ViewProps = $props(); let { navigate }: ViewProps = $props();
const collectionsQuery = useLiveQueryWithDefault(async () => { const collectionsQuery = useLiveQueryWithDefault(async () => {
const all = await db.table<LocalCollection>('inventarCollections').orderBy('order').toArray(); const all = await db.table<LocalCollection>('invCollections').orderBy('order').toArray();
return all.filter((c) => !c.deletedAt); return all.filter((c) => !c.deletedAt);
}, [] as LocalCollection[]); }, [] as LocalCollection[]);
const itemsQuery = useLiveQueryWithDefault(async () => { const itemsQuery = useLiveQueryWithDefault(async () => {
const all = await db.table<LocalItem>('inventarItems').toArray(); const all = await db.table<LocalItem>('invItems').toArray();
return all.filter((i) => !i.deletedAt); return all.filter((i) => !i.deletedAt);
}, [] as LocalItem[]); }, [] as LocalItem[]);

View file

@ -20,7 +20,7 @@ const DEMO_COLLECTION_ID = 'demo-electronics';
const DEMO_LOCATION_ID = 'demo-home'; const DEMO_LOCATION_ID = 'demo-home';
const DEMO_CATEGORY_ID = 'demo-tech'; const DEMO_CATEGORY_ID = 'demo-tech';
export const INVENTAR_GUEST_SEED = { export const INVENTORY_GUEST_SEED = {
invCollections: [ invCollections: [
{ {
id: DEMO_COLLECTION_ID, id: DEMO_COLLECTION_ID,

View file

@ -1,7 +1,7 @@
/** /**
* Inventar constants templates and status definitions. * Inventar constants templates and status definitions.
* *
* Inlined from @inventar/shared since the unified app does not depend on it. * Inlined from @inventory/shared since the unified app does not depend on it.
*/ */
import type { ItemStatus } from './queries'; import type { ItemStatus } from './queries';

View file

@ -49,6 +49,6 @@ export {
invItemTable, invItemTable,
invLocationTable, invLocationTable,
invCategoryTable, invCategoryTable,
INVENTAR_GUEST_SEED, INVENTORY_GUEST_SEED,
} from './collections'; } from './collections';
export type { LocalCollection, LocalItem, LocalLocation, LocalCategory } from './types'; export type { LocalCollection, LocalItem, LocalLocation, LocalCategory } from './types';

View file

@ -1,7 +1,7 @@
import type { ModuleConfig } from '$lib/data/module-registry'; import type { ModuleConfig } from '$lib/data/module-registry';
export const inventarModuleConfig: ModuleConfig = { export const inventoryModuleConfig: ModuleConfig = {
appId: 'inventar', appId: 'inventory',
tables: [ tables: [
{ name: 'invCollections', syncName: 'collections' }, { name: 'invCollections', syncName: 'collections' },
{ name: 'invItems', syncName: 'items' }, { name: 'invItems', syncName: 'items' },

View file

@ -9,7 +9,7 @@ import { db } from '$lib/data/database';
import { decryptRecords } from '$lib/data/crypto'; import { decryptRecords } from '$lib/data/crypto';
import type { LocalCollection, LocalItem, LocalLocation, LocalCategory } from './types'; import type { LocalCollection, LocalItem, LocalLocation, LocalCategory } from './types';
// ─── Shared Types (inline to avoid @inventar/shared dependency) ─── // ─── Shared Types (inline to avoid @inventory/shared dependency) ───
export interface Collection { export interface Collection {
id: string; id: string;

View file

@ -8,7 +8,7 @@
import { invCategoryTable } from '../collections'; import { invCategoryTable } from '../collections';
import { toCategory } from '../queries'; import { toCategory } from '../queries';
import type { LocalCategory } from '../types'; import type { LocalCategory } from '../types';
import { InventarEvents } from '@mana/shared-utils/analytics'; import { InventoryEvents } from '@mana/shared-utils/analytics';
export const categoriesStore = { export const categoriesStore = {
async create(data: { name: string; icon?: string; color?: string; parentId?: string }) { async create(data: { name: string; icon?: string; color?: string; parentId?: string }) {
@ -25,7 +25,7 @@ export const categoriesStore = {
order: siblings.length, order: siblings.length,
}; };
await invCategoryTable.add(newLocal); await invCategoryTable.add(newLocal);
InventarEvents.categoryCreated(); InventoryEvents.categoryCreated();
return toCategory(newLocal); return toCategory(newLocal);
}, },
@ -49,6 +49,6 @@ export const categoriesStore = {
for (const deleteId of idsToDelete) { for (const deleteId of idsToDelete) {
await invCategoryTable.update(deleteId, { deletedAt: now, updatedAt: now }); await invCategoryTable.update(deleteId, { deletedAt: now, updatedAt: now });
} }
InventarEvents.categoryDeleted(); InventoryEvents.categoryDeleted();
}, },
}; };

View file

@ -8,7 +8,7 @@
import { invCollectionTable } from '../collections'; import { invCollectionTable } from '../collections';
import { toCollection } from '../queries'; import { toCollection } from '../queries';
import type { LocalCollection } from '../types'; import type { LocalCollection } from '../types';
import { InventarEvents } from '@mana/shared-utils/analytics'; import { InventoryEvents } from '@mana/shared-utils/analytics';
export const collectionsStore = { export const collectionsStore = {
async create(data: { async create(data: {
@ -33,7 +33,7 @@ export const collectionsStore = {
itemCount: 0, itemCount: 0,
}; };
await invCollectionTable.add(newLocal); await invCollectionTable.add(newLocal);
InventarEvents.collectionCreated(); InventoryEvents.collectionCreated();
return toCollection(newLocal); return toCollection(newLocal);
}, },
@ -52,7 +52,7 @@ export const collectionsStore = {
deletedAt: new Date().toISOString(), deletedAt: new Date().toISOString(),
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
}); });
InventarEvents.collectionDeleted(); InventoryEvents.collectionDeleted();
}, },
async reorder(orderedIds: string[]) { async reorder(orderedIds: string[]) {

View file

@ -9,7 +9,7 @@ import { invItemTable } from '../collections';
import { toItem } from '../queries'; import { toItem } from '../queries';
import type { LocalItem } from '../types'; import type { LocalItem } from '../types';
import type { ItemStatus } from '../queries'; import type { ItemStatus } from '../queries';
import { InventarEvents } from '@mana/shared-utils/analytics'; import { InventoryEvents } from '@mana/shared-utils/analytics';
import { encryptRecord } from '$lib/data/crypto'; import { encryptRecord } from '$lib/data/crypto';
export const itemsStore = { export const itemsStore = {
@ -49,7 +49,7 @@ export const itemsStore = {
const plaintextSnapshot = toItem(newLocal); const plaintextSnapshot = toItem(newLocal);
await encryptRecord('invItems', newLocal); await encryptRecord('invItems', newLocal);
await invItemTable.add(newLocal); await invItemTable.add(newLocal);
InventarEvents.itemCreated(); InventoryEvents.itemCreated();
return plaintextSnapshot; return plaintextSnapshot;
}, },
@ -76,7 +76,7 @@ export const itemsStore = {
}; };
await encryptRecord('invItems', diff); await encryptRecord('invItems', diff);
await invItemTable.update(id, diff); await invItemTable.update(id, diff);
InventarEvents.itemUpdated(); InventoryEvents.itemUpdated();
}, },
async delete(id: string) { async delete(id: string) {
@ -84,7 +84,7 @@ export const itemsStore = {
deletedAt: new Date().toISOString(), deletedAt: new Date().toISOString(),
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
}); });
InventarEvents.itemDeleted(); InventoryEvents.itemDeleted();
}, },
async deleteByCollection(collectionId: string) { async deleteByCollection(collectionId: string) {

View file

@ -8,7 +8,7 @@
import { invLocationTable } from '../collections'; import { invLocationTable } from '../collections';
import { toLocation } from '../queries'; import { toLocation } from '../queries';
import type { LocalLocation } from '../types'; import type { LocalLocation } from '../types';
import { InventarEvents } from '@mana/shared-utils/analytics'; import { InventoryEvents } from '@mana/shared-utils/analytics';
function buildPath(locations: LocalLocation[], parentId?: string): string { function buildPath(locations: LocalLocation[], parentId?: string): string {
if (!parentId) return ''; if (!parentId) return '';
@ -42,7 +42,7 @@ export const locationsStore = {
order: siblings.length, order: siblings.length,
}; };
await invLocationTable.add(newLocal); await invLocationTable.add(newLocal);
InventarEvents.locationCreated(); InventoryEvents.locationCreated();
return toLocation(newLocal); return toLocation(newLocal);
}, },
@ -66,6 +66,6 @@ export const locationsStore = {
for (const deleteId of idsToDelete) { for (const deleteId of idsToDelete) {
await invLocationTable.update(deleteId, { deletedAt: now, updatedAt: now }); await invLocationTable.update(deleteId, { deletedAt: now, updatedAt: now });
} }
InventarEvents.locationDeleted(); InventoryEvents.locationDeleted();
}, },
}; };

View file

@ -1,5 +1,5 @@
/** /**
* Uinventar Tags Uses shared global tags + module-specific junction table. * Inventory Tags Uses shared global tags + module-specific junction table.
*/ */
import { db } from '$lib/data/database'; import { db } from '$lib/data/database';

View file

@ -6,7 +6,7 @@ import { createViewStore } from '@mana/shared-stores';
import type { ViewMode, FilterCriteria } from '../queries'; import type { ViewMode, FilterCriteria } from '../queries';
export const viewStore = createViewStore<ViewMode, FilterCriteria>({ export const viewStore = createViewStore<ViewMode, FilterCriteria>({
storagePrefix: 'inventar', storagePrefix: 'inventory',
defaultViewMode: 'list', defaultViewMode: 'list',
defaultSort: { field: 'name', direction: 'asc' }, defaultSort: { field: 'name', direction: 'asc' },
hasActiveFilters: (f) => hasActiveFilters: (f) =>

View file

@ -17,7 +17,7 @@ const SPLIT_APP_ID_LIST = [
'music', 'music',
'storage', 'storage',
'presi', 'presi',
'inventar', 'inventory',
'photos', 'photos',
'skilltree', 'skilltree',
'citycorners', 'citycorners',

View file

@ -6,8 +6,8 @@
useAllItems, useAllItems,
useAllLocations, useAllLocations,
useAllCategories, useAllCategories,
} from '$lib/modules/inventar/queries'; } from '$lib/modules/inventory/queries';
import { viewStore } from '$lib/modules/inventar/stores/view.svelte'; import { viewStore } from '$lib/modules/inventory/stores/view.svelte';
let { children }: { children: Snippet } = $props(); let { children }: { children: Snippet } = $props();

View file

@ -1,14 +1,14 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { collectionsStore } from '$lib/modules/inventar/stores/collections.svelte'; import { collectionsStore } from '$lib/modules/inventory/stores/collections.svelte';
import { itemsStore } from '$lib/modules/inventar/stores/items.svelte'; import { itemsStore } from '$lib/modules/inventory/stores/items.svelte';
import { import {
getSortedCollections, getSortedCollections,
getItemCountByCollection, getItemCountByCollection,
getTotalItemCount, getTotalItemCount,
} from '$lib/modules/inventar/queries'; } from '$lib/modules/inventory/queries';
import type { Collection, Item } from '$lib/modules/inventar/queries'; import type { Collection, Item } from '$lib/modules/inventory/queries';
import { Plus, Trash } from '@mana/shared-icons'; import { Plus, Trash } from '@mana/shared-icons';
const collectionsCtx: { readonly value: Collection[] } = getContext('collections'); const collectionsCtx: { readonly value: Collection[] } = getContext('collections');
@ -19,7 +19,7 @@
} }
function handleCollectionClick(collection: Collection) { function handleCollectionClick(collection: Collection) {
goto(`/inventar/collections/${collection.id}`); goto(`/inventory/collections/${collection.id}`);
} }
function handleDelete(e: Event, id: string) { function handleDelete(e: Event, id: string) {
@ -50,7 +50,7 @@
</p> </p>
</div> </div>
<a <a
href="/inventar/collections/new" href="/inventory/collections/new"
class="flex items-center gap-2 rounded-lg bg-[hsl(var(--primary))] px-4 py-2 text-sm font-medium text-[hsl(var(--primary-foreground))] transition-colors hover:opacity-90" class="flex items-center gap-2 rounded-lg bg-[hsl(var(--primary))] px-4 py-2 text-sm font-medium text-[hsl(var(--primary-foreground))] transition-colors hover:opacity-90"
> >
<Plus size={20} /> <Plus size={20} />
@ -69,7 +69,7 @@
Erstelle deine erste Sammlung, um loszulegen. Erstelle deine erste Sammlung, um loszulegen.
</p> </p>
<a <a
href="/inventar/collections/new" href="/inventory/collections/new"
class="rounded-lg bg-[hsl(var(--primary))] px-6 py-2.5 text-sm font-medium text-[hsl(var(--primary-foreground))]" class="rounded-lg bg-[hsl(var(--primary))] px-6 py-2.5 text-sm font-medium text-[hsl(var(--primary-foreground))]"
> >
Neue Sammlung Neue Sammlung

View file

@ -2,8 +2,8 @@
import { _ } from 'svelte-i18n'; import { _ } from 'svelte-i18n';
import { Plus } from '@mana/shared-icons'; import { Plus } from '@mana/shared-icons';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { categoriesStore } from '$lib/modules/inventar/stores/categories.svelte'; import { categoriesStore } from '$lib/modules/inventory/stores/categories.svelte';
import type { Category } from '$lib/modules/inventar/queries'; import type { Category } from '$lib/modules/inventory/queries';
const categoriesCtx: { readonly value: Category[] } = getContext('categories'); const categoriesCtx: { readonly value: Category[] } = getContext('categories');

View file

@ -3,18 +3,18 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { CaretLeft, PencilSimple, Plus, Trash } from '@mana/shared-icons'; import { CaretLeft, PencilSimple, Plus, Trash } from '@mana/shared-icons';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { itemsStore } from '$lib/modules/inventar/stores/items.svelte'; import { itemsStore } from '$lib/modules/inventory/stores/items.svelte';
import { viewStore } from '$lib/modules/inventar/stores/view.svelte'; import { viewStore } from '$lib/modules/inventory/stores/view.svelte';
import { import {
getCollectionById, getCollectionById,
getItemsByCollection, getItemsByCollection,
getSortedItems, getSortedItems,
} from '$lib/modules/inventar/queries'; } from '$lib/modules/inventory/queries';
import type { Collection, Item, ItemStatus } from '$lib/modules/inventar/queries'; import type { Collection, Item, ItemStatus } from '$lib/modules/inventory/queries';
import FieldRenderer from '$lib/modules/inventar/components/fields/FieldRenderer.svelte'; import FieldRenderer from '$lib/modules/inventory/components/fields/FieldRenderer.svelte';
import FieldEditor from '$lib/modules/inventar/components/fields/FieldEditor.svelte'; import FieldEditor from '$lib/modules/inventory/components/fields/FieldEditor.svelte';
import StatusBadge from '$lib/modules/inventar/components/StatusBadge.svelte'; import StatusBadge from '$lib/modules/inventory/components/StatusBadge.svelte';
import ViewModeToggle from '$lib/modules/inventar/components/ViewModeToggle.svelte'; import ViewModeToggle from '$lib/modules/inventory/components/ViewModeToggle.svelte';
const collectionsCtx: { readonly value: Collection[] } = getContext('collections'); const collectionsCtx: { readonly value: Collection[] } = getContext('collections');
const itemsCtx: { readonly value: Item[] } = getContext('items'); const itemsCtx: { readonly value: Item[] } = getContext('items');
@ -59,7 +59,7 @@
{#if !collection} {#if !collection}
<div class="text-center py-16"> <div class="text-center py-16">
<p class="text-[hsl(var(--muted-foreground))]">Sammlung nicht gefunden</p> <p class="text-[hsl(var(--muted-foreground))]">Sammlung nicht gefunden</p>
<a href="/inventar" class="mt-4 text-[hsl(var(--primary))]">Zuruck</a> <a href="/inventory" class="mt-4 text-[hsl(var(--primary))]">Zuruck</a>
</div> </div>
{:else} {:else}
<div class="space-y-6"> <div class="space-y-6">
@ -67,7 +67,7 @@
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<a <a
href="/inventar" href="/inventory"
class="text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]" class="text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]"
> >
<CaretLeft size={20} /> <CaretLeft size={20} />
@ -83,7 +83,7 @@
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<ViewModeToggle current={viewStore.viewMode} onchange={(m) => viewStore.setViewMode(m)} /> <ViewModeToggle current={viewStore.viewMode} onchange={(m) => viewStore.setViewMode(m)} />
<a <a
href="/inventar/collections/{collection.id}/edit" href="/inventory/collections/{collection.id}/edit"
class="rounded-lg border border-[hsl(var(--border))] p-2 text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]" class="rounded-lg border border-[hsl(var(--border))] p-2 text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]"
> >
<PencilSimple size={16} /> <PencilSimple size={16} />
@ -166,8 +166,8 @@
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{#each sortedItems as item (item.id)} {#each sortedItems as item (item.id)}
<div <div
onclick={() => goto(`/inventar/items/${item.id}`)} onclick={() => goto(`/inventory/items/${item.id}`)}
onkeydown={(e) => e.key === 'Enter' && goto(`/inventar/items/${item.id}`)} onkeydown={(e) => e.key === 'Enter' && goto(`/inventory/items/${item.id}`)}
role="button" role="button"
tabindex="0" tabindex="0"
class="group cursor-pointer rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] p-4 text-left" class="group cursor-pointer rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] p-4 text-left"
@ -221,7 +221,7 @@
{#each sortedItems as item (item.id)} {#each sortedItems as item (item.id)}
<tr <tr
class="cursor-pointer border-b border-[hsl(var(--border))] transition-colors hover:bg-[hsl(var(--accent)/0.05)]" class="cursor-pointer border-b border-[hsl(var(--border))] transition-colors hover:bg-[hsl(var(--accent)/0.05)]"
onclick={() => goto(`/inventar/items/${item.id}`)} onclick={() => goto(`/inventory/items/${item.id}`)}
> >
<td class="px-4 py-2.5 font-medium text-[hsl(var(--foreground))]">{item.name}</td> <td class="px-4 py-2.5 font-medium text-[hsl(var(--foreground))]">{item.name}</td>
<td class="px-4 py-2.5"><StatusBadge status={item.status} /></td> <td class="px-4 py-2.5"><StatusBadge status={item.status} /></td>
@ -248,8 +248,8 @@
<div class="space-y-2"> <div class="space-y-2">
{#each sortedItems as item (item.id)} {#each sortedItems as item (item.id)}
<div <div
onclick={() => goto(`/inventar/items/${item.id}`)} onclick={() => goto(`/inventory/items/${item.id}`)}
onkeydown={(e) => e.key === 'Enter' && goto(`/inventar/items/${item.id}`)} onkeydown={(e) => e.key === 'Enter' && goto(`/inventory/items/${item.id}`)}
role="button" role="button"
tabindex="0" tabindex="0"
class="group flex w-full cursor-pointer items-center gap-4 rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-4 py-3 text-left transition-colors hover:border-[hsl(var(--primary)/0.3)]" class="group flex w-full cursor-pointer items-center gap-4 rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-4 py-3 text-left transition-colors hover:border-[hsl(var(--primary)/0.3)]"

View file

@ -3,11 +3,11 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { CaretLeft } from '@mana/shared-icons'; import { CaretLeft } from '@mana/shared-icons';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { collectionsStore } from '$lib/modules/inventar/stores/collections.svelte'; import { collectionsStore } from '$lib/modules/inventory/stores/collections.svelte';
import { getCollectionById } from '$lib/modules/inventar/queries'; import { getCollectionById } from '$lib/modules/inventory/queries';
import type { Collection } from '$lib/modules/inventar/queries'; import type { Collection } from '$lib/modules/inventory/queries';
import type { CollectionSchema } from '$lib/modules/inventar/constants'; import type { CollectionSchema } from '$lib/modules/inventory/constants';
import SchemaEditor from '$lib/modules/inventar/components/fields/SchemaEditor.svelte'; import SchemaEditor from '$lib/modules/inventory/components/fields/SchemaEditor.svelte';
const collectionsCtx: { readonly value: Collection[] } = getContext('collections'); const collectionsCtx: { readonly value: Collection[] } = getContext('collections');
@ -38,7 +38,7 @@
icon: icon || undefined, icon: icon || undefined,
schema, schema,
}); });
goto(`/inventar/collections/${collection.id}`); goto(`/inventory/collections/${collection.id}`);
} }
const inputClass = const inputClass =
@ -55,7 +55,7 @@
<div class="mx-auto max-w-2xl space-y-6"> <div class="mx-auto max-w-2xl space-y-6">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<button <button
onclick={() => goto(`/inventar/collections/${collection.id}`)} onclick={() => goto(`/inventory/collections/${collection.id}`)}
class="text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]" class="text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]"
> >
<CaretLeft size={20} /> <CaretLeft size={20} />
@ -94,7 +94,7 @@
<div class="flex justify-end gap-3 pt-4"> <div class="flex justify-end gap-3 pt-4">
<button <button
onclick={() => goto(`/inventar/collections/${collection.id}`)} onclick={() => goto(`/inventory/collections/${collection.id}`)}
class="rounded-lg border border-[hsl(var(--border))] px-4 py-2 text-sm text-[hsl(var(--foreground))]" class="rounded-lg border border-[hsl(var(--border))] px-4 py-2 text-sm text-[hsl(var(--foreground))]"
> >
Abbrechen Abbrechen

View file

@ -1,13 +1,13 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { CaretLeft } from '@mana/shared-icons'; import { CaretLeft } from '@mana/shared-icons';
import { collectionsStore } from '$lib/modules/inventar/stores/collections.svelte'; import { collectionsStore } from '$lib/modules/inventory/stores/collections.svelte';
import { import {
DEFAULT_TEMPLATES, DEFAULT_TEMPLATES,
type Template, type Template,
type CollectionSchema, type CollectionSchema,
} from '$lib/modules/inventar/constants'; } from '$lib/modules/inventory/constants';
import SchemaEditor from '$lib/modules/inventar/components/fields/SchemaEditor.svelte'; import SchemaEditor from '$lib/modules/inventory/components/fields/SchemaEditor.svelte';
let step = $state<'template' | 'details'>('template'); let step = $state<'template' | 'details'>('template');
let selectedTemplate = $state<Template | null>(null); let selectedTemplate = $state<Template | null>(null);
@ -35,7 +35,7 @@
schema, schema,
templateId: selectedTemplate?.id, templateId: selectedTemplate?.id,
}); });
goto('/inventar'); goto('/inventory');
} }
const inputClass = const inputClass =
@ -50,7 +50,7 @@
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<button <button
onclick={() => onclick={() =>
step === 'details' && selectedTemplate ? (step = 'template') : goto('/inventar')} step === 'details' && selectedTemplate ? (step = 'template') : goto('/inventory')}
class="text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]" class="text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]"
> >
<CaretLeft size={20} /> <CaretLeft size={20} />
@ -127,7 +127,7 @@
<!-- Actions --> <!-- Actions -->
<div class="flex justify-end gap-3 pt-4"> <div class="flex justify-end gap-3 pt-4">
<button <button
onclick={() => goto('/inventar')} onclick={() => goto('/inventory')}
class="rounded-lg border border-[hsl(var(--border))] px-4 py-2 text-sm text-[hsl(var(--foreground))]" class="rounded-lg border border-[hsl(var(--border))] px-4 py-2 text-sm text-[hsl(var(--foreground))]"
> >
Abbrechen Abbrechen

View file

@ -4,24 +4,24 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { CaretLeft } from '@mana/shared-icons'; import { CaretLeft } from '@mana/shared-icons';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { itemsStore } from '$lib/modules/inventar/stores/items.svelte'; import { itemsStore } from '$lib/modules/inventory/stores/items.svelte';
import { import {
getItemById, getItemById,
getCollectionById, getCollectionById,
getLocationById, getLocationById,
getLocationFullPath, getLocationFullPath,
getCategoryById, getCategoryById,
} from '$lib/modules/inventar/queries'; } from '$lib/modules/inventory/queries';
import type { import type {
Collection, Collection,
Item, Item,
Location, Location,
Category, Category,
ItemStatus, ItemStatus,
} from '$lib/modules/inventar/queries'; } from '$lib/modules/inventory/queries';
import FieldRenderer from '$lib/modules/inventar/components/fields/FieldRenderer.svelte'; import FieldRenderer from '$lib/modules/inventory/components/fields/FieldRenderer.svelte';
import FieldEditor from '$lib/modules/inventar/components/fields/FieldEditor.svelte'; import FieldEditor from '$lib/modules/inventory/components/fields/FieldEditor.svelte';
import StatusBadge from '$lib/modules/inventar/components/StatusBadge.svelte'; import StatusBadge from '$lib/modules/inventory/components/StatusBadge.svelte';
const collectionsCtx: { readonly value: Collection[] } = getContext('collections'); const collectionsCtx: { readonly value: Collection[] } = getContext('collections');
const itemsCtx: { readonly value: Item[] } = getContext('items'); const itemsCtx: { readonly value: Item[] } = getContext('items');
@ -90,7 +90,7 @@
async function deleteItem() { async function deleteItem() {
if (!item || !confirm('Item endgultig loschen?')) return; if (!item || !confirm('Item endgultig loschen?')) return;
await itemsStore.delete(item.id); await itemsStore.delete(item.id);
goto(collection ? `/inventar/collections/${collection.id}` : '/inventar'); goto(collection ? `/inventory/collections/${collection.id}` : '/inventory');
} }
const inputClass = const inputClass =
@ -104,7 +104,7 @@
{#if !item} {#if !item}
<div class="text-center py-16"> <div class="text-center py-16">
<p class="text-[hsl(var(--muted-foreground))]">Item nicht gefunden</p> <p class="text-[hsl(var(--muted-foreground))]">Item nicht gefunden</p>
<a href="/inventar" class="mt-4 text-[hsl(var(--primary))]">Zuruck</a> <a href="/inventory" class="mt-4 text-[hsl(var(--primary))]">Zuruck</a>
</div> </div>
{:else} {:else}
<div class="mx-auto max-w-2xl space-y-6"> <div class="mx-auto max-w-2xl space-y-6">
@ -112,7 +112,8 @@
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<button <button
onclick={() => goto(collection ? `/inventar/collections/${collection.id}` : '/inventar')} onclick={() =>
goto(collection ? `/inventory/collections/${collection.id}` : '/inventory')}
class="text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]" class="text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]"
> >
<CaretLeft size={20} /> <CaretLeft size={20} />

View file

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { locationsStore } from '$lib/modules/inventar/stores/locations.svelte'; import { locationsStore } from '$lib/modules/inventory/stores/locations.svelte';
import { getLocationTree } from '$lib/modules/inventar/queries'; import { getLocationTree } from '$lib/modules/inventory/queries';
import type { Location } from '$lib/modules/inventar/queries'; import type { Location } from '$lib/modules/inventory/queries';
import { Plus } from '@mana/shared-icons'; import { Plus } from '@mana/shared-icons';
const locationsCtx: { readonly value: Location[] } = getContext('locations'); const locationsCtx: { readonly value: Location[] } = getContext('locations');

View file

@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { getFilteredItems, getCollectionById } from '$lib/modules/inventar/queries'; import { getFilteredItems, getCollectionById } from '$lib/modules/inventory/queries';
import type { Collection, Item } from '$lib/modules/inventar/queries'; import type { Collection, Item } from '$lib/modules/inventory/queries';
import StatusBadge from '$lib/modules/inventar/components/StatusBadge.svelte'; import StatusBadge from '$lib/modules/inventory/components/StatusBadge.svelte';
import { MagnifyingGlass } from '@mana/shared-icons'; import { MagnifyingGlass } from '@mana/shared-icons';
const collectionsCtx: { readonly value: Collection[] } = getContext('collections'); const collectionsCtx: { readonly value: Collection[] } = getContext('collections');
@ -41,7 +41,7 @@
<div class="space-y-2"> <div class="space-y-2">
{#each results as item (item.id)} {#each results as item (item.id)}
<button <button
onclick={() => goto(`/inventar/items/${item.id}`)} onclick={() => goto(`/inventory/items/${item.id}`)}
class="flex w-full items-center gap-4 rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-4 py-3 text-left transition-colors hover:border-[hsl(var(--primary)/0.3)]" class="flex w-full items-center gap-4 rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-4 py-3 text-left transition-colors hover:border-[hsl(var(--primary)/0.3)]"
> >
<div class="flex-1"> <div class="flex-1">

View file

@ -810,7 +810,7 @@ services:
# REMOVED standalone web containers — now served by unified mana-web container (mana.how): # REMOVED standalone web containers — now served by unified mana-web container (mana.how):
# chat-web, todo-web, zitare-web, calendar-web, clock-web, contacts-web, # chat-web, todo-web, zitare-web, calendar-web, clock-web, contacts-web,
# storage-web, presi-web, cards-web, nutriphi-web, skilltree-web, photos-web, # storage-web, presi-web, cards-web, nutriphi-web, skilltree-web, photos-web,
# music-web, citycorners-web, picture-web, inventar-web, calc-web, times-web, # music-web, citycorners-web, picture-web, inventory-web, calc-web, times-web,
# uload-web, memoro-web # uload-web, memoro-web
# picture-backend: REMOVED — replaced by Hono server (apps/picture/apps/server) # picture-backend: REMOVED — replaced by Hono server (apps/picture/apps/server)

View file

@ -242,7 +242,7 @@ scrape_configs:
- https://mana.how/notes - https://mana.how/notes
- https://mana.how/habits - https://mana.how/habits
- https://mana.how/guides - https://mana.how/guides
- https://mana.how/inventar - https://mana.how/inventory
- https://mana.how/playground - https://mana.how/playground
# Standalone games (separate containers) # Standalone games (separate containers)
- https://whopxl.mana.how - https://whopxl.mana.how

View file

@ -133,7 +133,7 @@ Pure CRUD apps use mana-sync directly.
| 5022 | citycorners-web | *(local-first only)* | | 5022 | citycorners-web | *(local-first only)* |
| 5023 | cards-web | 3036 cards-server | | 5023 | cards-web | 3036 cards-server |
| 5024 | mukke-web | 3037 mukke-server | | 5024 | mukke-web | 3037 mukke-server |
| 5025 | inventar-web | *(local-first only)* | | 5025 | inventory-web | *(local-first only)* |
| 5026 | context-web | *(local-first only)* | | 5026 | context-web | *(local-first only)* |
| 5027 | questions-web | *(local-first only)* | | 5027 | questions-web | *(local-first only)* |
| 5028 | planta-web | 3039 planta-server | | 5028 | planta-web | 3039 planta-server |

View file

@ -26,7 +26,7 @@ const apps = [
{ name: 'music', url: `${BASE}:5189` }, { name: 'music', url: `${BASE}:5189` },
{ name: 'citycorners', url: `${BASE}:5190` }, { name: 'citycorners', url: `${BASE}:5190` },
{ name: 'picture', url: `${BASE}:5174` }, { name: 'picture', url: `${BASE}:5174` },
{ name: 'inventar', url: `${BASE}:5191` }, { name: 'inventory', url: `${BASE}:5191` },
]; ];
// When testing against production, use subdomains // When testing against production, use subdomains

View file

@ -108,8 +108,8 @@
"dev:photos:app": "pnpm dev:photos:web", "dev:photos:app": "pnpm dev:photos:web",
"dev:photos:full": "concurrently -n auth,sync,web -c blue,magenta,cyan \"pnpm dev:auth\" \"pnpm dev:sync\" \"pnpm dev:photos:web\"", "dev:photos:full": "concurrently -n auth,sync,web -c blue,magenta,cyan \"pnpm dev:auth\" \"pnpm dev:sync\" \"pnpm dev:photos:web\"",
"dev:tags-test": "concurrently -n auth,sync,api -c blue,magenta,yellow \"pnpm dev:auth\" \"pnpm dev:sync\" \"pnpm dev:api\"", "dev:tags-test": "concurrently -n auth,sync,api -c blue,magenta,yellow \"pnpm dev:auth\" \"pnpm dev:sync\" \"pnpm dev:api\"",
"inventar:dev": "turbo run dev --filter=inventar...", "inventory:dev": "turbo run dev --filter=inventory...",
"dev:inventar:web": "pnpm --filter @inventar/web dev", "dev:inventory:web": "pnpm --filter @inventory/web dev",
"times:dev": "turbo run dev --filter=times...", "times:dev": "turbo run dev --filter=times...",
"dev:times:web": "pnpm --filter @times/web dev", "dev:times:web": "pnpm --filter @times/web dev",
"dev:times:full": "concurrently -n auth,sync,web -c blue,magenta,cyan \"pnpm dev:auth\" \"pnpm dev:sync\" \"pnpm dev:times:web\"", "dev:times:full": "concurrently -n auth,sync,web -c blue,magenta,cyan \"pnpm dev:auth\" \"pnpm dev:sync\" \"pnpm dev:times:web\"",
@ -246,7 +246,7 @@
"dev:skilltree:local": "concurrently -n sync,web -c magenta,cyan \"pnpm dev:sync\" \"pnpm dev:skilltree:web\"", "dev:skilltree:local": "concurrently -n sync,web -c magenta,cyan \"pnpm dev:sync\" \"pnpm dev:skilltree:web\"",
"dev:photos:local": "concurrently -n sync,web -c magenta,cyan \"pnpm dev:sync\" \"pnpm dev:photos:web\"", "dev:photos:local": "concurrently -n sync,web -c magenta,cyan \"pnpm dev:sync\" \"pnpm dev:photos:web\"",
"dev:citycorners:local": "concurrently -n sync,web -c magenta,cyan \"pnpm dev:sync\" \"pnpm dev:citycorners:web\"", "dev:citycorners:local": "concurrently -n sync,web -c magenta,cyan \"pnpm dev:sync\" \"pnpm dev:citycorners:web\"",
"dev:inventar:local": "concurrently -n sync,web -c magenta,cyan \"pnpm dev:sync\" \"pnpm dev:inventar:web\"", "dev:inventory:local": "concurrently -n sync,web -c magenta,cyan \"pnpm dev:sync\" \"pnpm dev:inventory:web\"",
"dev:times:local": "concurrently -n sync,web -c magenta,cyan \"pnpm dev:sync\" \"pnpm dev:times:web\"", "dev:times:local": "concurrently -n sync,web -c magenta,cyan \"pnpm dev:sync\" \"pnpm dev:times:web\"",
"dev:calc:local": "concurrently -n sync,web -c magenta,cyan \"pnpm dev:sync\" \"pnpm dev:calc:web\"", "dev:calc:local": "concurrently -n sync,web -c magenta,cyan \"pnpm dev:sync\" \"pnpm dev:calc:web\"",
"dev:manavoxel:local": "concurrently -n sync,web -c magenta,cyan \"pnpm dev:sync\" \"pnpm dev:manavoxel:web\"", "dev:manavoxel:local": "concurrently -n sync,web -c magenta,cyan \"pnpm dev:sync\" \"pnpm dev:manavoxel:web\"",

View file

@ -61,7 +61,7 @@
music: ['Musik machen, einfach so', 'Quelloffen & unabhängig', 'Privat by Design'], music: ['Musik machen, einfach so', 'Quelloffen & unabhängig', 'Privat by Design'],
storage: ['Deine Dateien, dein Tresor', 'Quelloffen & unabhängig', 'Privat by Design'], storage: ['Deine Dateien, dein Tresor', 'Quelloffen & unabhängig', 'Privat by Design'],
times: ['Zeiterfassung ohne Overhead', 'Quelloffen & unabhängig', 'Privat by Design'], times: ['Zeiterfassung ohne Overhead', 'Quelloffen & unabhängig', 'Privat by Design'],
inventar: ['Alles im Überblick behalten', 'Quelloffen & unabhängig', 'Privat by Design'], inventory: ['Alles im Überblick behalten', 'Quelloffen & unabhängig', 'Privat by Design'],
uload: ['Links kürzen & verwalten', 'Quelloffen & unabhängig', 'Privat by Design'], uload: ['Links kürzen & verwalten', 'Quelloffen & unabhängig', 'Privat by Design'],
news: ['Nachrichten, kuratiert für dich', 'Quelloffen & unabhängig', 'Privat by Design'], news: ['Nachrichten, kuratiert für dich', 'Quelloffen & unabhängig', 'Privat by Design'],
arcade: ['Spiele direkt im Browser', 'Quelloffen & unabhängig', 'Privat by Design'], arcade: ['Spiele direkt im Browser', 'Quelloffen & unabhängig', 'Privat by Design'],
@ -94,7 +94,7 @@
music: ['Make music, just like that', 'Open-source & independent', 'Private by design'], music: ['Make music, just like that', 'Open-source & independent', 'Private by design'],
storage: ['Your files, your vault', 'Open-source & independent', 'Private by design'], storage: ['Your files, your vault', 'Open-source & independent', 'Private by design'],
times: ['Time tracking without overhead', 'Open-source & independent', 'Private by design'], times: ['Time tracking without overhead', 'Open-source & independent', 'Private by design'],
inventar: ['Keep track of everything', 'Open-source & independent', 'Private by design'], inventory: ['Keep track of everything', 'Open-source & independent', 'Private by design'],
uload: ['Shorten & manage links', 'Open-source & independent', 'Private by design'], uload: ['Shorten & manage links', 'Open-source & independent', 'Private by design'],
news: ['News, curated for you', 'Open-source & independent', 'Private by design'], news: ['News, curated for you', 'Open-source & independent', 'Private by design'],
arcade: ['Games right in your browser', 'Open-source & independent', 'Private by design'], arcade: ['Games right in your browser', 'Open-source & independent', 'Private by design'],

View file

@ -17,7 +17,7 @@
* } * }
* *
* export const viewStore = createViewStore<MyViewMode, MyFilters>({ * export const viewStore = createViewStore<MyViewMode, MyFilters>({
* storagePrefix: 'inventar', * storagePrefix: 'inventory',
* defaultViewMode: 'list', * defaultViewMode: 'list',
* defaultSort: { field: 'name', direction: 'asc' }, * defaultSort: { field: 'name', direction: 'asc' },
* hasActiveFilters: (f) => !!(f.search || f.status?.length || f.tagIds?.length), * hasActiveFilters: (f) => !!(f.search || f.status?.length || f.tagIds?.length),
@ -40,7 +40,7 @@ export interface SavedFilter<F> {
} }
export interface ViewStoreConfig<V extends string, F extends object> { export interface ViewStoreConfig<V extends string, F extends object> {
/** Prefix for localStorage keys (e.g. 'inventar' → 'inventar_view_mode') */ /** Prefix for localStorage keys (e.g. 'inventory' → 'inventory_view_mode') */
storagePrefix: string; storagePrefix: string;
/** Default view mode */ /** Default view mode */
defaultViewMode: V; defaultViewMode: V;

View file

@ -167,7 +167,7 @@ const track = {
memoro: createModuleTracker('memoro'), memoro: createModuleTracker('memoro'),
app: createModuleTracker('app'), app: createModuleTracker('app'),
calc: createModuleTracker('calc'), calc: createModuleTracker('calc'),
inventar: createModuleTracker('inventar'), inventory: createModuleTracker('inventory'),
moodlit: createModuleTracker('moodlit'), moodlit: createModuleTracker('moodlit'),
citycorners: createModuleTracker('citycorners'), citycorners: createModuleTracker('citycorners'),
}; };
@ -570,18 +570,18 @@ export const CalcEvents = {
}; };
/** /**
* Inventar App Events * Inventory App Events
*/ */
export const InventarEvents = { export const InventoryEvents = {
itemCreated: () => track.inventar('item_created'), itemCreated: () => track.inventory('item_created'),
itemUpdated: () => track.inventar('item_updated'), itemUpdated: () => track.inventory('item_updated'),
itemDeleted: () => track.inventar('item_deleted'), itemDeleted: () => track.inventory('item_deleted'),
collectionCreated: () => track.inventar('collection_created'), collectionCreated: () => track.inventory('collection_created'),
collectionDeleted: () => track.inventar('collection_deleted'), collectionDeleted: () => track.inventory('collection_deleted'),
categoryCreated: () => track.inventar('category_created'), categoryCreated: () => track.inventory('category_created'),
categoryDeleted: () => track.inventar('category_deleted'), categoryDeleted: () => track.inventory('category_deleted'),
locationCreated: () => track.inventar('location_created'), locationCreated: () => track.inventory('location_created'),
locationDeleted: () => track.inventar('location_deleted'), locationDeleted: () => track.inventory('location_deleted'),
}; };
/** /**

View file

@ -190,7 +190,7 @@ export const MANA_APP_INDEX: Record<string, number> = {
photos: 12, photos: 12,
skilltree: 13, skilltree: 13,
citycorners: 14, citycorners: 14,
inventar: 15, inventory: 15,
times: 16, times: 16,
nutriphi: 17, nutriphi: 17,
planta: 18, planta: 18,

View file

@ -199,7 +199,6 @@ get_tier_badge() {
case "$appid" in case "$appid" in
mana.how) appid="mana" ;; mana.how) appid="mana" ;;
manadeck) appid="cards" ;; manadeck) appid="cards" ;;
inventar) appid="inventory" ;;
esac esac
echo "$TIER_APPS" | while IFS='|' read -r id name tier st; do echo "$TIER_APPS" | while IFS='|' read -r id name tier st; do

View file

@ -120,7 +120,7 @@ const PROJECT_META: Record<string, { name: string; icon: string }> = {
picture: { name: 'ManaPicture', icon: '🎨' }, picture: { name: 'ManaPicture', icon: '🎨' },
zitare: { name: 'Zitare', icon: '✨' }, zitare: { name: 'Zitare', icon: '✨' },
presi: { name: 'Presi', icon: '📊' }, presi: { name: 'Presi', icon: '📊' },
inventar: { name: 'Inventar', icon: '📦' }, inventory: { name: 'Inventar', icon: '📦' },
nutriphi: { name: 'Nutriphi', icon: '🥗' }, nutriphi: { name: 'Nutriphi', icon: '🥗' },
planta: { name: 'Planta', icon: '🌱' }, planta: { name: 'Planta', icon: '🌱' },
storage: { name: 'Storage', icon: '☁️' }, storage: { name: 'Storage', icon: '☁️' },