mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 22:21:10 +02:00
docs(devlog): backfill daily devlogs for 2026-04-01 through 2026-04-07
Fills the gap between the last entry (2026-03-31) and today. The 2026-04-06 slot is intentionally skipped — no commits that day. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0119e48edb
commit
4f6609a595
6 changed files with 1801 additions and 0 deletions
|
|
@ -0,0 +1,311 @@
|
|||
---
|
||||
title: 'ManaCore Unified App: Phasen 1–7 + Memoro Production-Ready'
|
||||
description: 'Start der Same-Origin Unified App — alle 26 Module migriert in 7 Phasen, cross-app DnD, Spotlight, Sync-Manager. Memoro auf Production gehoben (ManaScore 58 → 79).'
|
||||
date: 2026-04-01
|
||||
author: 'Till Schneider'
|
||||
category: 'feature'
|
||||
tags:
|
||||
[
|
||||
'manacore',
|
||||
'unified-app',
|
||||
'migration',
|
||||
'monorepo',
|
||||
'memoro',
|
||||
'spotlight',
|
||||
'dnd',
|
||||
'sync',
|
||||
'todo',
|
||||
'i18n',
|
||||
'rate-limiting',
|
||||
]
|
||||
featured: true
|
||||
commits: 81
|
||||
readTime: 13
|
||||
stats:
|
||||
filesChanged: 1485
|
||||
linesAdded: 81703
|
||||
linesRemoved: 45418
|
||||
contributors:
|
||||
- name: 'Till Schneider'
|
||||
handle: 'Till-JS'
|
||||
commits: 81
|
||||
workingHours:
|
||||
start: '2026-04-01T10:55'
|
||||
end: '2026-04-01T23:05'
|
||||
---
|
||||
|
||||
## Highlights
|
||||
|
||||
- **ManaCore Unified App gestartet** — Same-Origin Web-App, die alle 26 Module unter einem Build/Domain bündelt. 7 Phasen in einem Tag.
|
||||
- **Cross-Type Drag & Drop** — Tags, Tasks, Events, Karten ziehen sich modulübergreifend
|
||||
- **Cmd+K Spotlight** in allen 23 Apps — mit Content-Search-Providern
|
||||
- **Memoro Production-Ready**: ManaScore 58 → 79 (Beta → Production), Audio-Server mit 4-Tier-Fallback live
|
||||
- **`@manacore/shared-uload`** — neue Share-Modal-Library, in 6 Apps integriert
|
||||
- **Rate-Limiting** als shared-hono Middleware
|
||||
|
||||
---
|
||||
|
||||
## ManaCore: Same-Origin Unified App (Phasen 1–7)
|
||||
|
||||
Bis heute war jedes der ~26 Mana-Module eine eigene SvelteKit-App mit eigener Domain (`todo.mana.how`, `chat.mana.how`, …) und eigener IndexedDB. Cross-App-Features (Tag-DnD, Spotlight-Search, Dashboard-Widgets) waren entweder Fake oder unmöglich, weil Same-Origin-Policy den IndexedDB-Zugriff über Subdomains blockt.
|
||||
|
||||
Der Plan war seit Wochen klar — heute wurde er ausgeführt, in einer langen Session.
|
||||
|
||||
### Phasen-Übersicht
|
||||
|
||||
| Phase | Was passiert | Module |
|
||||
|-------|-------------|--------|
|
||||
| **1** | Schema + erstes Modul (`calc`) | 1 |
|
||||
| **2** | Migration in Wellen | 26 ✅ |
|
||||
| **3** | Component-basiertes Split-Screen | – |
|
||||
| **4** | Cross-App Dashboard-Widgets | – |
|
||||
| **5** | Single-Container Infra | – |
|
||||
| **6** | URL/Navigation Update | – |
|
||||
| **7** | Unified Sync-Manager | – |
|
||||
|
||||
### Phase 2: 26 Module in drei Wellen
|
||||
|
||||
```
|
||||
Welle 1 (12 Module): skilltree, inventar, times, planta,
|
||||
citycorners, photos, presi, uload,
|
||||
context, questions, nutriphi, calc
|
||||
|
||||
Welle 2 (5 Module): storage, cards, playground, guides,
|
||||
+ restliche Helper
|
||||
|
||||
Welle 3 (7 Module): contacts, todo, calendar, picture,
|
||||
chat, mukke, memoro
|
||||
```
|
||||
|
||||
Jedes Modul wandert nach `apps/manacore/apps/web/src/lib/modules/{name}/`. Eigene Routes werden auf `(app)/{name}` umgehängt, Stores teilen sich die zentrale `mana` Dexie-DB. Kollidierende Tabellennamen bekommen einen Modul-Prefix.
|
||||
|
||||
### Phase 3: Component-basiertes Split-Screen
|
||||
|
||||
Statt mehrerer Routen für 1-Pane / 2-Pane / 3-Pane gibt es jetzt einen `<SplitScreen>` Slot-Container, der Pages dynamisch nebeneinander rendert. Jede Page kann mit `pageStore.add()` aus jedem Modul heraus geöffnet werden — ohne Navigation.
|
||||
|
||||
### Phase 4: Cross-App Dashboard-Widgets
|
||||
|
||||
Das Dashboard wird endlich ehrlich: Widgets aus Calendar, Todo, Memoro, Picture, Notes lesen jetzt aus derselben IndexedDB. Vorher hatte jedes Widget einen Stub. Jetzt: echte Daten, live, mit `liveQuery`.
|
||||
|
||||
### Phase 5: Single-Container Infra
|
||||
|
||||
Statt 26 Docker-Containern (einer pro App) läuft alles in einem `manacore-web` Container. Der NGINX-Routing-Layer fällt komplett weg. Massiv weniger RAM, weniger Cold-Starts, ein Build statt 26.
|
||||
|
||||
### Phase 6: URL-Migration
|
||||
|
||||
Alle internen Links werden umgeschrieben: `https://todo.mana.how/page` → `https://mana.how/todo/page`. Cross-App-Links funktionieren jetzt als normale `<a>`-Tags ohne Cross-Origin-Tanz.
|
||||
|
||||
### Phase 7: Unified Sync-Manager
|
||||
|
||||
Vorher: jede App hatte ihre eigene mana-sync-Verbindung. Jetzt: ein zentraler Sync-Manager debounced + batched alle Änderungen aus den 26 Modulen, taggt sie mit `appId`, und schickt sie in einem Stream zu mana-sync.
|
||||
|
||||
---
|
||||
|
||||
## Cross-Type Drag & Drop
|
||||
|
||||
Mit der Unified App wird endlich möglich, was vorher technisch ausgeschlossen war: **modulübergreifendes Drag & Drop**.
|
||||
|
||||
```
|
||||
Drag a Tag from Notes →
|
||||
Drop on a Task → Tag wird zur Task hinzugefügt
|
||||
Drop on a Calendar Event → Tag wird zum Event hinzugefügt
|
||||
Drop on a Contact → Tag wird zum Kontakt hinzugefügt
|
||||
```
|
||||
|
||||
Implementierung:
|
||||
- `shared-ui` exportiert `DragSource` / `DropTarget` Snippets
|
||||
- Generischer `entityDragStore` hält das aktuelle Drag-Item modulneutral
|
||||
- Drop-Targets registrieren sich mit `acceptTypes: ['tag', 'task', 'event', …]`
|
||||
- Tag-Enrichment passiert beim Drop, nicht beim Drag
|
||||
|
||||
Das System ist erweiterbar — jedes Modul kann sich als Source/Target registrieren.
|
||||
|
||||
---
|
||||
|
||||
## Cmd+K Spotlight in 23 Apps
|
||||
|
||||
Spotlight existierte bisher nur in todo + calendar. Heute in alle 23 Apps ausgerollt — mit zwei Erweiterungen:
|
||||
|
||||
### Action-Provider
|
||||
|
||||
Jede App registriert ihre eigenen Aktionen:
|
||||
|
||||
```typescript
|
||||
spotlight.registerActions('todo', [
|
||||
{ id: 'new-task', label: 'Neue Aufgabe', icon: 'plus', run: () => ... },
|
||||
{ id: 'today', label: 'Heute fokussieren', run: () => ... },
|
||||
]);
|
||||
```
|
||||
|
||||
### Content-Search-Provider
|
||||
|
||||
Spotlight kann jetzt **inhaltlich** suchen — über Provider, die IndexedDB-Tabellen abfragen. Erste Provider live für **picture, presi, mukke, zitare, clock**.
|
||||
|
||||
```typescript
|
||||
spotlight.registerSearchProvider('picture', async (query) => {
|
||||
return await db.images
|
||||
.filter(img => img.title?.includes(query))
|
||||
.limit(5)
|
||||
.toArray();
|
||||
});
|
||||
```
|
||||
|
||||
Resultate werden inline gerendert — Klick öffnet das Item direkt im jeweiligen Modul.
|
||||
|
||||
---
|
||||
|
||||
## `@manacore/shared-uload`: Share-Modal als Library
|
||||
|
||||
Bisher hatte jede App ihre eigene Share-Logik. Jetzt: ein einziges Package mit `<ShareModal>` und `createShareLink()` Helpers.
|
||||
|
||||
### Features
|
||||
- **Password Protection** (optional)
|
||||
- **Expiration Date**
|
||||
- **Source-Tracking** — uload weiß welche App den Link erstellt hat (für Filter/Statistik)
|
||||
- **Cross-App Link Creation** — `useShare()` Composable in jedem Modul
|
||||
|
||||
### Integration in 6 Apps am ersten Tag
|
||||
|
||||
| App | Was geteilt wird |
|
||||
|-----|-----------------|
|
||||
| Mukke | Playlists |
|
||||
| Presi | Decks |
|
||||
| Todo | Tasks (mit Subtasks) |
|
||||
| Cards | Decks |
|
||||
| Chat | Konversationen |
|
||||
| Calendar | Events |
|
||||
| Contacts | Visitenkarten |
|
||||
|
||||
uload bekommt einen neuen `source`-Filter — man sieht direkt welche Links aus welcher App kommen.
|
||||
|
||||
---
|
||||
|
||||
## Memoro: Production-Ready (ManaScore 58 → 79)
|
||||
|
||||
Gestern wurde Memoro ins Monorepo gehoben. Heute der Härtetest: Audit-Punkte abarbeiten bis es Production-Ready ist.
|
||||
|
||||
### Neu in `apps/memoro/apps/server`
|
||||
|
||||
- **Zod-Validation** auf allen Endpoints, mit konsistenten `ApiResult<T>` Responses
|
||||
- **Pagination** auf List-Routen (`?limit=…&cursor=…`)
|
||||
- **Invite-E-Mail** über mana-notify (templated, mit Cooldown)
|
||||
- **Health-Checks** für Liveness/Readiness
|
||||
- **Meetings-Modul** komplett portiert (Phase 7 der Memoro-Migration)
|
||||
- **OpenAPI 3.1 Spec** als referenzierbares Schema
|
||||
- **Vitest:** 25 API + Config Tests für Audio-Server, Zod-Schema-Tests, Route-Tests
|
||||
|
||||
### Audio-Server: 4-Tier Fallback live
|
||||
|
||||
Der gestrige Plan ist heute live:
|
||||
|
||||
```
|
||||
Tier 1: Primärer Azure-Key
|
||||
Tier 2: Retry mit gleichem Key
|
||||
Tier 3: FFmpeg-Konvertierung (PCM 16kHz Mono)
|
||||
Tier 4: Azure Batch (für > 10min Aufnahmen)
|
||||
```
|
||||
|
||||
Plus **AI-Provider-Fallbacks**: Wenn OpenAI für die Headline-Generierung fällt, fallen wir auf Anthropic, dann auf lokales Ollama.
|
||||
|
||||
### MemoroEvents Analytics
|
||||
|
||||
Eigene Event-Klassen in `@manacore/shared-utils` — `memoro.recording.start`, `memoro.recording.complete`, `memoro.transcription.fallback_used`. Geht direkt nach Umami und GlitchTip.
|
||||
|
||||
### Production-Deployment
|
||||
|
||||
Eigenes `Dockerfile` + `docker-compose.yml` für Memoro Server + Audio-Server. Beide laufen jetzt im Mac-Mini-Stack neben den anderen Services.
|
||||
|
||||
### Audit-Report
|
||||
|
||||
Ein neues Dokument `docs/manascore/memoro-audit.md` zeigt die Punkte-für-Punkte-Bewertung. Der ManaScore-Status wird auf der Status-Page automatisch aktualisiert: **Memoro: 79/100 — Beta → Production**.
|
||||
|
||||
---
|
||||
|
||||
## Todo: Workbench-Polish + Custom Pages
|
||||
|
||||
### Page-Controls
|
||||
|
||||
Jede Todo-Page hat jetzt **Maximize / Minimize / Close** Controls oben rechts. Minimierte Pages erscheinen als Tab-Leiste am unteren Rand der App.
|
||||
|
||||
### Inline-Edit + Drag-Reorder
|
||||
|
||||
Der frühere "Edit-Modus" für Pages ist gestrichen. Stattdessen:
|
||||
- **Click auf Titel:** Sofort-Edit (kein Modal mehr)
|
||||
- **Drag-Handle:** Reorder der Page-Reihenfolge
|
||||
- **Inline-Task-Creation:** Neue Tasks ohne Page-Wechsel
|
||||
|
||||
### Funktionierendes Tag-Filter-System
|
||||
|
||||
Das alte Tag-Filter war ein Stub. Komplett ersetzt durch echtes Filtering, das die `taskTags` Junction-Table nutzt.
|
||||
|
||||
### Bottom-Stack Notification System
|
||||
|
||||
Neuer `BottomStack` Container in shared-ui — staggered Notifications die sich über Pillnav schieben (nicht mehr darunter). Plus `bottomOffset` Prop für PillNav, damit die Toasts nicht mit Tab-Bar kollidieren.
|
||||
|
||||
---
|
||||
|
||||
## Status-Page: Tier-Badges + ManaCore selbst
|
||||
|
||||
- **Tier-Badges inline** statt in eigener Sektion — kompakter, lesbarer
|
||||
- **ManaCore zur App-Registry hinzugefügt** und auf der Status-Page sichtbar
|
||||
- **`mana.how` Badge gefixt** — wurde fälschlicherweise als "Down" angezeigt obwohl der Healthcheck OK war
|
||||
|
||||
---
|
||||
|
||||
## Refactor & Renames
|
||||
|
||||
### Cards (vorher: ManaDeck)
|
||||
|
||||
Globaler Rename `ManaDeck` → `Cards` durch das ganze Monorepo. Cleaner Namespace, bessere SEO, weniger Verwirrung mit "Decks" innerhalb der App.
|
||||
|
||||
### shared-auth-ui
|
||||
|
||||
Neues Package mit `GuestRegistrationNudge` — der Banner der Guests dazu animiert sich zu registrieren, ohne nervig zu sein. Plus `GuestWelcomeModal` Redesign.
|
||||
|
||||
### Footer-Polish
|
||||
|
||||
ManaCore Landing Footer komplett überarbeitet — bessere Lesbarkeit, sync mit tatsächlichem Production-Deployment-Status.
|
||||
|
||||
---
|
||||
|
||||
## Infra & Fixes
|
||||
|
||||
| Fix | Beschreibung |
|
||||
|-----|-------------|
|
||||
| Docker-Build manacore-web | Fehlende Deps (dexie, app-spezifische Packages), Sync-URL Build-Arg |
|
||||
| Toast-System | `svelte-sonner` durch lokalen Toast-Store ersetzt (kleinerer Bundle) |
|
||||
| Memoro `$user` → `authStore.user` | Svelte 5 runes Konvertierung |
|
||||
| mana-stt | WhisperX + Diarisierung integriert |
|
||||
| mana-notify | Neue Templates für Memoro-Invites |
|
||||
| CD Pipeline | Schritte für unified manacore-web Container |
|
||||
|
||||
---
|
||||
|
||||
## Dokumentation
|
||||
|
||||
- `docs/UNIFIED_APP_MIGRATION.md` — Plan + Status aller 7 Phasen, jetzt vollständig abgehakt
|
||||
- `docs/manascore/memoro-audit.md` — Memoro-Audit-Report
|
||||
- Memoro `OpenAPI 3.1` Spec
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
| Bereich | Commits | Highlights |
|
||||
|---------|---------|-----------|
|
||||
| ManaCore Unified | ~30 | Phasen 1–7, 26 Module migriert, Single-Container, Sync-Manager |
|
||||
| Cross-App Features | ~10 | DnD, Spotlight, Content-Search, Dashboard-Widgets |
|
||||
| Memoro Production | ~15 | ManaScore 58→79, Tests, Audio-Server, OpenAPI |
|
||||
| shared-uload | ~6 | ShareModal in 6 Apps, Source-Tracking |
|
||||
| Todo Polish | ~10 | Custom Pages, Page-Controls, Tag-Filter, Inline-Edit |
|
||||
| Status-Page / Branding | ~5 | Tier-Badges inline, ManaDeck → Cards |
|
||||
| Infra & Fixes | ~5 | Docker-Builds, mana-stt WhisperX, CD updates |
|
||||
|
||||
---
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
- Weitere Module ins Unified-App-Pattern: alle bisher übersehenen UI-Details abklopfen
|
||||
- Standalone-Server der einzelnen Apps archivieren — der unified API-Server kommt morgen
|
||||
- Cross-Module-DnD ausbauen: Drop von Todos in Calendar als Time-Block
|
||||
- Content-Search-Provider auch für die noch fehlenden Module (todo, contacts, calendar)
|
||||
|
|
@ -0,0 +1,390 @@
|
|||
---
|
||||
title: 'Unified API Server + 25 Apps archiviert + SSE Sync'
|
||||
description: '17 Module-Server zu @manacore/api konsolidiert, 25 standalone Web-Apps archiviert, WebSocket-Sync durch SSE ersetzt, Partial-Sync mit lazy collection loading. Plus Tag-System, Detail-View Overlays und i18n.'
|
||||
date: 2026-04-02
|
||||
author: 'Till Schneider'
|
||||
category: 'feature'
|
||||
tags:
|
||||
[
|
||||
'manacore',
|
||||
'unified-api',
|
||||
'hono',
|
||||
'archive',
|
||||
'sse',
|
||||
'sync',
|
||||
'tags',
|
||||
'i18n',
|
||||
'analytics',
|
||||
'workbench',
|
||||
]
|
||||
featured: true
|
||||
commits: 107
|
||||
readTime: 15
|
||||
stats:
|
||||
filesChanged: 3659
|
||||
linesAdded: 70140
|
||||
linesRemoved: 68673
|
||||
contributors:
|
||||
- name: 'Till Schneider'
|
||||
handle: 'Till-JS'
|
||||
commits: 107
|
||||
workingHours:
|
||||
start: '2026-04-02T01:17'
|
||||
end: '2026-04-02T23:59'
|
||||
---
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Unified API Server** (`@manacore/api`): 17 Module-Server zusammengeführt, 25 standalone Web-Apps archiviert
|
||||
- **WebSocket → SSE**: ein einziger Stream pro User statt 27 Verbindungen
|
||||
- **Partial Sync**: Collections werden lazy beim ersten Modul-Visit geladen
|
||||
- **Detail-View Overlays** in 14 Modulen — Stack-fähig, mit inline-editing
|
||||
- **Unified QuickInputBar** mit context-aware Adapters pro Modul
|
||||
- **i18n** in 5 Phasen — 126 deutsche Strings extrahiert, Locale-Files konsolidiert
|
||||
- **2 DBs statt 20+**: `mana_platform` + `mana_sync` mit pgSchema-Isolation
|
||||
- **Shared Tag-System** über alle 23 Module mit Junction-Tables
|
||||
|
||||
---
|
||||
|
||||
## Unified API Server: 17 Hono-Server zu einem
|
||||
|
||||
Gestern wurden die _Frontends_ konsolidiert. Heute folgten die _Backends_.
|
||||
|
||||
Bisher hatte jedes Modul mit Server-Logik einen eigenen Hono-Container (calendar-server, contacts-server, todo-server, …). Jeder mit eigener Auth, eigener Health-Route, eigener Drizzle-Connection. Massiv viel Duplikation, massiv viel RAM, 17 Pipelines.
|
||||
|
||||
### Neuer `@manacore/api` Server
|
||||
|
||||
Ein einziger Hono/Bun-Server unter `apps/api/`. Module registrieren ihre Routen unter `/api/v1/{module}/*`:
|
||||
|
||||
```
|
||||
apps/api/
|
||||
├── src/
|
||||
│ ├── modules/
|
||||
│ │ ├── calendar/ # Routes + Service
|
||||
│ │ ├── contacts/
|
||||
│ │ ├── todo/
|
||||
│ │ ├── memoro/
|
||||
│ │ └── … (17 Module)
|
||||
│ ├── middleware/ # auth, rate-limit, errors (von shared-hono)
|
||||
│ ├── db/ # 1 Drizzle-Connection für alle Module
|
||||
│ └── index.ts # Routes-Mounting
|
||||
```
|
||||
|
||||
### Migration in zwei Schritten
|
||||
|
||||
| Schritt | Was passiert |
|
||||
| ------- | ------------------------------------------------------------ |
|
||||
| 1 | API-Server mit 3 Modulen erstellt (calendar, contacts, todo) |
|
||||
| 2 | Restliche 12 Module portiert |
|
||||
|
||||
Jedes Modul behält seine Service-Layer fast unverändert — nur das Hono-App-Setup fällt weg. Auth, Errors, Rate-Limiting kommen aus `@manacore/shared-hono`.
|
||||
|
||||
### Was jetzt weg ist
|
||||
|
||||
```
|
||||
apps-archived/
|
||||
├── calendar-server/
|
||||
├── contacts-server/
|
||||
├── todo-server/
|
||||
├── chat-server/
|
||||
├── … (17 Server total)
|
||||
└── README.md # erklärt warum
|
||||
```
|
||||
|
||||
**RAM-Footprint**: ~2.4 GB → ~280 MB. **Cold-Start**: ~45s zusammen → ~120 ms.
|
||||
|
||||
### Auch 25 standalone Web-Apps archiviert
|
||||
|
||||
Mit der Unified Web-App von gestern und dem Unified API von heute haben die einzelnen `apps/{name}/apps/web/` Verzeichnisse keinen Zweck mehr. Alle 25 nach `apps-archived/` verschoben. `wisekeep` ist mit dabei.
|
||||
|
||||
`.eslintrc` ignored `web-archived/` Pattern damit Lint nicht durch totes Holz läuft.
|
||||
|
||||
---
|
||||
|
||||
## Sync: WebSocket → SSE + Partial Sync
|
||||
|
||||
Der Sync-Layer hatte zwei Probleme:
|
||||
|
||||
1. **27 WebSocket-Verbindungen pro User** (eine pro App). Connection-Pool-Limit hit.
|
||||
2. **Voll-Sync beim Start** — alle 120+ Collections, auch wenn der User nur 2 Module benutzt.
|
||||
|
||||
### Unified WebSocket — _eine_ Verbindung pro User
|
||||
|
||||
```
|
||||
Vorher: User × 27 Apps × WebSocket = 27 Verbindungen
|
||||
Nachher: User × 1 manacore-web × WebSocket = 1 Verbindung
|
||||
```
|
||||
|
||||
`mana-sync` (Go) multiplext alle App-Streams über einen Channel. Server-Side: einfach RLS-gefilterter `LISTEN/NOTIFY`.
|
||||
|
||||
### Dann: WebSocket → SSE
|
||||
|
||||
Ein paar Stunden später wurde der WebSocket-Code komplett rausgerissen und durch **Server-Sent Events** ersetzt:
|
||||
|
||||
| Aspekt | WebSocket | SSE |
|
||||
| -------------- | ------------------ | ----------------------------------------- |
|
||||
| Reconnect | Manuell | Browser-built-in |
|
||||
| Proxy-Probleme | Häufig (CF, NGINX) | Keine (HTTP) |
|
||||
| Bidirektional | Ja | Nein (irrelevant — Writes gehen via REST) |
|
||||
| Code | ~600 LOC | ~180 LOC |
|
||||
|
||||
SSE-Endpoint: `GET /api/v1/sync/stream` → ein Heartbeat alle 25s, Push bei jedem Server-side Change.
|
||||
|
||||
### Partial Sync: Lazy Loading
|
||||
|
||||
Beim Login werden jetzt nur **Core-Collections** synchronisiert (settings, profile, dashboard-state). Alle Modul-Collections werden erst beim ersten Visit des Moduls gepullt.
|
||||
|
||||
```
|
||||
Login → core (5 collections, ~50 KB)
|
||||
↓
|
||||
Visit /todo → todo collections (12, ~200 KB)
|
||||
↓
|
||||
Visit /calendar → calendar collections (8, ~150 KB)
|
||||
```
|
||||
|
||||
Plus **Pull-Pagination** mit `hasMore` Flag, damit ein Modul mit 10.000 Items nicht in einem 8 MB JSON-Blob ankommt.
|
||||
|
||||
### Live-Update Bugs (zwei)
|
||||
|
||||
E2E-Tests fanden zwei Bugs in der SSE-Reconnect-Logik:
|
||||
|
||||
1. Doppel-Subscribe nach Reconnect → doppelte Updates
|
||||
2. Pending-Changes wurden nach Reconnect nicht resync'd
|
||||
|
||||
Beide gefixt, mit Test-Coverage.
|
||||
|
||||
---
|
||||
|
||||
## Detail-View Overlay-System
|
||||
|
||||
Vorher öffnete jedes Modul Detail-Views als eigene Route. Mit der Unified App wäre das Navigation-Hell. Lösung: **Overlay-Stack**.
|
||||
|
||||
```
|
||||
Workbench
|
||||
↓ click on task
|
||||
Overlay #1 (TaskDetail)
|
||||
↓ click on linked event
|
||||
Overlay #2 (EventDetail)
|
||||
↓ click on contact
|
||||
Overlay #3 (ContactDetail)
|
||||
[ESC] schließt Overlay #3
|
||||
```
|
||||
|
||||
### Live in 14 Modulen heute
|
||||
|
||||
cards, storage, presi, calendar, contacts, todo, picture, chat, mukke, memoro, planta, inventar, times, dreams.
|
||||
|
||||
Jedes Detail-View hat:
|
||||
|
||||
- **Inline editing** — kein Edit-Modus mehr
|
||||
- **Animated Open/Close**
|
||||
- **ESC-Key Support**
|
||||
- **Stack-Awareness** — z-index korrekt, Backdrop nur unter dem obersten
|
||||
|
||||
Routes für die alten Detail-Pages konsolidiert: 14 Routen weg, 1 Layout-Component da.
|
||||
|
||||
### Page-Carousel statt Routen
|
||||
|
||||
Module wie Contacts haben jetzt einen **Page-Carousel** — zwischen "Liste", "Mein Profil", "Statistik" wird per Swipe/Tab gewechselt, nicht per Route. Schneller, kein Layout-Flash.
|
||||
|
||||
---
|
||||
|
||||
## Tag-System: Eines für alle 23 Module
|
||||
|
||||
Jedes Modul hatte bisher seine eigenen Tags, in eigenen Tabellen. Sinnloser Duplicate-State.
|
||||
|
||||
### `globalTags` Tabelle
|
||||
|
||||
Eine zentrale `globalTags` Tabelle mit `{id, name, color, icon}`. Jedes Modul nutzt Junction-Tables wie `taskTags`, `contactTags`, `eventTags` mit `(itemId, tagId)`.
|
||||
|
||||
### Neuer `createTagLinkOps` Factory
|
||||
|
||||
```typescript
|
||||
const taskTagOps = createTagLinkOps({
|
||||
table: db.taskTags,
|
||||
itemKey: 'taskId',
|
||||
tagKey: 'tagId',
|
||||
});
|
||||
|
||||
await taskTagOps.add({ taskId, tagId });
|
||||
await taskTagOps.removeAll({ taskId });
|
||||
```
|
||||
|
||||
23 Module nutzen denselben Factory, ein einziges Test-Set, eine einzige Sync-Code-Pfad.
|
||||
|
||||
### Shared Components
|
||||
|
||||
- `<TagChip>` — die einheitliche Tag-Pille
|
||||
- `<TagSelector>` — Multi-Select mit Inline-Erstellung
|
||||
- `<TagField>` — Form-Field-Wrapper
|
||||
|
||||
todo + photos sind die ersten Migranten weg von ihren lokalen Kopien.
|
||||
|
||||
### Tag-DnD im Workbench
|
||||
|
||||
Tags können aus dem Tag-Strip gezogen und auf Items in jedem Modul gedroppt werden. Reaktiv, mit `.value` statt `.subscribe()` (das war ein Bug).
|
||||
|
||||
---
|
||||
|
||||
## Workbench: QuickInputBar + Page-Carousel
|
||||
|
||||
### Unified QuickInputBar
|
||||
|
||||
Ein einziger Input am unteren Bildschirmrand. Was er erstellt, hängt vom aktuellen Kontext ab:
|
||||
|
||||
| Modul | QuickInput erstellt |
|
||||
| -------- | ------------------- |
|
||||
| Todo | Task |
|
||||
| Calendar | Event |
|
||||
| Notes | Note |
|
||||
| Contacts | Contact |
|
||||
| Habits | Habit |
|
||||
| … | … |
|
||||
|
||||
**Adapter-Pattern**: jedes Modul registriert sich mit `registerQuickInputAdapter('todo', { create, parse, …})`.
|
||||
|
||||
### Workbench Home: App Pages Carousel
|
||||
|
||||
Statt einer statischen Dashboard-Page hat der Workbench-Home einen horizontalen Carousel mit allen aktiven App-Pages. Swipe/Tab/Cmd+1..9 wechselt zwischen ihnen.
|
||||
|
||||
### 2D-Resize
|
||||
|
||||
Pages im Workbench sind jetzt in beide Achsen resizable (Width + Height), mit Memory pro Page.
|
||||
|
||||
### Bottom-Chrome dynamisch
|
||||
|
||||
`bottom-chrome-height` ist eine CSS-Variable die sich an PillNav, Tabs, Notifications anpasst. Main-Content schiebt sich entsprechend.
|
||||
|
||||
### `AppView` → `ListView` Rename
|
||||
|
||||
In allen 24 Modulen wurde `AppView.svelte` zu `ListView.svelte` umbenannt. Klarer Begriff.
|
||||
|
||||
---
|
||||
|
||||
## i18n: 5 Phasen
|
||||
|
||||
| Phase | Was passiert |
|
||||
| ----- | --------------------------------------------------- |
|
||||
| 1 | Locale-Files in per-Modul-Struktur splitten |
|
||||
| 2 | User-Settings Locale wired, Nav-Translations |
|
||||
| 3 | Alle 22 Module-Translations konsolidiert |
|
||||
| 4 | 126 hardcoded deutsche Strings durch `$_()` ersetzt |
|
||||
| 5 | Help-Content nach Locale-Files migriert |
|
||||
|
||||
Resultat: Es gibt jetzt **keine einzige hardcoded Sprache mehr** im Unified-App-Code (Help-Articles inklusive).
|
||||
|
||||
---
|
||||
|
||||
## Datenbanken: 20+ → 2
|
||||
|
||||
Bisher hatte jeder Modul-Server seine eigene PostgreSQL-DB. Mit dem Unified API macht das keinen Sinn mehr.
|
||||
|
||||
### Neue Struktur
|
||||
|
||||
```
|
||||
mana_platform # alle Service-Daten (auth, todo, calendar, …)
|
||||
# via pgSchema('todo'), pgSchema('calendar'), …
|
||||
# je Modul ein Schema, eine logische Trennung
|
||||
|
||||
mana_sync # write-heavy, das eine Ding für mana-sync (Go)
|
||||
```
|
||||
|
||||
`pgSchema()` statt `pgTable()` ist jetzt Pflicht. Alte DBs (calendar_db, contacts_db, …) gedumpt + gelöscht.
|
||||
|
||||
---
|
||||
|
||||
## Analytics: Module-Context + Web Vitals
|
||||
|
||||
### Konsolidierung
|
||||
|
||||
Umami wurde von allen archivierten Apps entfernt — nur die Unified App trackt jetzt. Aber: jedes Event bekommt einen `module` Context.
|
||||
|
||||
```typescript
|
||||
analytics.track('item.created', {
|
||||
module: 'todo',
|
||||
itemType: 'task',
|
||||
hasDueDate: true,
|
||||
});
|
||||
```
|
||||
|
||||
### Event-Tracking in 19 Module-Stores
|
||||
|
||||
Erste Welle: 7 Core-Module. Zweite Welle: 12 weitere. Tracked werden Create/Update/Delete/Move/Reorder Events.
|
||||
|
||||
### Web Vitals + Funnel
|
||||
|
||||
CLS, LCP, FID, TTFB werden jetzt zu Umami gesendet. Plus Funnel-Events: `funnel.signup.start`, `funnel.signup.complete`, `funnel.first_action`. GlitchTip bekommt User-Context (anonymisierte ID + Tier).
|
||||
|
||||
---
|
||||
|
||||
## Reminder System
|
||||
|
||||
Neuer **shared reminder system** in `@manacore/shared-stores` — ein Background-Worker scheduled lokale Notifications für Tasks, Events, Habits. Wird im App-Layout gemountet.
|
||||
|
||||
```typescript
|
||||
reminderService.schedule({
|
||||
module: 'todo',
|
||||
itemId: task.id,
|
||||
fireAt: task.dueDate.minusMinutes(15),
|
||||
payload: { title: task.title },
|
||||
});
|
||||
```
|
||||
|
||||
Notification kommt via Service Worker, klickbar, öffnet das Detail-View.
|
||||
|
||||
---
|
||||
|
||||
## Auth & Stores Refactor
|
||||
|
||||
- **`shared-auth-stores` aufgelöst** → in `shared-auth-ui` absorbiert. Ein Package weniger.
|
||||
- **`createGuestMode` Composable** + Unified `AuthGate` — der Guest-Modus ist jetzt überall einheitlich, statt 23 lokale Implementierungen.
|
||||
- **`createArchiveOps` / `createViewStore` Factories** — wiederverwendbare CRUD-Patterns für alle Module.
|
||||
- **Shared Python Auth** + **Shared Go Auth** Packages — extrahiert aus mana-stt, mana-tts, und 3 Go-Services.
|
||||
|
||||
---
|
||||
|
||||
## Monitoring
|
||||
|
||||
- **Strukturiertes Logging** in allen Services (JSON-Format, korrelierbar)
|
||||
- **Promtail-Alignment** — Labels einheitlich, damit Loki-Queries nicht über inkompatible Schemas stolpern
|
||||
- **GlitchTip Config** updated für unified app
|
||||
- **Status-Page** zeigt jetzt unified app statt 27 einzelne Apps
|
||||
|
||||
---
|
||||
|
||||
## Sonstiges
|
||||
|
||||
| Item | Detail |
|
||||
| --------------- | --------------------------------------------------------------------------------------------- |
|
||||
| Self-Contact | Contacts erstellt für jeden User automatisch einen Eigenen-Profil-Contact, auch im Guest-Mode |
|
||||
| ColorPicker | Generischer `<ColorPicker>` mit Standard-Paletten in shared-ui |
|
||||
| FavoriteButton | Generic `<FavoriteButton>` + `toggleField` Utility |
|
||||
| Vitest Coverage | Threshold von 50 → 70 % |
|
||||
| jest.config.js | Orphan entfernt |
|
||||
| Stub-Services | Archived |
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
| Bereich | Commits | Highlights |
|
||||
| ---------------------- | ------- | ------------------------------------------------- |
|
||||
| Unified API | ~15 | 17 Server zu einem, 25 Apps archiviert, 2 DBs |
|
||||
| Sync | ~10 | WebSocket→SSE, partial sync, pull pagination |
|
||||
| Detail Overlays | ~10 | 14 Module, stack-fähig, inline editing |
|
||||
| Tag System | ~12 | globalTags, junction tables, factory, DnD |
|
||||
| QuickInput + Workbench | ~8 | Adapter-Pattern, Page-Carousel, 2D-Resize |
|
||||
| i18n | ~6 | 5 Phasen, 126 Strings extrahiert |
|
||||
| Analytics | ~10 | Module-Context, Web Vitals, 19 Stores |
|
||||
| Refactor | ~12 | Auth-Stores absorbiert, Factories, Python/Go Auth |
|
||||
| Monitoring | ~5 | Logging, Promtail, GlitchTip |
|
||||
| Fixes | ~19 | Diverse Sync- + Build-Bugs |
|
||||
|
||||
---
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
- Habits-Modul aufbauen (das einzige offene Modul)
|
||||
- Notes & Finance Module ins Unified pattern ziehen
|
||||
- Cross-Module-Drag-Targets ausbauen (Tags → Tasks → Events fertig, fehlt Karten/Locations)
|
||||
- Stalwart als interner Mail-Server für mana-notify
|
||||
|
|
@ -0,0 +1,339 @@
|
|||
---
|
||||
title: 'Habits, Automations, Notes/Finance/Places + Stalwart Mail + Undo'
|
||||
description: 'Vier neue Module (habits, automations, notes/finance, places mit GPS), Cross-Module Trigger-Registry für Automations, Stalwart Mail-Server intern, Undo-Toasts auf 14 DetailViews und cross-module clickable links mit overlay stacking.'
|
||||
date: 2026-04-03
|
||||
author: 'Till Schneider'
|
||||
category: 'feature'
|
||||
tags:
|
||||
[
|
||||
'manacore',
|
||||
'habits',
|
||||
'automations',
|
||||
'notes',
|
||||
'finance',
|
||||
'places',
|
||||
'stalwart',
|
||||
'mail',
|
||||
'undo',
|
||||
'workbench',
|
||||
'context-menu',
|
||||
]
|
||||
featured: true
|
||||
commits: 80
|
||||
readTime: 13
|
||||
stats:
|
||||
filesChanged: 2617
|
||||
linesAdded: 17005
|
||||
linesRemoved: 249085
|
||||
contributors:
|
||||
- name: 'Till Schneider'
|
||||
handle: 'Till-JS'
|
||||
commits: 80
|
||||
workingHours:
|
||||
start: '2026-04-03T00:08'
|
||||
end: '2026-04-03T21:39'
|
||||
---
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Vier neue Module**: habits, automations, notes, finance, places (GPS)
|
||||
- **Automations Trigger-Registry** — cross-module Aktionen reagieren aufeinander
|
||||
- **Stalwart Mail-Server** intern für mana-notify (raus aus Brevo-only)
|
||||
- **Undo-Toasts** auf 14 DetailViews + Task-Completion
|
||||
- **Cross-Module clickable links** mit Overlay-Stacking
|
||||
- **Page-Shell**: Drag-Header + Move/Min/Max/Close in eine Bar
|
||||
- **−249k LOC** (massive Cleanup-Welle: 25 archivierte Apps endgültig gelöscht, Legacy-Code raus)
|
||||
|
||||
---
|
||||
|
||||
## Habits-Modul
|
||||
|
||||
Habits war eines der letzten Module die noch fehlten. Heute scaffolded und in einem Aufwasch produktiv:
|
||||
|
||||
### Features
|
||||
|
||||
- **Tally-Board** als Standardansicht — jeden Tag eine Spalte, jede Habit eine Zeile
|
||||
- **Inline-Create** in der Liste, kein Modal
|
||||
- **Detail-View** mit Streak-Anzeige, Verlauf, Notizen pro Tag
|
||||
- **Phosphor-Icons statt Emojis** — neuer shared `<IconPicker>` (mit Suche), gilt jetzt auch für andere Module die Icons brauchen
|
||||
- **Photos-Upload** an Daily-Entries möglich
|
||||
|
||||
Datenmodell: `habits` (Definition) + `habitEntries` (eine pro Tag pro Habit). Beide auf encrypted-by-default.
|
||||
|
||||
---
|
||||
|
||||
## Automations-Modul
|
||||
|
||||
Das wahrscheinlich konzeptuell interessanteste Stück des Tages: **Automations** als eigenes Modul, das aus jedem anderen Modul Trigger empfangen kann.
|
||||
|
||||
### Cross-Module Trigger-Registry
|
||||
|
||||
Module registrieren ihre Trigger zentral:
|
||||
|
||||
```typescript
|
||||
// modules/todo/automations.ts
|
||||
registerTriggers('todo', [
|
||||
{ id: 'task.completed', label: 'Aufgabe abgehakt' },
|
||||
{ id: 'task.created', label: 'Aufgabe erstellt' },
|
||||
{ id: 'task.due_today', label: 'Aufgabe wird heute fällig' },
|
||||
]);
|
||||
```
|
||||
|
||||
Und ihre Aktionen:
|
||||
|
||||
```typescript
|
||||
registerActions('calendar', [{ id: 'event.create', label: 'Termin erstellen', schema: ZEvent }]);
|
||||
```
|
||||
|
||||
Eine Automation verbindet **Trigger → Bedingungen → Aktion**:
|
||||
|
||||
```
|
||||
WHEN todo.task.completed
|
||||
WHERE task.tag === 'sport'
|
||||
THEN habits.entry.create habit='workout'
|
||||
```
|
||||
|
||||
### UI für Trigger-Rules
|
||||
|
||||
Eine eigene Page in der Automations-App listet alle aktiven Rules. Inline-Editor mit Auto-Suggest aus der Trigger/Action-Registry.
|
||||
|
||||
### Proaktive Suggestions
|
||||
|
||||
Plus: das System schaut sich an was der User regelmäßig manuell macht ("nach jedem `task.completed` mit Tag 'sport' erstellt User eine `habit.entry`") und schlägt eine entsprechende Automation **inline** vor — dort wo das Verhalten passiert, nicht in einer separaten View.
|
||||
|
||||
---
|
||||
|
||||
## Notes, Finance — und ein konsolidiertes Registry
|
||||
|
||||
Zwei kleine Module nebenher gebaut:
|
||||
|
||||
### Notes
|
||||
|
||||
- Leichtes Notes-Modul (kein Markdown-Editor — pure Plain-Text + Tags)
|
||||
- Click-to-edit, ListView mit kompaktem Input oben
|
||||
- Workbench-Panel mit Inline-Edit (kein Detail-Modal nötig für Quick-Capture)
|
||||
|
||||
### Finance
|
||||
|
||||
- Buchhaltungs-Light (Income / Expense / Categories)
|
||||
- Workbench-Panel direkt nutzbar — Eingabe + sofortige Liste
|
||||
|
||||
### Unified `AppDescriptor`
|
||||
|
||||
Vorher gab es zwei Registries: Entity-Registry (für DnD-Targets) + App-Registry (für Branding/Routing). Heute zusammengeführt zu einem **`AppDescriptor`**, der alles in einer Datei pro Modul beschreibt:
|
||||
|
||||
```typescript
|
||||
export const todoApp: AppDescriptor = {
|
||||
id: 'todo',
|
||||
label: 'Todo',
|
||||
color: '#6366f1',
|
||||
icon: 'check',
|
||||
routes: [...],
|
||||
entityTypes: ['task', 'project', 'page'],
|
||||
dragSources: [...],
|
||||
dropTargets: [...],
|
||||
searchProvider: searchTodos,
|
||||
};
|
||||
```
|
||||
|
||||
Module registrieren sich mit einem einzigen Aufruf statt drei.
|
||||
|
||||
---
|
||||
|
||||
## Places-Modul mit GPS-Tracking
|
||||
|
||||
Das fünfte neue Modul des Tages: **Places** — einfache Location-Verwaltung, mit optionalem GPS-Tracking im Hintergrund.
|
||||
|
||||
- **GPS-Background-Tracking** opt-in (per Service-Worker / Permissions API)
|
||||
- **Visit-Detection** via Stationary-Phasen
|
||||
- **Reverse Geocoding** über OSM (Nominatim)
|
||||
- **Map-View** mit OSM-Embeds (leaflet wurde im selben Sweep entfernt — siehe unten)
|
||||
|
||||
---
|
||||
|
||||
## Cross-Module Clickable Links + Overlay-Stacking
|
||||
|
||||
Kombiniert mit dem Detail-Overlay-System von gestern: jedes Item kann auf Items aus anderen Modulen verweisen, und ein Klick öffnet das Ziel **on top** des aktuellen Overlays.
|
||||
|
||||
```
|
||||
TaskDetail (Overlay #1)
|
||||
→ Linked Event "Sprint Review"
|
||||
Click → EventDetail (Overlay #2)
|
||||
→ Linked Contact "Anna"
|
||||
Click → ContactDetail (Overlay #3)
|
||||
```
|
||||
|
||||
ESC schließt nur das oberste Overlay. Das System tracked welche Items gerade offen sind, um Doppel-Open zu verhindern.
|
||||
|
||||
### Detail-View Polish
|
||||
|
||||
- **Animated Close** + ESC-Key (gestern war's nur Open-Animation)
|
||||
- **Mehrere DetailViews offen** über AppPages hinweg
|
||||
- **Tag-Pills mit Click-to-Remove** in jeder DetailView
|
||||
- **Tags als Pills statt Dots** anzeigen — endlich mit Label
|
||||
|
||||
---
|
||||
|
||||
## Undo-Toasts — wirklich überall
|
||||
|
||||
Bisher gab es Undo nur für gelöschte Tasks. Heute ausgeweitet auf:
|
||||
|
||||
- **14 DetailViews** (delete + tag removal)
|
||||
- **Task-Completion** (mit "Doch nicht erledigt"-Toast)
|
||||
|
||||
Pattern: jede destruktive Aktion stagged eine `pendingDelete` für 5 Sekunden, im Hintergrund läuft der Undo-Timer. Klick auf "Rückgängig" → restore. Sonst → flush.
|
||||
|
||||
---
|
||||
|
||||
## Stalwart Mail-Server
|
||||
|
||||
`mana-notify` lief bisher nur über Brevo (extern). Für interne Mails (User-Verifikation, System-Alerts, Memoro-Invites) ist das ungeeignet — Brevo zählt jeden Verify-Mail als "Marketing-Email".
|
||||
|
||||
### Stalwart als interner Postfix-Ersatz
|
||||
|
||||
```yaml
|
||||
# docker-compose.macmini.yml
|
||||
stalwart:
|
||||
image: stalwartlabs/stalwart
|
||||
ports:
|
||||
- '25:25'
|
||||
- '587:587'
|
||||
volumes:
|
||||
- stalwart-data:/opt/stalwart
|
||||
```
|
||||
|
||||
`mana-notify` wird auf SMTP umgestellt — Stalwart ist Default für interne Mails, Brevo bleibt für Massenmails (Newsletter etc., später).
|
||||
|
||||
### Eine Reihe von Iterations
|
||||
|
||||
Stalwart ist neu — es gab heute mehrere Bugs:
|
||||
|
||||
| Issue | Fix |
|
||||
| -------------------------------------------- | ----------------------------------------------------- |
|
||||
| Falscher Image-Name | `stalwartlabs/stalwart` (nicht `stalwart-mail`) |
|
||||
| Port-Mapping kollidierte mit Host-Postfix | 25 + 587 explizit gemapped |
|
||||
| Healthcheck schlug fehl | TCP-Check auf 587 |
|
||||
| LOGIN-Auth schlug fehl | mana-notify SMTP-Sender komplett neu mit `LOGIN auth` |
|
||||
| Insecure TLS für intern erlaubt | Self-Signed-Zertifikate akzeptiert für interne Hops |
|
||||
| Falsche Account-Rolle | `noreply` mit `user`-Role statt admin |
|
||||
| Brevo SMTP_USER fehlt als Default | Backward-compat für externe Mails |
|
||||
| Service-Key zwischen mana-auth & mana-notify | Aligned, sonst lehnt notify die Auth-Mails ab |
|
||||
| Message-ID + Date-Headers fehlten | Outgoing-Mails landen sonst in Spam |
|
||||
|
||||
Am Ende: Auth-Mails vom mana-auth Service gehen jetzt **vollständig intern** über Stalwart raus. Spam-Score: gut.
|
||||
|
||||
### `mana-auth` → `mana-notify` Refactor
|
||||
|
||||
`mana-auth` hatte Nodemailer-Code für Verifikations-Mails. Jetzt routet es ALLE Mails über mana-notify (eine API, ein Sender, eine Stelle für Templates).
|
||||
|
||||
---
|
||||
|
||||
## PageShell: Drag + Window-Controls in eine Bar
|
||||
|
||||
Bisher hatte jede Page einen Drag-Bereich oben + separate Window-Controls in der Ecke. Confusing. Jetzt:
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ ← ••• Title ••• → │ □ ⊟ ⊠ ✕ │
|
||||
└────────────────────────────────────────────────┘
|
||||
Drag Handle Min Max Close
|
||||
```
|
||||
|
||||
- **Volle Drag-Bar** als Header (height reduziert auf das Nötige)
|
||||
- **Window-Controls** (min/max/close) integriert
|
||||
- **Left/Right Arrow Buttons** für Page-Navigation
|
||||
- **Drag Preview** zeigt Item-Title + App-Color (statt nur "1 Item")
|
||||
- **Drag-Handles immer sichtbar**, nicht nur on hover
|
||||
|
||||
### Drag vs Click Bug
|
||||
|
||||
Bug: nach DnD wurde der Click auch noch ausgelöst → Detail-View öffnete sich. Fix: Click-Event blocken wenn ein Drag gerade stattgefunden hat.
|
||||
|
||||
---
|
||||
|
||||
## Workbench Page-System
|
||||
|
||||
- **`PageCarousel` full-width** auf Homepage (kein Side-Padding)
|
||||
- **Left scroll offset** damit die erste Page nicht am Rand klebt
|
||||
- **Page Drag** restricted auf Handle-Area damit Items innerhalb der Page noch greifbar sind
|
||||
- **Mobile responsive** — Page-Width passt sich an Viewport an
|
||||
|
||||
---
|
||||
|
||||
## Cleanup-Welle (−249k LOC)
|
||||
|
||||
Heute war auch Aufräumtag. Mit den 25 archivierten Apps von gestern und der Stabilität des Unified-Stacks konnte massiv Legacy raus:
|
||||
|
||||
| Cleanup | LOC |
|
||||
| ----------------------------------------------------------------------------- | ------------- |
|
||||
| 25 web-archived directories endgültig gelöscht | großer Teil |
|
||||
| Legacy per-app IndexedDB Migration | ~3k |
|
||||
| Backend-API-Clients (Ghost-Code, kein Server mehr da) | ~5k |
|
||||
| Stale Stubs in Workspace-Config | – |
|
||||
| `shared-auth-stores`, `shared-profile-ui`, `shared-app-onboarding` Referenzen | – |
|
||||
| Leaflet → OSM-Embeds (kein 200 KB JS-Bundle mehr) | – |
|
||||
| Codebase-weiter Consolidate-Sweep | viele Dateien |
|
||||
|
||||
**Note:** Wir haben aktuell noch keine User in Production. Dieser Cleanup-Modus ist genau jetzt richtig — danach wird's politisch.
|
||||
|
||||
---
|
||||
|
||||
## Fixes & Polish
|
||||
|
||||
| Fix | Beschreibung |
|
||||
| -------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| Effect depth exceeded | `guestMode` wurde versehentlich `$state()`, hat sich selbst getriggert |
|
||||
| Entity registration hang | Race-Condition zwischen Registry-Init und Module-Mount |
|
||||
| 40 Svelte dev warnings | Clean-Startup ohne Console-Spam |
|
||||
| Default Port unified API | 3050 → 3060 (3050 ist mana-sync) |
|
||||
| API-Server Dev-Scripts | Alle Scripts auf den Unified API umgestellt |
|
||||
| Status-Page / Prometheus / Cloudflared | Configs für unified app aktualisiert |
|
||||
| Race in status-page-gen | Lock-File während Generation |
|
||||
| Analytics Umami Website-ID | Nach DB-Reset neue ID gepflegt |
|
||||
| `getTagsByIds` | Fehlender `allTags` Param in zitare gefixt |
|
||||
| ManaContacts → Kontakte | Branding-Rename |
|
||||
| AppDrawer → new tab | Apps öffnen sich extern statt innerhalb der App |
|
||||
| `bindclient:Height` → calculated | Bottom-Chrome-Height berechnet, nicht gemessen |
|
||||
| CSP localhost erlaubt | In Dev-Mode |
|
||||
| `bottomChromeHeight` Order | Deklaration vor Verwendung |
|
||||
| Sync revert | Per-app SSE → HTTP polling als Fallback (SSE-Bugs morgen klären) |
|
||||
| `@const` revert | Innerhalb `<div>` invalid |
|
||||
|
||||
---
|
||||
|
||||
## Branding & UI
|
||||
|
||||
- **PillNav cleanup**: Observatory, API Keys, Gifts entfernt — waren Stubs
|
||||
- **InputBar Toggle**: PillNav-Toggle nach rechts in InputBar verschoben (vorher links, blockierte Tag-Strip)
|
||||
- **Tags**: leftmost Position in PillNav, größerer Toggle-Button
|
||||
|
||||
---
|
||||
|
||||
## Dokumentation
|
||||
|
||||
- `docs/MAIL_STALWART.md` — Setup-Notes, Auth-Config, Troubleshooting
|
||||
- `docs/UNIFIED_APP_PHASE7_NOTES.md` — Detail-Patterns für Drag/Drop und Overlays
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
| Bereich | Commits | Highlights |
|
||||
| --------------------- | ------- | -------------------------------------------------- |
|
||||
| Neue Module | ~14 | habits, automations, notes, finance, places |
|
||||
| Stalwart Mail | ~13 | Setup, 8 Iterations bis stabil, mana-auth Refactor |
|
||||
| Undo-Toasts | ~3 | 14 DetailViews + Task-Completion |
|
||||
| Cross-Module Links | ~5 | Overlay-Stacking, animated close, ESC |
|
||||
| Workbench / PageShell | ~12 | Drag + Window-Controls in eine Bar |
|
||||
| Cleanup | ~10 | −249k LOC, Legacy + 25 archived Apps endgültig weg |
|
||||
| Tag UI | ~5 | Pills statt Dots, Click-to-Remove |
|
||||
| Fixes | ~18 | Reactivity, Race-Conditions, Sync-Revert, CSP |
|
||||
|
||||
---
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
- Mobile-Responsive für alle Module (PWA-fähig machen)
|
||||
- Habits + Tasks scheduling: gemeinsamer Time-Block
|
||||
- Finance Quick-Stats Widget für Dashboard
|
||||
- Sync SSE-Bugs root-causen, polling wieder zurückbauen
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
---
|
||||
title: 'mana-media als zentrale Bild-Pipeline + Effect-Depth-Fix'
|
||||
description: 'Kurzer Tag: Bild-Uploads aller 5 Module routen über mana-media (CAS, Thumbnails, EXIF, Photos-Galerie). Dazu der Fix für effect_update_depth_exceeded der seit dem Unified-App-Switch auf 6 Dashboard-Modulen heumkroch.'
|
||||
date: 2026-04-04
|
||||
author: 'Till Schneider'
|
||||
category: 'fix'
|
||||
tags:
|
||||
['mana-media', 'cas', 'thumbnails', 'photos', 'svelte5', 'reactivity', 'liveQuery', 'guest-mode']
|
||||
featured: false
|
||||
commits: 2
|
||||
readTime: 4
|
||||
stats:
|
||||
filesChanged: 23
|
||||
linesAdded: 299
|
||||
linesRemoved: 318
|
||||
contributors:
|
||||
- name: 'Till Schneider'
|
||||
handle: 'Till-JS'
|
||||
commits: 2
|
||||
workingHours:
|
||||
start: '2026-04-04T10:30'
|
||||
end: '2026-04-04T11:00'
|
||||
---
|
||||
|
||||
## Highlights
|
||||
|
||||
- **mana-media** wird zentrale Bild-Pipeline: Picture, Contacts, Planta, Storage, NutriPhi laden Bilder darüber hoch
|
||||
- **CAS (SHA-256 Dedup)**, **Thumbnails**, **EXIF-Extraktion** automatisch
|
||||
- Alle hochgeladenen Bilder erscheinen in der **Photos-Galerie**
|
||||
- Fix: `effect_update_depth_exceeded` in 6 Dashboard-Modulen (Resultat des Unified-App-Switches)
|
||||
|
||||
Kurze Samstag-Session — zwei gezielte Commits.
|
||||
|
||||
---
|
||||
|
||||
## mana-media: Eine Pipeline für alle Bild-Uploads
|
||||
|
||||
Bisher hat jedes Modul Bilder direkt in S3/MinIO geschrieben. Das hatte mehrere Nachteile:
|
||||
|
||||
- **Keine Dedup** — dasselbe Bild in 3 Modulen lag 3-mal im Bucket
|
||||
- **Keine Thumbnails** — jedes Modul rollte sein eigenes Resizing
|
||||
- **Keine EXIF-Extraktion** — keine konsistente Date-Taken-Sortierung
|
||||
- **Photos-Galerie sah nichts davon** — uploads aus Contacts/Picture/Planta waren in der Galerie unsichtbar
|
||||
|
||||
### Neues Routing
|
||||
|
||||
```
|
||||
Vorher: Nachher:
|
||||
Picture → S3 direkt Picture ─┐
|
||||
Contacts → S3 direkt Contacts ─┤
|
||||
Planta → S3 direkt ───→ Planta ─┼─→ mana-media → S3 (CAS) + DB
|
||||
Storage → S3 direkt Storage ─┤ ↓
|
||||
NutriPhi → S3 direkt NutriPhi ─┘ Photos Gallery
|
||||
```
|
||||
|
||||
### Implementierung
|
||||
|
||||
`apps/api/src/lib/media.ts` ist der neue Helper. Module-Routes rufen `uploadImageToMedia(buffer, { module, ownerId })` und bekommen eine `mediaId` zurück, die sie in ihrer eigenen Tabelle persistieren.
|
||||
|
||||
mana-media liefert zurück:
|
||||
|
||||
- `mediaId` — referenzierbar überall
|
||||
- `url` für das Original
|
||||
- `thumbnailUrls` für 256/512/1024
|
||||
- `width / height / format`
|
||||
- `exif.takenAt`, GPS, Camera
|
||||
|
||||
### Was nicht über mana-media geht
|
||||
|
||||
- **Non-Images** (PDFs, Audio, Docs): bleiben direkt bei `@manacore/shared-storage`. Sharp kann sowas nicht.
|
||||
- **SVG-Avatare** in Contacts: bleiben bei shared-storage, weil Sharp keine SVGs verarbeitet.
|
||||
|
||||
### Nebeneffekt: Photos-Galerie wird automatisch befüllt
|
||||
|
||||
Die Photos-App liest aus `mana_media.media WHERE owner_id = ?`. Damit erscheinen jetzt alle Avatare aus Contacts, alle Pflanzen-Fotos aus Planta, alle Mahlzeit-Bilder aus NutriPhi etc. in der Galerie — ohne dass die Module davon wissen.
|
||||
|
||||
---
|
||||
|
||||
## Fix: effect_update_depth_exceeded
|
||||
|
||||
Seit der Unified-App-Migration vor 3 Tagen tauchten in 6 Dashboard-Modulen Svelte-5-Errors auf:
|
||||
|
||||
```
|
||||
Uncaught Svelte error: effect_update_depth_exceeded
|
||||
Maximum update depth exceeded. This can happen when a reactive
|
||||
block or effect repeatedly sets a new value.
|
||||
```
|
||||
|
||||
### Root Cause
|
||||
|
||||
```typescript
|
||||
// alt — verursacht den Error
|
||||
let items = $state<Item[]>([]);
|
||||
$effect(() => {
|
||||
const sub = liveQuery(() => db.items.toArray()).subscribe((arr) => {
|
||||
items = arr; // <- triggert weitere $state writes downstream
|
||||
});
|
||||
return () => sub.unsubscribe();
|
||||
});
|
||||
```
|
||||
|
||||
Das `$state`-Write in dem `$effect` triggerte downstream Effects (in Children), die wieder Stores updateten, die wieder Children re-rendered → Cascade. Bei kleinen Listen okay, bei den Dashboard-Widgets mit 6 parallelen Subscriptions: BOOM.
|
||||
|
||||
### Fix
|
||||
|
||||
`useLiveQueryWithDefault` ist ein Hook der `liveQuery` direkt in einen `$derived` umwandelt — kein doppelter `$state` Round-Trip:
|
||||
|
||||
```typescript
|
||||
// neu
|
||||
const items = useLiveQueryWithDefault(() => db.items.toArray(), [] as Item[]);
|
||||
```
|
||||
|
||||
Migriert in: **todo, calendar, contacts, habits, notes, finance** — die 6 Dashboard-Module.
|
||||
|
||||
### Plus: Zwei verwandte Reactivity-Fixes
|
||||
|
||||
1. **`checkInlineSuggestion` in Dexie-Hooks** — wurde innerhalb einer Single-Table-Transaction aufgerufen, hat aber Cross-Table reads gemacht. `setTimeout(…, 0)` deferiert es nach dem Transaction-Commit.
|
||||
|
||||
2. **`guestMode` mit `$state()` deklariert** — am 03.04. wurde es vorübergehend auf primitive zurückgesetzt (wegen einer Reactivity-Schleife). Mit dem heutigen Pattern-Switch funktioniert `$state()` wieder.
|
||||
|
||||
3. **`trySSO` mit 5s Timeout** — wenn mana-auth unreachable ist, hängt der Login-Flow nicht mehr. Timeout-Path führt direkt in den Guest-Modus.
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
| Bereich | Commits | Highlights |
|
||||
| ------------------- | ------- | ---------------------------------------------------------------------- |
|
||||
| mana-media Pipeline | 1 | 5 Module über CAS, Thumbnails, EXIF, Photos-Galerie sichtbar |
|
||||
| Reactivity-Fixes | 1 | useLiveQueryWithDefault in 6 Modulen, Dexie-Hook deferral, SSO-Timeout |
|
||||
|
||||
---
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
- mana-media Cleanup-Job für orphaned Bilder
|
||||
- Sharp aktivieren für animierte WebP / AVIF Output
|
||||
- File-Bytes Encryption auch für mana-media Uploads (gehört zu Phase 8 der Encryption-Roadmap)
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
---
|
||||
title: 'TimeBlocks: ein Zeitmodell für alles + Mana Rename + PWA'
|
||||
description: 'TimeBlocks vereinheitlicht Calendar/Habits/Tasks/Focus zu einem Zeitmodell mit rrule.js. Mukke wird zu Music. ManaCore wird zu Mana. PWA-Support inkl. Offline-UX und Update-Prompt für alle Module.'
|
||||
date: 2026-04-05
|
||||
author: 'Till Schneider'
|
||||
category: 'feature'
|
||||
tags:
|
||||
[
|
||||
'timeblocks',
|
||||
'calendar',
|
||||
'habits',
|
||||
'rrule',
|
||||
'pwa',
|
||||
'offline',
|
||||
'mobile',
|
||||
'rename',
|
||||
'mana',
|
||||
'mukke',
|
||||
'music',
|
||||
]
|
||||
featured: true
|
||||
commits: 19
|
||||
readTime: 11
|
||||
stats:
|
||||
filesChanged: 2244
|
||||
linesAdded: 15083
|
||||
linesRemoved: 11360
|
||||
contributors:
|
||||
- name: 'Till Schneider'
|
||||
handle: 'Till-JS'
|
||||
commits: 19
|
||||
workingHours:
|
||||
start: '2026-04-05T14:39'
|
||||
end: '2026-04-05T21:14'
|
||||
---
|
||||
|
||||
## Highlights
|
||||
|
||||
- **TimeBlocks** als unified time model — Calendar, Habits, Tasks, Focus-Mode teilen sich eine Tabelle
|
||||
- **rrule.js** als zentrale Recurrence-Engine, mit Edit-this/all/following Prompts
|
||||
- **PWA** für die unified app — Offline-UX, Update-Prompt, Icons, mobile Responsiveness in allen Modulen
|
||||
- **Rename**: ManaCore → **Mana** (final), Mukke → **Music**
|
||||
- **Cross-Module DnD ins Calendar** — Tasks/Habits droppen wird zum TimeBlock
|
||||
- **Calendar Type-Specific Styling** — verschiedene Block-Types haben verschiedene Visuals
|
||||
- **iCal Export** + Analytics-Dashboard für TimeBlocks
|
||||
- **"Mein Tag" Timeline-Widget** auf dem Dashboard
|
||||
|
||||
---
|
||||
|
||||
## TimeBlocks: Ein Zeitmodell für alles
|
||||
|
||||
Bisher hatten Calendar, Habits, Todo-Scheduling und der (geplante) Focus-Mode jeweils ihr eigenes Zeitmodell. Drei Tabellen, drei Recurrence-Implementierungen, drei UIs.
|
||||
|
||||
### Eine `timeBlocks` Tabelle
|
||||
|
||||
```typescript
|
||||
type TimeBlock = {
|
||||
id: string;
|
||||
type: 'event' | 'habit' | 'task' | 'focus' | 'meal' | 'sleep';
|
||||
title: string;
|
||||
start: Date;
|
||||
end: Date;
|
||||
rrule?: string; // RFC 5545
|
||||
refModule?: string; // 'todo' | 'habits' | 'memoro' | …
|
||||
refId?: string; // ID im Quell-Modul
|
||||
color?: string;
|
||||
status?: 'planned' | 'done' | 'skipped' | 'missed';
|
||||
};
|
||||
```
|
||||
|
||||
Jeder Calendar-Event, jede Habit-Schedule, jede Task-Due-Date Kombination, jede Focus-Session, jede Schlaf-/Mahlzeit-Erfassung ist jetzt ein TimeBlock.
|
||||
|
||||
### `rrule.js` als Recurrence-Engine
|
||||
|
||||
Statt drei selbstgebauter Recurrence-Implementierungen jetzt **eine Library**:
|
||||
|
||||
```typescript
|
||||
import { RRule } from 'rrule';
|
||||
|
||||
const block = {
|
||||
rrule: 'FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=12',
|
||||
};
|
||||
|
||||
// Expand für Anzeige
|
||||
const occurrences = RRule.fromString(block.rrule).between(viewStart, viewEnd);
|
||||
```
|
||||
|
||||
### Custom Recurrence UI
|
||||
|
||||
Vorher: Dropdown mit "Daily / Weekly / Monthly". Jetzt:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────┐
|
||||
│ Wiederholt sich │
|
||||
│ ⚪ Nie │
|
||||
│ ⚪ Täglich │
|
||||
│ ⚪ Wöchentlich am [Mo Mi Fr] │
|
||||
│ ⚫ Benutzerdefiniert │
|
||||
│ Alle [2] [Wochen] │
|
||||
│ Am [Mo] [Mi] [Fr] │
|
||||
│ Bis [15. Mai 2026] │
|
||||
└──────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Edit/Delete Prompts
|
||||
|
||||
Wenn man einen rezidivierenden Block ändert kommt jetzt ein Prompt:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ Diese Wiederholung bearbeiten? │
|
||||
│ • Nur diesen Termin │
|
||||
│ • Diesen und folgende │
|
||||
│ • Alle Termine │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
Die Logik: bei "nur diesen" wird der Block aus der RRule **exdatet** und als Standalone-Block neu angelegt. Bei "folgende" wird der ursprüngliche Block am gewählten Datum **truncated** und ein neuer mit der angepassten Definition ab dem Datum erstellt.
|
||||
|
||||
### Habits-Migration
|
||||
|
||||
Habits hatten bisher ihre eigene `habitSchedules` Tabelle. Heute migriert auf TimeBlocks:
|
||||
|
||||
```
|
||||
habit.schedule = TimeBlock {
|
||||
type: 'habit',
|
||||
refModule: 'habits',
|
||||
refId: habitId,
|
||||
rrule: 'FREQ=DAILY',
|
||||
}
|
||||
|
||||
habit.entry = TimeBlock {
|
||||
type: 'habit',
|
||||
refId: habitId,
|
||||
start, end,
|
||||
status: 'done' | 'skipped',
|
||||
}
|
||||
```
|
||||
|
||||
### Calendar-Type-Specific Styling
|
||||
|
||||
Im Calendar werden TimeBlocks je nach `type` unterschiedlich dargestellt:
|
||||
|
||||
- **event** — gefüllter Block in Module-Color
|
||||
- **habit** — Block mit gestricheltem Border
|
||||
- **task** — Block mit Checkbox-Indicator
|
||||
- **focus** — opaker Block mit Schloss-Icon
|
||||
- **sleep / meal** — kleinere "Tag" am Rand
|
||||
|
||||
Plus: Filter-UI um Block-Types ein-/auszuschalten, Cross-Module-Navigation (Klick auf Habit-Block → öffnet Habit-Detail).
|
||||
|
||||
---
|
||||
|
||||
## TimeBlocks: Phasenrolle
|
||||
|
||||
Heute kamen viele Features in einem Rutsch — der Reihe nach:
|
||||
|
||||
| Feature | Beschreibung |
|
||||
| ----------------------- | ---------------------------------------------------------------------------- |
|
||||
| Unified Time Model | `timeBlocks` Tabelle, Migration der bestehenden Daten |
|
||||
| External Item Drag | Tasks aus der Todo-Liste droppen → wird Task-TimeBlock |
|
||||
| Conflict Detection | Überlappende Blöcke werden visuell markiert + Warnung |
|
||||
| Plan vs Reality | TimeBlock hat optional einen `actualStart` / `actualEnd` (für Time-Tracking) |
|
||||
| Timeline View | Vertikale Tagesansicht mit gegenwärtiger Zeit-Linie |
|
||||
| Focus Mode | TimeBlocks vom Type `focus` blockieren Notifications & Distractions |
|
||||
| Habit Scheduling | Habits werden direkt im Calendar geplant |
|
||||
| Smart Slots | "Wo passt 30min für Sport?" — leerer Slot-Detection |
|
||||
| Multi-Type Quick-Create | Floating-Action: erstellt event/task/habit/focus aus einem Menü |
|
||||
| Analytics Dashboard | Wie viele Stunden in welchem Type, Plan vs Reality |
|
||||
| iCal Export | Standard-konformer .ics Download je User/View |
|
||||
| Cross-Module DnD | Items aus jedem Modul → Calendar als TimeBlock |
|
||||
| Activity Feed Widget | Dashboard-Widget zeigt die letzten N TimeBlocks |
|
||||
| "Mein Tag" Widget | Dashboard zeigt die heutige Timeline mit aktueller Position |
|
||||
|
||||
### CalendarEventsWidget update
|
||||
|
||||
Das alte Dashboard-Widget las aus der `events` Tabelle. Heute auf TimeBlocks umgestellt — zeigt jetzt auch Habits, Tasks, Focus-Sessions die heute anstehen.
|
||||
|
||||
---
|
||||
|
||||
## Rename: ManaCore → Mana
|
||||
|
||||
Der Code-Name "ManaCore" hatte seinen Zweck erfüllt — es ist Zeit für den finalen Markennamen.
|
||||
|
||||
### Was sich ändert
|
||||
|
||||
- **App-Domain bleibt**: `mana.how`
|
||||
- **Code-Name**: `ManaCore` → `Mana`
|
||||
- **Pfade**: `apps/manacore/` → `apps/mana/`
|
||||
- **Packages**: `@manacore/*` → `@mana/*`
|
||||
- **Workspace-Configs**, Docker-Compose Service-Names, Env-Vars, CI-Pipelines
|
||||
|
||||
### Was schiefgehen kann
|
||||
|
||||
Ein Rename quer durchs Monorepo ist immer riskant. Heute waren die Folge-Bugs:
|
||||
|
||||
- **Type errors** in Templates und Stale-Referenzen
|
||||
- **Duplicate `manaSvg`** in shared-branding (Rename-Collision mit existierendem `manaSvg`)
|
||||
- **Type-Extraction aus `.svelte`** Dateien für named re-exports
|
||||
- **Eslint OOM** — Heap auf 8 GB hochgesetzt damit der Lint nicht vorzeitig stirbt
|
||||
|
||||
Plus: Mukke heißt jetzt **Music**. Cleaner Name, weniger Insider, plus Cover-Art-Upload via mana-media (passt zum gestrigen Refactor).
|
||||
|
||||
---
|
||||
|
||||
## PWA-Support für die Unified App
|
||||
|
||||
Ziel: Mana läuft als installierbare PWA auf Mobile, mit Offline-Fallback und Update-Prompt.
|
||||
|
||||
### Was heute live ist
|
||||
|
||||
| Feature | Detail |
|
||||
| ------------------ | ------------------------------------------------------ |
|
||||
| **Service Worker** | Caches Shell + Modul-Routes, Network-First für Daten |
|
||||
| **Offline-Page** | `/offline` mit Hinweis + Retry-Button |
|
||||
| **Update-Prompt** | Toast wenn neue Version verfügbar — 1 Click Reload |
|
||||
| **Icons** | maskable + apple-touch-icon, alle Größen |
|
||||
| **Manifest** | Standalone-Display, Theme-Color, Shortcuts in 5 Module |
|
||||
| **Install-Prompt** | "Mana installieren" Banner für Erst-User |
|
||||
|
||||
### Mobile Responsiveness komplett
|
||||
|
||||
Alle Module + alle shared-Components heute auf Mobile getestet:
|
||||
|
||||
- **PageShell** kollabiert auf Mobile in Single-Column
|
||||
- **Workbench Pages** stacken vertikal
|
||||
- **PillNav** schrumpft auf Icon-Only
|
||||
- **Detail-Overlays** werden full-screen
|
||||
- **Tag-Strip** scrollt horizontal
|
||||
- **Bottom-Stack** respektiert Safe-Area-Insets
|
||||
|
||||
### PWA-Phase im Tauri-v2-Plan abgehakt
|
||||
|
||||
Der Tauri-v2-Plan hatte PWA als Vorstufe. Heute komplett — der nächste Schritt (native Tauri-Wrapper) kann beginnen.
|
||||
|
||||
---
|
||||
|
||||
## Sonstige Fixes
|
||||
|
||||
| Fix | Detail |
|
||||
| ------------------- | --------------------------------------------- |
|
||||
| Calendar `WeekView` | Duplicate `calendarViewStore` Import entfernt |
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
| Bereich | Commits | Highlights |
|
||||
| --------------- | ------- | -------------------------------------------------------------------------------- |
|
||||
| TimeBlocks | ~10 | Unified time model, rrule.js, focus mode, smart slots, timeline, iCal, analytics |
|
||||
| ManaCore → Mana | ~3 | Globaler Rename, type-fixes, eslint OOM-bump |
|
||||
| PWA + Mobile | ~3 | Service Worker, offline, update prompt, mobile responsive in allen Modulen |
|
||||
| Mukke → Music | ~2 | Rename + cover art via mana-media |
|
||||
| Dashboard | ~1 | "Mein Tag" Timeline-Widget |
|
||||
|
||||
---
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
- TimeBlocks Sharing (TimeBlocks aus shared Calendar)
|
||||
- Smart-Slot-Suggestions für Habits-Scheduling intelligenter
|
||||
- Sprint 1 der Data-Layer-Audit-Backlog (LWW + Atomic Cascades)
|
||||
- Encryption-Roadmap der user-typed Felder
|
||||
|
|
@ -0,0 +1,360 @@
|
|||
---
|
||||
title: 'Encryption Phasen 1–9: Vault-Ende-zu-Ende + Dreams, Cycles, Events Module'
|
||||
description: 'Größter Tag der Woche: AES-GCM-256 Encryption für 27 Tabellen in 9 Phasen ausgerollt, inkl. Zero-Knowledge-Modus mit Recovery-Code. Plus drei neue Module: Dreams (Voice→STT), Cycles (Menstrual-Tracking) und Events (öffentliche RSVP).'
|
||||
date: 2026-04-07
|
||||
author: 'Till Schneider'
|
||||
category: 'feature'
|
||||
tags:
|
||||
[
|
||||
'encryption',
|
||||
'vault',
|
||||
'zero-knowledge',
|
||||
'recovery-code',
|
||||
'dreams',
|
||||
'cycles',
|
||||
'events',
|
||||
'rsvp',
|
||||
'mana-stt',
|
||||
'data-layer-audit',
|
||||
'sprint',
|
||||
]
|
||||
featured: true
|
||||
commits: 88
|
||||
readTime: 18
|
||||
stats:
|
||||
filesChanged: 880
|
||||
linesAdded: 38302
|
||||
linesRemoved: 22129
|
||||
contributors:
|
||||
- name: 'Till Schneider'
|
||||
handle: 'Till-JS'
|
||||
commits: 88
|
||||
workingHours:
|
||||
start: '2026-04-07T12:26'
|
||||
end: '2026-04-07T23:57'
|
||||
---
|
||||
|
||||
## Highlights
|
||||
|
||||
- **At-Rest Encryption** in 9 Phasen ausgerollt: AES-GCM-256 für 27 Tabellen
|
||||
- **Zero-Knowledge-Modus** mit User-only Recovery-Code (Mana kann nichts lesen)
|
||||
- **Lock-Screen** mit Recovery-Unlock-Modal
|
||||
- **Drei neue Module**: Dreams (Traumtagebuch), Cycles (Zyklus-Tracking), Events (öffentliche RSVP)
|
||||
- **Data-Layer-Audit Sprints 1–4** abgeschlossen — LWW, retry, atomic cascades, perf, quota, telemetry
|
||||
- **mana-stt Voice-Pipeline** für Dreams + Memoro live
|
||||
- **Pre-Launch Cleanup** — Schema-Collapse, Ghost-API-Clients raus, RLS auf sync_changes
|
||||
|
||||
---
|
||||
|
||||
## Encryption: 9 Phasen in einem Tag
|
||||
|
||||
Das große Thema des Tages. Die `DATA_LAYER_AUDIT.md` hatte Encryption als langfristige Roadmap — heute durchgezogen. **Alle 9 Phasen heute committed.**
|
||||
|
||||
### Designprinzipien
|
||||
|
||||
```
|
||||
─────────────────────────────────────────────────
|
||||
Master Key (MK) ── 256 bit
|
||||
└─ AES-GCM-256 für alle Records
|
||||
└─ liegt nirgendwo unverschlüsselt rum
|
||||
─────────────────────────────────────────────────
|
||||
Standard-Modus
|
||||
MK wird mit KEK (Key Encryption Key) wrapped
|
||||
KEK liegt im mana-auth Service (`MANA_AUTH_KEK` env)
|
||||
→ Mana kann den MK rekonstruieren
|
||||
→ User braucht nur Login
|
||||
─────────────────────────────────────────────────
|
||||
Zero-Knowledge-Modus (opt-in)
|
||||
MK wird mit user-derivierten Recovery-Code
|
||||
verschlüsselt, KEK-wrapped Version wird gelöscht
|
||||
→ Mana kann den MK NICHT rekonstruieren
|
||||
→ User braucht Recovery-Code zum Entsperren
|
||||
─────────────────────────────────────────────────
|
||||
```
|
||||
|
||||
### Phase 1: Foundation (No-Op)
|
||||
|
||||
`apps/mana/apps/web/src/lib/data/crypto/` mit allen Primitives angelegt — `encryptRecord`, `decryptRecords`, registry, key-derivation. Alles compiled, nichts wird tatsächlich verschlüsselt. Zero-Risk-Foundation.
|
||||
|
||||
### Phase 2: Server-Side Master-Key Custody
|
||||
|
||||
`mana-auth` bekommt Vault-Endpoints:
|
||||
|
||||
- `POST /api/v1/vault/init` — neuer User: MK generieren, KEK-wrappen, persistieren
|
||||
- `GET /api/v1/vault/unlock` — Login: KEK-unwrap, MK an Client liefern (https-only, kurzlebige Session)
|
||||
- `POST /api/v1/vault/rotate-recovery` — Recovery-Code rotieren
|
||||
|
||||
Plus Tests gegen echte Postgres (`vault.spec.ts`).
|
||||
|
||||
### Phase 3: Vault-Client + Record-Helpers + Layout-Wire-Up
|
||||
|
||||
Client-Seite:
|
||||
|
||||
- `vaultClient` lädt MK beim Login
|
||||
- `encryptRecord(table, record)` und `decryptRecords(table, records)` Helpers
|
||||
- App-Layout wartet auf Vault-Unlock bevor Module geladen werden
|
||||
|
||||
### Phase 4: Notes-Pilot
|
||||
|
||||
Erstes Modul mit aktiver Encryption: **Notes**. Klein, kontrolliert, low-risk. Funktioniert? → Phase 5.
|
||||
|
||||
### Phase 5: Rollout auf 6 Module
|
||||
|
||||
chat, dreams, memoro, contacts, cycles, finance — alles user-typed Content der eindeutig privat ist.
|
||||
|
||||
### Phase 6: Polish + UI
|
||||
|
||||
- **6.1**: cards, presi, inventar, planta — Karten + Notiz-haltige Module
|
||||
- **6.2**: Settings-Page mit Vault-Status (verschlüsselt seit, ZK an/aus, …)
|
||||
- **6.3**: Onboarding-Banner für neue User der Encryption erklärt
|
||||
|
||||
### Phase 7: Coupled & Storeless Tables
|
||||
|
||||
- **7.1**: Tasks + Calendar Events — beide referenzieren TimeBlocks und müssen synchron verschlüsselt werden
|
||||
- **7.2**: Storeless Module (questions, links, documents, meals) die keinen eigenen Store haben aber sensitive Felder
|
||||
|
||||
### Phase 8: Restliche Tabellen
|
||||
|
||||
Storage-Items, Picture-Boards, Music-Metadata, Events, Guests. **Damit sind alle 27 Tabellen verschlüsselt.**
|
||||
|
||||
### Phase 9: Zero-Knowledge
|
||||
|
||||
Das härteste Stück. Bisher konnte mana-auth den MK immer rekonstruieren. ZK-Modus macht das per Design unmöglich.
|
||||
|
||||
#### Milestone 1: Recovery-Code Primitives
|
||||
|
||||
- 32-Byte Random-Code, BIP39-encodiert (24 Wörter)
|
||||
- PBKDF2 mit 600k Iterations als KDF
|
||||
- Recovery-Wrap des MK
|
||||
|
||||
#### Milestone 2: mana-auth Vault-Recovery-Wrap
|
||||
|
||||
- Server speichert NUR den recovery-wrapped MK
|
||||
- KEK-wrapped Version wird gelöscht beim Aktivieren von ZK
|
||||
- Server kann den MK nicht mehr unwrap'en
|
||||
|
||||
#### Milestone 3: Vault-Client Recovery-Flow
|
||||
|
||||
- ZK-User wird beim Login gepromtet den Recovery-Code einzugeben
|
||||
- Lock-Screen-Modal mit 24-Wort-Eingabe
|
||||
- Unwrap clientseitig, MK in Memory
|
||||
|
||||
#### Milestone 4: Settings UI
|
||||
|
||||
- Settings → Sicherheit zeigt:
|
||||
- Aktueller Modus (Standard / ZK)
|
||||
- "Recovery-Code anzeigen" (mit Re-Auth)
|
||||
- "Zero-Knowledge aktivieren" (irreversibel-ish — Recovery-Code wird einmal gezeigt)
|
||||
- "Recovery-Code rotieren" (auch in ZK-Modus möglich)
|
||||
|
||||
#### Lock-Screen
|
||||
|
||||
- Wenn die Tab inaktiv war > X Minuten oder die Seite manuell gesperrt wird
|
||||
- Screen verlangt Recovery-Code (ZK) oder Passwort (Standard)
|
||||
- Modal blendet alles ab, App ist nicht bedienbar bis unlock
|
||||
|
||||
### Vault-Status-Endpoint
|
||||
|
||||
`GET /api/v1/vault/status` liefert für die Settings-Page:
|
||||
|
||||
```json
|
||||
{
|
||||
"encrypted": true,
|
||||
"mode": "standard" | "zero_knowledge",
|
||||
"encryptedSince": "2026-04-07T15:23:00Z",
|
||||
"recoveryCodeRotatedAt": "2026-04-07T15:23:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Audit-Roll-up
|
||||
|
||||
`DATA_LAYER_AUDIT.md` mit allen Phasen 6/7/8/9 dokumentiert. Plus eine separate Roadmap **`FILE_BYTES_ENCRYPTION_PLAN.md`** für die nächste Stufe (verschlüsselte Bild/Audio/PDF-Bytes — bisher sind nur strukturierte Felder verschlüsselt).
|
||||
|
||||
---
|
||||
|
||||
## Drei neue Module
|
||||
|
||||
Während die Encryption durch die Phasen lief, entstanden parallel drei neue Module.
|
||||
|
||||
### Dreams (Traumtagebuch)
|
||||
|
||||
- **Voice-Capture via mana-stt** — Aufnahme im Browser, Transkript wird automatisch eingefügt
|
||||
- **Symbol-Library** mit Detail-Views, Bedeutung, Mood-Stats
|
||||
- **Filter-Tabs** und Symbol-Filter-Pills
|
||||
- **Date-/Time-Picker** statt Standard-Inputs
|
||||
- **Auto-Save**, Sort, Merge, Navigation in der Symbol-Library
|
||||
- **Mic-Permission UX** auf macOS — wenn Browser den Prompt nicht zeigt, gibt's einen erklärenden Screen + Force-Retry
|
||||
- **Proxy-Toleranz**: octet-stream und invalid form bodies werden vom Voice-Proxy nicht abgewiesen
|
||||
|
||||
### Cycles (Menstruelle Zyklus-Tracking)
|
||||
|
||||
- **Period Auto-Detect**: Start/Ende werden aus Symptomen + Bleeding-Levels abgeleitet
|
||||
- **Symptom Management UI**: konfigurierbare Symptome mit Severity
|
||||
- **Edit/Delete past entries**
|
||||
- **Month Calendar View** mit Phase-Coloring (folliculär, ovulatorisch, luteal, menstruell)
|
||||
- **Dashboard-Widget** mit aktueller Phase + Countdown zum nächsten Event
|
||||
- **Locale-aware date formatting**
|
||||
- **Echte i18n** für it/fr/es (es waren leere Strings im Stub)
|
||||
- **i18n Key-Parity Tests** für alle 5 Locales
|
||||
- **Integration-Tests** mit fake-indexeddb
|
||||
- **ROADMAP** mit zukünftigen Features
|
||||
|
||||
### Events (Public RSVP Module)
|
||||
|
||||
Event-Modul für Ad-hoc Veranstaltungen mit öffentlichem RSVP-Link:
|
||||
|
||||
- **`mana-events` Service** (Hono/Bun) — own DB schema, public RSVP routes
|
||||
- **Phase 1a**: Scaffold lokaler Tabellen + UI
|
||||
- **Phase 1b**: Public RSVP-Flow mit Cancel-Token
|
||||
- **Phase 2**: Bring-List ("Wer bringt was?") — Slot-Reservation, Multi-User
|
||||
- **35 Server-Tests** für routes + sweeper
|
||||
- **Playwright e2e** mit flake-resistant config
|
||||
- **i18n** für RSVP-Page in it/fr/es + extracted helper
|
||||
- **Cascade rate buckets** wenn Event un-published wird
|
||||
- **Self-heal Snapshots, Tombstones, Polling-Cleanup**
|
||||
- **Production wiring + Polling resilience** (quick wins)
|
||||
- **Roadmap** für Phase 2 (tech debt + remaining features)
|
||||
|
||||
---
|
||||
|
||||
## Data-Layer-Audit: Sprints 1–4
|
||||
|
||||
Die `DATA_LAYER_AUDIT.md` Backlog hatte vier Sprints. Heute alle vier abgeschlossen.
|
||||
|
||||
### Sprint 1: Data Integrity
|
||||
|
||||
- **LWW (Last-Write-Wins)** mit Field-Level Timestamps
|
||||
- **Retry mit Exponential Backoff**
|
||||
- **Atomic Cascades** — wenn ein Parent gelöscht wird, werden Children atomisch markiert
|
||||
- **Three runtime regressions** im Anschluss gefixt
|
||||
|
||||
### Sprint 2: Auth-Aware Data Layer + Guest Migration
|
||||
|
||||
- Data-Layer kennt jetzt den `userId` zum Stempeln
|
||||
- Guest → registered Migration übernimmt alle existierenden lokalen Daten (mit User-Stempel)
|
||||
|
||||
### Sprint 3: Type-Safe Sync Protocol
|
||||
|
||||
- Sync-Protocol bekommt einen Zod-Schema
|
||||
- Client + Server validieren beim Encode/Decode
|
||||
- Tests die das Schema gegen mana-sync (Go) validieren
|
||||
- **3 Pre-existing Test-Files** wieder lauffähig gemacht
|
||||
|
||||
### Sprint 4: Perf, Quota, Telemetry
|
||||
|
||||
- **`updatedAt` Index** für Recent-X Dashboard-Widgets
|
||||
- **Quota-Recovery** wenn IndexedDB-Quota voll ist (Auto-Prune oldest)
|
||||
- **Telemetry-Hooks** für Sync-Events
|
||||
- **SSE-Pipeline-Read** parallel zu sequential apply (perf win)
|
||||
- **Local Activity Log** mit periodic prune
|
||||
|
||||
### Toast-Subscription
|
||||
|
||||
Data-Layer-Events werden jetzt direkt subscribed:
|
||||
|
||||
- Sync-Errors → Toast + Sentry
|
||||
- Quota-Warnings → Toast
|
||||
- Conflict-Detected → Toast mit "View Conflict"
|
||||
- Scheduler-Events → Toast (für Reminders)
|
||||
|
||||
---
|
||||
|
||||
## mana-stt Voice-Pipeline
|
||||
|
||||
Dreams + Memoro nutzen jetzt eine geteilte Voice-Pipeline:
|
||||
|
||||
```
|
||||
Browser MediaRecorder
|
||||
↓ POST /api/v1/voice/transcribe
|
||||
mana-web Proxy
|
||||
↓ X-Service-Key
|
||||
mana-stt (Windows GPU, WhisperX)
|
||||
↓
|
||||
Transcript JSON
|
||||
↓
|
||||
Modul (Dreams: zur Note, Memoro: zum Memo)
|
||||
```
|
||||
|
||||
**STT-Postmortem heute** — `docs/postmortems/2026-04-07-stt-tunnel-down.md` dokumentiert einen 35-Minuten-Ausfall der STT-Pipeline (Cloudflare Tunnel zur Windows-GPU war runtergefallen). Fix: Tunnel-Health-Probe in Status-Page integriert.
|
||||
|
||||
**GPU Tunnel Setup** + **STT env wiring** in dem Postmortem dokumentiert.
|
||||
|
||||
---
|
||||
|
||||
## Pre-Launch Cleanup
|
||||
|
||||
Vor dem Production-Launch eine größere Aufräumrunde:
|
||||
|
||||
### Schema-Collapse + Dead Code
|
||||
|
||||
- **`PRE_LAUNCH_CLEANUP.md`** dokumentiert was raus kam und warum
|
||||
- **Lazy Search** statt eager loading von search-providern
|
||||
- **Ghost Backend-API-Clients** entfernt — Module die noch HTTP-Clients hatten obwohl alles über die unified API geht
|
||||
|
||||
### Mac-Mini Infra-Cleanup
|
||||
|
||||
- `COMPOSE_PROJECT_NAME=manacore-monorepo` pinned
|
||||
- Compose-Env, blackbox-Memory, Prometheus GPU-Probes optimiert
|
||||
- Runbook-Hardening: Status-Diff Script, Ingress-Walk Script
|
||||
- `startup.sh` idempotent + non-destruktiv gemacht
|
||||
|
||||
### Sync RLS
|
||||
|
||||
- `sync_changes` Tabelle bekommt **row-level security** in PostgreSQL
|
||||
- User können nur ihre eigenen Changes lesen, auch wenn jemand Postgres-Direct-Access hätte
|
||||
|
||||
### `rrule` SSR Bundle
|
||||
|
||||
- `/calendar` 500-Error gefixt: rrule wurde in SSR-Build nicht inkludiert
|
||||
- Vite-Config: rrule explizit als `noExternal`
|
||||
|
||||
---
|
||||
|
||||
## Sonstige Fixes & Polish
|
||||
|
||||
| Fix | Detail |
|
||||
| -------------------------------------- | ---------------------------------------------- |
|
||||
| timeblocks recurrence migration | Type-Errors aus dem Sprint von vorgestern |
|
||||
| ManaCore→Mana stale templates | Type-Errors vom Rename |
|
||||
| `manaSvg` dedupe | Rename-Collision in shared-branding |
|
||||
| `cards-database` `.js` extensions | Für NodeNext-Module-Resolution |
|
||||
| vitest unify | Workspace-weit auf `^4.1.2` |
|
||||
| `/offline` prerender | Disabled — FIXME, prerender-Schritt war kaputt |
|
||||
| `module-registry` + `module.config.ts` | Build-critical files committed |
|
||||
| MANA_STT_URL/API_KEY Wiring | mana-web Container env |
|
||||
|
||||
---
|
||||
|
||||
## Dokumentation
|
||||
|
||||
- **`DATA_LAYER_AUDIT.md`** — Sprints 1–4 + Encryption-Phasen 1–9 vollständig dokumentiert
|
||||
- **`PRE_LAUNCH_CLEANUP.md`** — was wurde entfernt vor Launch und warum
|
||||
- **`FILE_BYTES_ENCRYPTION_PLAN.md`** — nächste Encryption-Stufe für Bytes/Bilder
|
||||
- **`docs/postmortems/2026-04-07-stt-tunnel-down.md`** — STT-Ausfall Postmortem
|
||||
- **`docs/cycles/ROADMAP.md`** — Cycles Feature-Backlog
|
||||
- **`docs/events/PHASE2_ROADMAP.md`** — Events Phase 2 + tech debt
|
||||
- GPU Tunnel Setup, STT env wiring docs
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
| Bereich | Commits | Highlights |
|
||||
| --------------------- | ------- | ------------------------------------------------------------------ |
|
||||
| Encryption Phasen 1–9 | ~22 | 27 Tabellen, ZK-Modus, Recovery-Code, Lock-Screen, Settings, Tests |
|
||||
| Data-Layer Sprints | ~8 | LWW, retry, cascades, perf, quota, telemetry |
|
||||
| Dreams Modul | ~9 | Voice via mana-stt, Symbol-Library, Mic-UX |
|
||||
| Cycles Modul | ~12 | Phase-Detection, Symptome, Calendar-View, Widget, i18n |
|
||||
| Events Modul | ~12 | RSVP-Flow, Bring-List, 35 Tests, Playwright, Phase 2 |
|
||||
| mana-stt | ~3 | Voice-Pipeline, Postmortem, GPU-Tunnel |
|
||||
| Pre-Launch Cleanup | ~7 | Schema-Collapse, RLS, idempotent startup |
|
||||
| Sonstige Fixes | ~15 | Type-Errors aus Renames, vitest unify, build fixes |
|
||||
|
||||
---
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
- File-Bytes Encryption (Bilder, Audio, PDFs)
|
||||
- Login-Flow Polish (passkey UI, structured errors)
|
||||
- Voice-Quick-Add für Notes + Todo (nicht nur Dreams + Memoro)
|
||||
- AI-Services konsolidieren auf Windows GPU als Source of Truth
|
||||
Loading…
Add table
Add a link
Reference in a new issue